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..284b2aec7d397a4bca7e62e41bee29b877dbf891 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 = static_cast<int>(m_subrunRanges.size()) - 1; rangeIndex >= 0; --rangeIndex) { |
+ 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) |