Chromium Code Reviews| Index: third_party/WebKit/Source/core/layout/svg/SVGTextMetricsBuilder.cpp |
| diff --git a/third_party/WebKit/Source/core/layout/svg/SVGTextMetricsBuilder.cpp b/third_party/WebKit/Source/core/layout/svg/SVGTextMetricsBuilder.cpp |
| index 70d014dc02e049924f22a7c7bea94715a1f0f413..90995c532af94a963044f7d4539bfd05c9d3a379 100644 |
| --- a/third_party/WebKit/Source/core/layout/svg/SVGTextMetricsBuilder.cpp |
| +++ b/third_party/WebKit/Source/core/layout/svg/SVGTextMetricsBuilder.cpp |
| @@ -24,8 +24,7 @@ |
| #include "core/layout/svg/LayoutSVGInlineText.h" |
| #include "core/layout/svg/LayoutSVGText.h" |
| #include "core/layout/svg/SVGTextMetrics.h" |
| -#include "platform/fonts/GlyphBuffer.h" |
| -#include "platform/fonts/shaping/SimpleShaper.h" |
| +#include "platform/fonts/CharacterRange.h" |
| #include "platform/text/BidiCharacterRun.h" |
| #include "platform/text/BidiResolver.h" |
| #include "platform/text/TextDirection.h" |
| @@ -41,34 +40,52 @@ public: |
| SVGTextMetricsCalculator(LayoutSVGInlineText*); |
| ~SVGTextMetricsCalculator(); |
| - SVGTextMetrics computeMetricsForCharacter(unsigned textPosition); |
| - unsigned textLength() const { return static_cast<unsigned>(m_run.charactersLength()); } |
| + bool advancePosition(); |
| + unsigned currentPosition() const { return m_currentPosition; } |
| - bool characterStartsSurrogatePair(unsigned textPosition) const |
| + SVGTextMetrics currentCharacterMetrics(); |
| + |
| + // TODO(pdr): Character-based iteration is ambiguous and error-prone. It |
| + // should be unified under a single concept. See: https://crbug.com/593570 |
| + bool currentCharacterStartsSurrogatePair() const |
| + { |
| + return U16_IS_LEAD(m_run[m_currentPosition]) && m_currentPosition + 1 < characterCount() && U16_IS_TRAIL(m_run[m_currentPosition + 1]); |
| + } |
| + bool currentCharacterIsWhiteSpace() const |
| { |
| - return U16_IS_LEAD(m_run[textPosition]) && textPosition + 1 < textLength() && U16_IS_TRAIL(m_run[textPosition + 1]); |
| + return m_run[m_currentPosition] == ' '; |
| } |
| - bool characterIsWhiteSpace(unsigned textPosition) const |
| + unsigned characterCount() const |
| { |
| - return m_run[textPosition] == ' '; |
| + return static_cast<unsigned>(m_run.charactersLength()); |
| } |
| private: |
| void setupBidiRuns(); |
| + // Ensure |m_subrunRanges| is updated for the current bidi run, or the |
| + // complete m_run if no bidi runs are present. Returns the current position |
| + // in the subrun which can be used to index into |m_subrunRanges|. |
| + unsigned updateSubrunRangesForCurrentPosition(); |
| + |
| + // Current character position in m_text. |
| + unsigned m_currentPosition; |
| + |
| LineLayoutSVGInlineText m_text; |
| - BidiCharacterRun* m_bidiRun; |
| TextRun m_run; |
| + |
| + BidiCharacterRun* m_bidiRun; |
| BidiResolver<TextRunIterator, BidiCharacterRun> m_bidiResolver; |
| - float m_totalWidth; |
| - TextDirection m_textDirection; |
| + |
| + // Ranges for the current bidi run if present, or the entire run otherwise. |
| + Vector<CharacterRange> m_subrunRanges; |
| }; |
| SVGTextMetricsCalculator::SVGTextMetricsCalculator(LayoutSVGInlineText* text) |
| - : m_text(LineLayoutSVGInlineText(text)) |
| - , m_bidiRun(nullptr) |
| + : m_currentPosition(0) |
| + , m_text(LineLayoutSVGInlineText(text)) |
| , m_run(SVGTextMetrics::constructTextRun(m_text, 0, m_text.textLength(), m_text.styleRef().direction())) |
| - , m_totalWidth(0) |
| + , m_bidiRun(nullptr) |
| { |
| setupBidiRuns(); |
| } |
| @@ -81,11 +98,8 @@ SVGTextMetricsCalculator::~SVGTextMetricsCalculator() |
| void SVGTextMetricsCalculator::setupBidiRuns() |
| { |
| - const ComputedStyle& style = m_text.styleRef(); |
| - m_textDirection = style.direction(); |
| - if (isOverride(style.unicodeBidi())) |
| + if (isOverride(m_text.styleRef().unicodeBidi())) |
| return; |
| - |
| BidiStatus status(LTR, false); |
| status.last = status.lastStrong = WTF::Unicode::OtherNeutral; |
| m_bidiResolver.setStatus(status); |
| @@ -97,36 +111,66 @@ void SVGTextMetricsCalculator::setupBidiRuns() |
| m_bidiRun = bidiRuns.firstRun(); |
| } |
| -SVGTextMetrics SVGTextMetricsCalculator::computeMetricsForCharacter(unsigned textPosition) |
| +bool SVGTextMetricsCalculator::advancePosition() |
| +{ |
| + m_currentPosition += currentCharacterStartsSurrogatePair() ? 2 : 1; |
| + return m_currentPosition < characterCount(); |
| +} |
| + |
| +unsigned SVGTextMetricsCalculator::updateSubrunRangesForCurrentPosition() |
| { |
| if (m_bidiRun) { |
| - if (textPosition >= static_cast<unsigned>(m_bidiRun->stop())) { |
| + if (m_currentPosition >= static_cast<unsigned>(m_bidiRun->stop())) { |
| m_bidiRun = m_bidiRun->next(); |
| - // New BiDi run means new reference position for measurements, so reset |m_totalWidth|. |
| - m_totalWidth = 0; |
| + // Ensure new subrange ranges are computed below. |
| + m_subrunRanges.clear(); |
| } |
| ASSERT(m_bidiRun); |
| - ASSERT(static_cast<int>(textPosition) < m_bidiRun->stop()); |
| - m_textDirection = m_bidiRun->direction(); |
| + ASSERT(static_cast<int>(m_currentPosition) < m_bidiRun->stop()); |
| + } |
| + |
| + unsigned positionInRun = m_bidiRun ? m_currentPosition - m_bidiRun->start() : m_currentPosition; |
| + if (positionInRun >= m_subrunRanges.size()) { |
| + unsigned subrunStart = m_bidiRun ? m_bidiRun->start() : 0; |
| + unsigned subrunEnd = m_bidiRun ? m_bidiRun->stop() : m_run.charactersLength(); |
| + TextDirection subrunDirection = m_bidiRun ? m_bidiRun->direction() : m_text.styleRef().direction(); |
| + TextRun subRun = SVGTextMetrics::constructTextRun(m_text, subrunStart, subrunEnd - subrunStart, subrunDirection); |
| + m_subrunRanges = m_text.scaledFont().individualCharacterRanges(subRun, 0, subRun.length() - 1); |
| + |
| + // TODO(pdr): We only have per-glyph data so we need to synthesize per- |
| + // grapheme data. E.g., if 'fi' is shaped into a single glyph, we do not |
| + // know the 'i' position. The code below synthesizes an average glyph |
| + // width when characters share a position. This will incorrectly split |
| + // combining diacritics. See: https://crbug.com/473476. |
| + unsigned distributeCount = 0; |
| + for (int rangeIndex = m_subrunRanges.size() - 1; rangeIndex >= 0; --rangeIndex) { |
|
fs
2016/03/10 09:55:57
Nit: People like to yell about "implementation-def
|
| + CharacterRange& currentRange = m_subrunRanges[rangeIndex]; |
| + if (currentRange.width() == 0) { |
| + distributeCount++; |
| + } else if (distributeCount != 0) { |
| + distributeCount++; |
| + float newWidth = currentRange.width() / distributeCount; |
| + currentRange.end = currentRange.start + newWidth; |
| + for (unsigned distribute = 1; distribute < distributeCount; distribute++) { |
| + unsigned distributeIndex = rangeIndex + distribute; |
| + float newStartPosition = m_subrunRanges[distributeIndex - 1].end; |
| + m_subrunRanges[distributeIndex].start = newStartPosition; |
| + m_subrunRanges[distributeIndex].end = newStartPosition + newWidth; |
| + } |
| + distributeCount = 0; |
| + } |
| + } |
| } |
| - unsigned metricsLength = characterStartsSurrogatePair(textPosition) ? 2 : 1; |
| - SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(m_text, textPosition, metricsLength, m_textDirection); |
| - ASSERT(metrics.length() == metricsLength); |
| - |
| - unsigned startPosition = m_bidiRun ? m_bidiRun->start() : 0; |
| - ASSERT(startPosition <= textPosition); |
| - SVGTextMetrics complexStartToCurrentMetrics = SVGTextMetrics::measureCharacterRange(m_text, startPosition, textPosition - startPosition + metricsLength, m_textDirection); |
| - // Frequent case for Arabic text: when measuring a single character the arabic isolated form is taken |
| - // when laying out the glyph "in context" (with it's surrounding characters) it changes due to shaping. |
| - // So whenever currentWidth != currentMetrics.width(), we are processing a text run whose length is |
| - // not equal to the sum of the individual lengths of the glyphs, when measuring them isolated. |
| - float currentWidth = complexStartToCurrentMetrics.width() - m_totalWidth; |
| - if (currentWidth != metrics.width()) |
| - metrics.setWidth(currentWidth); |
| - |
| - m_totalWidth = complexStartToCurrentMetrics.width(); |
| - return metrics; |
| + return positionInRun; |
| +} |
| + |
| +SVGTextMetrics SVGTextMetricsCalculator::currentCharacterMetrics() |
| +{ |
| + unsigned currentSubrunPosition = updateSubrunRangesForCurrentPosition(); |
| + unsigned length = currentCharacterStartsSurrogatePair() ? 2 : 1; |
| + float width = m_subrunRanges[currentSubrunPosition].width(); |
| + return SVGTextMetrics(m_text, length, width); |
| } |
| struct MeasureTextData { |
| @@ -155,47 +199,46 @@ static void measureTextLayoutObject(LayoutSVGInlineText* text, MeasureTextData* |
| textMetricsValues->clear(); |
| } |
| + // TODO(pdr): This loop is too tightly coupled to SVGTextMetricsCalculator. |
| + // We should refactor SVGTextMetricsCalculator to be a simple bidi run |
| + // iterator and move all subrun logic to a single function. |
| SVGTextMetricsCalculator calculator(text); |
| bool preserveWhiteSpace = text->style()->whiteSpace() == PRE; |
| unsigned surrogatePairCharacters = 0; |
| unsigned skippedCharacters = 0; |
| - unsigned textPosition = 0; |
| - unsigned textLength = calculator.textLength(); |
| - |
| - SVGTextMetrics currentMetrics; |
| - for (; textPosition < textLength; textPosition += currentMetrics.length()) { |
| - currentMetrics = calculator.computeMetricsForCharacter(textPosition); |
| - if (!currentMetrics.length()) |
| + do { |
| + SVGTextMetrics metrics = calculator.currentCharacterMetrics(); |
| + if (!metrics.length()) |
| break; |
| - bool characterIsWhiteSpace = calculator.characterIsWhiteSpace(textPosition); |
| + bool characterIsWhiteSpace = calculator.currentCharacterIsWhiteSpace(); |
| if (characterIsWhiteSpace && !preserveWhiteSpace && data->lastCharacterWasWhiteSpace) { |
| if (processLayoutObject) |
| textMetricsValues->append(SVGTextMetrics(SVGTextMetrics::SkippedSpaceMetrics)); |
| if (data->allCharactersMap) |
| - skippedCharacters += currentMetrics.length(); |
| + skippedCharacters += metrics.length(); |
| continue; |
| } |
| if (processLayoutObject) { |
| if (data->allCharactersMap) { |
| - const SVGCharacterDataMap::const_iterator it = data->allCharactersMap->find(data->valueListPosition + textPosition - skippedCharacters - surrogatePairCharacters + 1); |
| + const SVGCharacterDataMap::const_iterator it = data->allCharactersMap->find(data->valueListPosition + calculator.currentPosition() - skippedCharacters - surrogatePairCharacters + 1); |
| if (it != data->allCharactersMap->end()) |
| - attributes->characterDataMap().set(textPosition + 1, it->value); |
| + attributes->characterDataMap().set(calculator.currentPosition() + 1, it->value); |
| } |
| - textMetricsValues->append(currentMetrics); |
| + textMetricsValues->append(metrics); |
| } |
| - if (data->allCharactersMap && calculator.characterStartsSurrogatePair(textPosition)) |
| + if (data->allCharactersMap && calculator.currentCharacterStartsSurrogatePair()) |
| surrogatePairCharacters++; |
| data->lastCharacterWasWhiteSpace = characterIsWhiteSpace; |
| - } |
| + } while (calculator.advancePosition()); |
| if (!data->allCharactersMap) |
| return; |
| - data->valueListPosition += textPosition - skippedCharacters; |
| + data->valueListPosition += calculator.currentPosition() - skippedCharacters; |
| } |
| static void walkTree(LayoutSVGText* start, LayoutSVGInlineText* stopAtLeaf, MeasureTextData* data) |