| Index: third_party/WebKit/Source/core/layout/svg/LayoutSVGInlineText.cpp
|
| diff --git a/third_party/WebKit/Source/core/layout/svg/LayoutSVGInlineText.cpp b/third_party/WebKit/Source/core/layout/svg/LayoutSVGInlineText.cpp
|
| index 2de7c1730e6b718d67cd8c3ed648fc7742a499b5..3154e73ea24311e011b1b2f6700a7d9c2506d582 100644
|
| --- a/third_party/WebKit/Source/core/layout/svg/LayoutSVGInlineText.cpp
|
| +++ b/third_party/WebKit/Source/core/layout/svg/LayoutSVGInlineText.cpp
|
| @@ -30,8 +30,13 @@
|
| #include "core/editing/VisiblePosition.h"
|
| #include "core/layout/svg/LayoutSVGText.h"
|
| #include "core/layout/svg/SVGLayoutSupport.h"
|
| -#include "core/layout/svg/SVGTextMetricsBuilder.h"
|
| #include "core/layout/svg/line/SVGInlineTextBox.h"
|
| +#include "platform/fonts/CharacterRange.h"
|
| +#include "platform/text/BidiCharacterRun.h"
|
| +#include "platform/text/BidiResolver.h"
|
| +#include "platform/text/TextDirection.h"
|
| +#include "platform/text/TextRun.h"
|
| +#include "platform/text/TextRunIterator.h"
|
|
|
| namespace blink {
|
|
|
| @@ -200,14 +205,236 @@ PositionWithAffinity LayoutSVGInlineText::positionForPoint(const LayoutPoint& po
|
| return createPositionWithAffinity(offset + closestDistanceBox->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : TextAffinity::Downstream);
|
| }
|
|
|
| -void LayoutSVGInlineText::updateScaledFont()
|
| +namespace {
|
| +
|
| +inline bool characterStartsSurrogatePair(const TextRun& run, unsigned index)
|
| {
|
| - computeNewScaledFontForStyle(this, m_scalingFactor, m_scaledFont);
|
| + if (!U16_IS_LEAD(run[index]))
|
| + return false;
|
| + if (index + 1 >= static_cast<unsigned>(run.charactersLength()))
|
| + return false;
|
| + return U16_IS_TRAIL(run[index + 1]);
|
| +}
|
| +
|
| +class SVGTextMetricsCalculator {
|
| +public:
|
| + SVGTextMetricsCalculator(LayoutSVGInlineText&);
|
| + ~SVGTextMetricsCalculator();
|
| +
|
| + bool advancePosition();
|
| + unsigned currentPosition() const { return m_currentPosition; }
|
| +
|
| + 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 characterStartsSurrogatePair(m_run, m_currentPosition);
|
| + }
|
| + bool currentCharacterIsWhiteSpace() const
|
| + {
|
| + return m_run[m_currentPosition] == ' ';
|
| + }
|
| + unsigned characterCount() const
|
| + {
|
| + return static_cast<unsigned>(m_run.charactersLength());
|
| + }
|
| +
|
| +private:
|
| + void setupBidiRuns();
|
| +
|
| + static TextRun constructTextRun(LayoutSVGInlineText&, unsigned position, unsigned length, TextDirection);
|
| +
|
| + // 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;
|
| +
|
| + LayoutSVGInlineText& m_text;
|
| + float m_fontScalingFactor;
|
| + float m_cachedFontHeight;
|
| + TextRun m_run;
|
| +
|
| + BidiCharacterRun* m_bidiRun;
|
| + BidiResolver<TextRunIterator, BidiCharacterRun> m_bidiResolver;
|
| +
|
| + // Ranges for the current bidi run if present, or the entire run otherwise.
|
| + Vector<CharacterRange> m_subrunRanges;
|
| +};
|
| +
|
| +TextRun SVGTextMetricsCalculator::constructTextRun(LayoutSVGInlineText& text, unsigned position, unsigned length, TextDirection textDirection)
|
| +{
|
| + const ComputedStyle& style = text.styleRef();
|
| +
|
| + TextRun run(static_cast<const LChar*>(nullptr) // characters, will be set below if non-zero.
|
| + , 0 // length, will be set below if non-zero.
|
| + , 0 // xPos, only relevant with allowTabs=true
|
| + , 0 // padding, only relevant for justified text, not relevant for SVG
|
| + , TextRun::AllowTrailingExpansion
|
| + , textDirection
|
| + , isOverride(style.unicodeBidi()) /* directionalOverride */);
|
| +
|
| + if (length) {
|
| + if (text.is8Bit())
|
| + run.setText(text.characters8() + position, length);
|
| + else
|
| + run.setText(text.characters16() + position, length);
|
| + }
|
| +
|
| + // We handle letter & word spacing ourselves.
|
| + run.disableSpacing();
|
| +
|
| + // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring.
|
| + run.setCharactersLength(text.textLength() - position);
|
| + ASSERT(run.charactersLength() >= run.length());
|
| + return run;
|
| }
|
|
|
| +SVGTextMetricsCalculator::SVGTextMetricsCalculator(LayoutSVGInlineText& text)
|
| + : m_currentPosition(0)
|
| + , m_text(text)
|
| + , m_fontScalingFactor(m_text.scalingFactor())
|
| + , m_cachedFontHeight(m_text.scaledFont().getFontMetrics().floatHeight() / m_fontScalingFactor)
|
| + , m_run(constructTextRun(m_text, 0, m_text.textLength(), m_text.styleRef().direction()))
|
| + , m_bidiRun(nullptr)
|
| +{
|
| + setupBidiRuns();
|
| +}
|
| +
|
| +SVGTextMetricsCalculator::~SVGTextMetricsCalculator()
|
| +{
|
| + m_bidiResolver.runs().deleteRuns();
|
| +}
|
| +
|
| +void SVGTextMetricsCalculator::setupBidiRuns()
|
| +{
|
| + BidiRunList<BidiCharacterRun>& bidiRuns = m_bidiResolver.runs();
|
| + bool bidiOverride = isOverride(m_text.styleRef().unicodeBidi());
|
| + BidiStatus status(LTR, bidiOverride);
|
| + if (m_run.is8Bit() || bidiOverride) {
|
| + WTF::Unicode::CharDirection direction = WTF::Unicode::LeftToRight;
|
| + // If BiDi override is in effect, use the specified direction.
|
| + if (bidiOverride && !m_text.styleRef().isLeftToRightDirection())
|
| + direction = WTF::Unicode::RightToLeft;
|
| + bidiRuns.addRun(new BidiCharacterRun(0, m_run.charactersLength(), status.context.get(), direction));
|
| + } else {
|
| + status.last = status.lastStrong = WTF::Unicode::OtherNeutral;
|
| + m_bidiResolver.setStatus(status);
|
| + m_bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&m_run, 0));
|
| + const bool hardLineBreak = false;
|
| + const bool reorderRuns = false;
|
| + m_bidiResolver.createBidiRunsForLine(TextRunIterator(&m_run, m_run.length()), NoVisualOverride, hardLineBreak, reorderRuns);
|
| + }
|
| + m_bidiRun = bidiRuns.firstRun();
|
| +}
|
| +
|
| +bool SVGTextMetricsCalculator::advancePosition()
|
| +{
|
| + m_currentPosition += currentCharacterStartsSurrogatePair() ? 2 : 1;
|
| + return m_currentPosition < characterCount();
|
| +}
|
| +
|
| +// 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 single position. This will incorrectly split combining diacritics.
|
| +// See: https://crbug.com/473476.
|
| +static void synthesizeGraphemeWidths(const TextRun& run, Vector<CharacterRange>& ranges)
|
| +{
|
| + unsigned distributeCount = 0;
|
| + for (int rangeIndex = static_cast<int>(ranges.size()) - 1; rangeIndex >= 0; --rangeIndex) {
|
| + CharacterRange& currentRange = ranges[rangeIndex];
|
| + if (currentRange.width() == 0) {
|
| + distributeCount++;
|
| + } else if (distributeCount != 0) {
|
| + // Only count surrogate pairs as a single character.
|
| + bool surrogatePair = characterStartsSurrogatePair(run, rangeIndex);
|
| + if (!surrogatePair)
|
| + distributeCount++;
|
| +
|
| + float newWidth = currentRange.width() / distributeCount;
|
| + currentRange.end = currentRange.start + newWidth;
|
| + float lastEndPosition = currentRange.end;
|
| + for (unsigned distribute = 1; distribute < distributeCount; distribute++) {
|
| + // This surrogate pair check will skip processing of the second
|
| + // character forming the surrogate pair.
|
| + unsigned distributeIndex = rangeIndex + distribute + (surrogatePair ? 1 : 0);
|
| + ranges[distributeIndex].start = lastEndPosition;
|
| + ranges[distributeIndex].end = lastEndPosition + newWidth;
|
| + lastEndPosition = ranges[distributeIndex].end;
|
| + }
|
| +
|
| + distributeCount = 0;
|
| + }
|
| + }
|
| +}
|
| +
|
| +unsigned SVGTextMetricsCalculator::updateSubrunRangesForCurrentPosition()
|
| +{
|
| + ASSERT(m_bidiRun);
|
| + if (m_currentPosition >= static_cast<unsigned>(m_bidiRun->stop())) {
|
| + m_bidiRun = m_bidiRun->next();
|
| + // Ensure new subrange ranges are computed below.
|
| + m_subrunRanges.clear();
|
| + }
|
| + ASSERT(m_bidiRun);
|
| + ASSERT(static_cast<int>(m_currentPosition) < m_bidiRun->stop());
|
| +
|
| + unsigned positionInRun = m_currentPosition - m_bidiRun->start();
|
| + if (positionInRun >= m_subrunRanges.size()) {
|
| + TextRun subRun = constructTextRun(m_text, m_bidiRun->start(),
|
| + m_bidiRun->stop() - m_bidiRun->start(), m_bidiRun->direction());
|
| + m_subrunRanges = m_text.scaledFont().individualCharacterRanges(subRun);
|
| + synthesizeGraphemeWidths(subRun, m_subrunRanges);
|
| + }
|
| +
|
| + ASSERT(m_subrunRanges.size() && positionInRun < m_subrunRanges.size());
|
| + return positionInRun;
|
| +}
|
| +
|
| +SVGTextMetrics SVGTextMetricsCalculator::currentCharacterMetrics()
|
| +{
|
| + unsigned currentSubrunPosition = updateSubrunRangesForCurrentPosition();
|
| + unsigned length = currentCharacterStartsSurrogatePair() ? 2 : 1;
|
| + float width = m_subrunRanges[currentSubrunPosition].width();
|
| + return SVGTextMetrics(length, width / m_fontScalingFactor, m_cachedFontHeight);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| void LayoutSVGInlineText::updateMetricsList(bool& lastCharacterWasWhiteSpace)
|
| {
|
| - SVGTextMetricsBuilder::updateTextMetrics(*this, lastCharacterWasWhiteSpace);
|
| + m_metrics.clear();
|
| +
|
| + if (!textLength())
|
| + return;
|
| +
|
| + // 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(*this);
|
| + bool preserveWhiteSpace = styleRef().whiteSpace() == PRE;
|
| + do {
|
| + bool currentCharacterIsWhiteSpace = calculator.currentCharacterIsWhiteSpace();
|
| + if (!preserveWhiteSpace && lastCharacterWasWhiteSpace && currentCharacterIsWhiteSpace) {
|
| + m_metrics.append(SVGTextMetrics(SVGTextMetrics::SkippedSpaceMetrics));
|
| + ASSERT(calculator.currentCharacterMetrics().length() == 1);
|
| + continue;
|
| + }
|
| +
|
| + m_metrics.append(calculator.currentCharacterMetrics());
|
| +
|
| + lastCharacterWasWhiteSpace = currentCharacterIsWhiteSpace;
|
| + } while (calculator.advancePosition());
|
| +}
|
| +
|
| +void LayoutSVGInlineText::updateScaledFont()
|
| +{
|
| + computeNewScaledFontForStyle(this, m_scalingFactor, m_scaledFont);
|
| }
|
|
|
| void LayoutSVGInlineText::computeNewScaledFontForStyle(LayoutObject* layoutObject, float& scalingFactor, Font& scaledFont)
|
|
|