TextRun SVGTextMetrics::constructTextRun(LineLayoutSVGInlineText textLayoutItem, unsigned position, unsigned length, TextDirection textDirection)
{
    const ComputedStyle& style = textLayoutItem.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 (textLayoutItem.is8Bit())
            run.setText(textLayoutItem.characters8() + position, length);
        else
            run.setText(textLayoutItem.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(textLayoutItem.textLength() - position);
    ASSERT(run.charactersLength() >= run.length());
    return run;
}
Пример #2
0
bool SVGInlineTextBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit, LayoutUnit)
{
    // FIXME: integrate with InlineTextBox::nodeAtPoint better.
    ASSERT(!isLineBreak());

    PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, result.hitTestRequest(), lineLayoutItem().style()->pointerEvents());
    bool isVisible = lineLayoutItem().style()->visibility() == VISIBLE;
    if (isVisible || !hitRules.requireVisible) {
        if (hitRules.canHitBoundingBox
                || (hitRules.canHitStroke && (lineLayoutItem().style()->svgStyle().hasStroke() || !hitRules.requireStroke))
                || (hitRules.canHitFill && (lineLayoutItem().style()->svgStyle().hasFill() || !hitRules.requireFill))) {
            LayoutPoint boxOrigin(x(), y());
            boxOrigin.moveBy(accumulatedOffset);
            LayoutRect rect(boxOrigin, size());
            if (locationInContainer.intersects(rect)) {
                LineLayoutSVGInlineText lineLayoutItem = LineLayoutSVGInlineText(this->lineLayoutItem());
                ASSERT(lineLayoutItem.scalingFactor());
                float baseline = lineLayoutItem.scaledFont().fontMetrics().floatAscent() / lineLayoutItem.scalingFactor();

                FloatPoint floatLocation = FloatPoint(locationInContainer.point());
                for (const SVGTextFragment& fragment : m_textFragments) {
                    FloatQuad fragmentQuad = fragment.boundingQuad(baseline);
                    if (fragmentQuad.containsPoint(floatLocation)) {
                        lineLayoutItem.updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset));
                        if (!result.addNodeToListBasedTestResult(lineLayoutItem.node(), locationInContainer, rect))
                            return true;
                    }
                }
            }
        }
    }
    return false;
}
Пример #3
0
static inline FloatRect calculateFragmentBoundaries(LineLayoutSVGInlineText textLineLayout, const SVGTextFragment& fragment)
{
    float scalingFactor = textLineLayout.scalingFactor();
    ASSERT(scalingFactor);
    float baseline = textLineLayout.scaledFont().fontMetrics().floatAscent() / scalingFactor;
    return fragment.boundingBox(baseline);
}
Пример #4
0
int SVGInlineTextBox::offsetForPositionInFragment(
    const SVGTextFragment& fragment,
    LayoutUnit position,
    bool includePartialGlyphs) const {
  LineLayoutSVGInlineText lineLayoutItem =
      LineLayoutSVGInlineText(this->getLineLayoutItem());

  float scalingFactor = lineLayoutItem.scalingFactor();
  ASSERT(scalingFactor);

  const ComputedStyle& style = lineLayoutItem.styleRef();

  TextRun textRun = constructTextRun(style, fragment);

  // Eventually handle lengthAdjust="spacingAndGlyphs".
  // FIXME: Handle vertical text.
  if (fragment.isTransformed()) {
    AffineTransform fragmentTransform = fragment.buildFragmentTransform();
    textRun.setHorizontalGlyphStretch(
        clampTo<float>(fragmentTransform.xScale()));
  }

  return fragment.characterOffset - start() +
         lineLayoutItem.scaledFont().offsetForPosition(
             textRun, position * scalingFactor, includePartialGlyphs);
}
Пример #5
0
FloatRect SVGInlineTextBox::selectionRectForTextFragment(
    const SVGTextFragment& fragment,
    int startPosition,
    int endPosition,
    const ComputedStyle& style) const {
  ASSERT(startPosition < endPosition);

  LineLayoutSVGInlineText lineLayoutItem =
      LineLayoutSVGInlineText(this->getLineLayoutItem());

  float scalingFactor = lineLayoutItem.scalingFactor();
  ASSERT(scalingFactor);

  const Font& scaledFont = lineLayoutItem.scaledFont();
  const SimpleFontData* fontData = scaledFont.primaryFont();
  DCHECK(fontData);
  if (!fontData)
    return FloatRect();

  const FontMetrics& scaledFontMetrics = fontData->getFontMetrics();
  FloatPoint textOrigin(fragment.x, fragment.y);
  if (scalingFactor != 1)
    textOrigin.scale(scalingFactor, scalingFactor);

  textOrigin.move(0, -scaledFontMetrics.floatAscent());

  FloatRect selectionRect = scaledFont.selectionRectForText(
      constructTextRun(style, fragment), textOrigin,
      fragment.height * scalingFactor, startPosition, endPosition);
  if (scalingFactor == 1)
    return selectionRect;

  selectionRect.scale(1 / scalingFactor);
  return selectionRect;
}
Пример #6
0
// Execute a query in "logical" order starting at |queryRoot|. This means
// walking the lines boxes for each layout object in layout tree (pre)order.
static void logicalQuery(LayoutObject* queryRoot, QueryData* queryData, ProcessTextFragmentCallback fragmentCallback)
{
    if (!queryRoot)
        return;

    // Walk the layout tree in pre-order, starting at the specified root, and
    // run the query for each text node.
    Vector<SVGInlineTextBox*> textBoxes;
    for (LayoutObject* layoutObject = queryRoot->slowFirstChild(); layoutObject; layoutObject = layoutObject->nextInPreOrder(queryRoot)) {
        if (!layoutObject->isSVGInlineText())
            continue;

        LineLayoutSVGInlineText textLineLayout = LineLayoutSVGInlineText(toLayoutSVGInlineText(layoutObject));
        ASSERT(textLineLayout.style());

        // TODO(fs): Allow filtering the search earlier, since we should be
        // able to trivially reject (prune) at least some of the queries.
        collectTextBoxesInLogicalOrder(textLineLayout, textBoxes);

        for (const SVGInlineTextBox* textBox : textBoxes) {
            if (queryTextBox(queryData, textBox, fragmentCallback))
                return;
            queryData->currentOffset += textBox->len();
        }
    }
}
Пример #7
0
static inline FloatRect calculateFragmentBoundaries(LineLayoutSVGInlineText textLineLayout, const SVGTextFragment& fragment)
{
    float scalingFactor = textLineLayout.scalingFactor();
    ASSERT(scalingFactor);
    float baseline = textLineLayout.scaledFont().fontMetrics().floatAscent() / scalingFactor;

    AffineTransform fragmentTransform;
    FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height);
    fragment.buildFragmentTransform(fragmentTransform);
    return fragmentTransform.mapRect(fragmentRect);
}
SVGTextMetrics::SVGTextMetrics(LineLayoutSVGInlineText textLayoutItem, unsigned length, float width)
{
    ASSERT(textLayoutItem);

    float scalingFactor = textLayoutItem.scalingFactor();
    ASSERT(scalingFactor);

    m_width = width / scalingFactor;
    m_height = textLayoutItem.scaledFont().fontMetrics().floatHeight() / scalingFactor;

    m_length = length;
}
Пример #9
0
LayoutRect SVGInlineTextBox::calculateBoundaries() const
{
    LineLayoutSVGInlineText lineLayoutItem = LineLayoutSVGInlineText(this->lineLayoutItem());
    float scalingFactor = lineLayoutItem.scalingFactor();
    ASSERT(scalingFactor);
    LayoutUnit baseline = lineLayoutItem.scaledFont().fontMetrics().floatAscent() / scalingFactor;

    LayoutRect textBoundingRect;
    for (const SVGTextFragment& fragment : m_textFragments)
        textBoundingRect.unite(LayoutRect(fragment.overflowBoundingBox(baseline)));

    return textBoundingRect;
}
Пример #10
0
SVGTextMetrics::SVGTextMetrics(LineLayoutSVGInlineText textLayoutItem, const TextRun& run)
{
    ASSERT(textLayoutItem);

    float scalingFactor = textLayoutItem.scalingFactor();
    ASSERT(scalingFactor);

    const Font& scaledFont = textLayoutItem.scaledFont();

    // Calculate width/height using the scaled font, divide this result by the scalingFactor afterwards.
    m_width = scaledFont.width(run) / scalingFactor;
    m_height = scaledFont.fontMetrics().floatHeight() / scalingFactor;

    ASSERT(run.length() >= 0);
    m_length = static_cast<unsigned>(run.length());
}
Пример #11
0
static void collectTextBoxesInLogicalOrder(LineLayoutSVGInlineText textLineLayout, Vector<SVGInlineTextBox*>& textBoxes)
{
    textBoxes.shrink(0);
    for (InlineTextBox* textBox = textLineLayout.firstTextBox(); textBox; textBox = textBox->nextTextBox())
        textBoxes.append(toSVGInlineTextBox(textBox));
    std::sort(textBoxes.begin(), textBoxes.end(), InlineTextBox::compareByStart);
}
Пример #12
0
LayoutRect SVGInlineTextBox::calculateBoundaries() const {
  LineLayoutSVGInlineText lineLayoutItem =
      LineLayoutSVGInlineText(this->getLineLayoutItem());
  const SimpleFontData* fontData = lineLayoutItem.scaledFont().primaryFont();
  DCHECK(fontData);
  if (!fontData)
    return LayoutRect();

  float scalingFactor = lineLayoutItem.scalingFactor();
  ASSERT(scalingFactor);
  LayoutUnit baseline(fontData->getFontMetrics().floatAscent() / scalingFactor);

  LayoutRect textBoundingRect;
  for (const SVGTextFragment& fragment : m_textFragments)
    textBoundingRect.unite(LayoutRect(fragment.overflowBoundingBox(baseline)));

  return textBoundingRect;
}
Пример #13
0
void SVGTextLayoutEngine::layoutInlineTextBox(SVGInlineTextBox* textBox)
{
    ASSERT(textBox);

    LineLayoutSVGInlineText textLineLayout = LineLayoutSVGInlineText(textBox->lineLayoutItem());
    ASSERT(textLineLayout.parent());
    ASSERT(textLineLayout.parent().node());
    ASSERT(textLineLayout.parent().node()->isSVGElement());

    const ComputedStyle& style = textLineLayout.styleRef();

    textBox->clearTextFragments();
    m_isVerticalText = !style.isHorizontalWritingMode();
    layoutTextOnLineOrPath(textBox, textLineLayout, style);

    if (m_inPathLayout)
        return;

    m_lineLayoutBoxes.append(textBox);
}
void SVGTextMetricsCalculator::setupBidiRuns()
{
    const ComputedStyle& style = m_text.styleRef();
    m_textDirection = style.direction();
    if (isOverride(style.unicodeBidi()))
        return;

    BidiStatus status(LTR, false);
    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);
    BidiRunList<BidiCharacterRun>& bidiRuns = m_bidiResolver.runs();
    m_bidiRun = bidiRuns.firstRun();
}
Пример #15
0
bool SVGInlineTextBox::nodeAtPoint(HitTestResult& result,
                                   const HitTestLocation& locationInContainer,
                                   const LayoutPoint& accumulatedOffset,
                                   LayoutUnit,
                                   LayoutUnit) {
  // FIXME: integrate with InlineTextBox::nodeAtPoint better.
  ASSERT(!isLineBreak());

  PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING,
                                 result.hitTestRequest(),
                                 getLineLayoutItem().style()->pointerEvents());
  bool isVisible =
      getLineLayoutItem().style()->visibility() == EVisibility::Visible;
  if (isVisible || !hitRules.requireVisible) {
    if (hitRules.canHitBoundingBox ||
        (hitRules.canHitStroke &&
         (getLineLayoutItem().style()->svgStyle().hasStroke() ||
          !hitRules.requireStroke)) ||
        (hitRules.canHitFill &&
         (getLineLayoutItem().style()->svgStyle().hasFill() ||
          !hitRules.requireFill))) {
      LayoutRect rect(topLeft(), LayoutSize(logicalWidth(), logicalHeight()));
      rect.moveBy(accumulatedOffset);
      if (locationInContainer.intersects(rect)) {
        LineLayoutSVGInlineText lineLayoutItem =
            LineLayoutSVGInlineText(this->getLineLayoutItem());
        const SimpleFontData* fontData =
            lineLayoutItem.scaledFont().primaryFont();
        DCHECK(fontData);
        if (!fontData)
          return false;

        DCHECK(lineLayoutItem.scalingFactor());
        float baseline = fontData->getFontMetrics().floatAscent() /
                         lineLayoutItem.scalingFactor();
        FloatPoint floatLocation = FloatPoint(locationInContainer.point());
        for (const SVGTextFragment& fragment : m_textFragments) {
          FloatQuad fragmentQuad = fragment.boundingQuad(baseline);
          if (fragmentQuad.containsPoint(floatLocation)) {
            lineLayoutItem.updateHitTestResult(
                result,
                locationInContainer.point() - toLayoutSize(accumulatedOffset));
            if (result.addNodeToListBasedTestResult(lineLayoutItem.node(),
                                                    locationInContainer,
                                                    rect) == StopHitTesting)
              return true;
          }
        }
      }
    }
  }
  return false;
}
Пример #16
0
void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, LineLayoutSVGInlineText textLineLayout, const ComputedStyle& style)
{
    if (m_inPathLayout && !m_textPathCalculator)
        return;

    // Find the start of the current text box in the metrics list.
    m_visualMetricsIterator.advanceToTextStart(&textLineLayout, textBox->start());

    const Font& font = style.font();

    SVGTextLayoutEngineSpacing spacingLayout(font, style.effectiveZoom());
    SVGTextLayoutEngineBaseline baselineLayout(font, style.effectiveZoom());

    bool didStartTextFragment = false;
    bool applySpacingToNextCharacter = false;

    float lastAngle = 0;
    float baselineShift = baselineLayout.calculateBaselineShift(style);
    baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, textLineLayout);

    // Main layout algorithm.
    const unsigned boxEndOffset = textBox->start() + textBox->len();
    while (!m_visualMetricsIterator.isAtEnd() && m_visualMetricsIterator.characterOffset() < boxEndOffset) {
        const SVGTextMetrics& visualMetrics = m_visualMetricsIterator.metrics();
        if (visualMetrics.isEmpty()) {
            m_visualMetricsIterator.next();
            continue;
        }

        SVGTextLayoutAttributes* logicalAttributes = nullptr;
        if (!currentLogicalCharacterAttributes(logicalAttributes))
            break;

        ASSERT(logicalAttributes);
        SVGTextMetrics logicalMetrics(SVGTextMetrics::SkippedSpaceMetrics);
        if (!currentLogicalCharacterMetrics(logicalAttributes, logicalMetrics))
            break;

        SVGCharacterDataMap& characterDataMap = logicalAttributes->characterDataMap();
        SVGCharacterData data;
        SVGCharacterDataMap::iterator it = characterDataMap.find(m_logicalCharacterOffset + 1);
        if (it != characterDataMap.end())
            data = it->value;

        float x = data.x;
        float y = data.y;

        // When we've advanced to the box start offset, determine using the original x/y values,
        // whether this character starts a new text chunk, before doing any further processing.
        if (m_visualMetricsIterator.characterOffset() == textBox->start())
            textBox->setStartsNewTextChunk(logicalAttributes->context()->characterStartsNewTextChunk(m_logicalCharacterOffset));

        float angle = SVGTextLayoutAttributes::isEmptyValue(data.rotate) ? 0 : data.rotate;

        // Calculate glyph orientation angle.
        // Font::width() calculates the resolved FontOrientation for each character,
        // but is not exposed today to avoid the API complexity.
        UChar32 currentCharacter = textLineLayout.codepointAt(m_visualMetricsIterator.characterOffset());
        FontOrientation fontOrientation = font.fontDescription().orientation();
        fontOrientation = adjustOrientationForCharacterInMixedVertical(fontOrientation, currentCharacter);

        // Calculate glyph advance.
        // Shaping engine takes care of x/y orientation shifts for different fontOrientation values.
        float glyphAdvance = visualMetrics.advance(fontOrientation);

        // Assign current text position to x/y values, if needed.
        updateCharacterPositionIfNeeded(x, y);

        // Apply dx/dy value adjustments to current text position, if needed.
        updateRelativePositionAdjustmentsIfNeeded(data.dx, data.dy);

        // Calculate CSS 'letter-spacing' and 'word-spacing' for next character, if needed.
        float spacing = spacingLayout.calculateCSSSpacing(currentCharacter);

        float textPathOffset = 0;
        float textPathShiftX = 0;
        float textPathShiftY = 0;
        if (m_inPathLayout) {
            float scaledGlyphAdvance = glyphAdvance * m_textPathScaling;
            if (m_isVerticalText) {
                // If there's an absolute y position available, it marks the beginning of a new position along the path.
                if (!SVGTextLayoutAttributes::isEmptyValue(y))
                    m_textPathCurrentOffset = y + m_textPathStartOffset;

                m_textPathCurrentOffset += m_dy;
                m_dy = 0;

                // Apply dx/dy correction and setup translations that move to the glyph midpoint.
                textPathShiftX += m_dx + baselineShift;
                textPathShiftY -= scaledGlyphAdvance / 2;
            } else {
                // If there's an absolute x position available, it marks the beginning of a new position along the path.
                if (!SVGTextLayoutAttributes::isEmptyValue(x))
                    m_textPathCurrentOffset = x + m_textPathStartOffset;

                m_textPathCurrentOffset += m_dx;
                m_dx = 0;

                // Apply dx/dy correction and setup translations that move to the glyph midpoint.
                textPathShiftX -= scaledGlyphAdvance / 2;
                textPathShiftY += m_dy - baselineShift;
            }

            // Calculate current offset along path.
            textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2;

            // Move to next character.
            m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling;

            // Skip character, if we're before the path.
            if (textPathOffset < 0) {
                advanceToNextLogicalCharacter(logicalMetrics);
                m_visualMetricsIterator.next();
                continue;
            }

            // Stop processing, if the next character lies behind the path.
            if (textPathOffset > m_textPathLength)
                break;

            FloatPoint point;
            bool ok = m_textPathCalculator->pointAndNormalAtLength(textPathOffset, point, angle);
            ASSERT_UNUSED(ok, ok);
            x = point.x();
            y = point.y();

            // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle!
            if (m_isVerticalText)
                angle -= 90;
        } else {
            // Apply all previously calculated shift values.
            if (m_isVerticalText)
                x += baselineShift;
            else
                y -= baselineShift;

            x += m_dx;
            y += m_dy;
        }

        // Determine whether we have to start a new fragment.
        bool shouldStartNewFragment = m_dx || m_dy || m_isVerticalText || m_inPathLayout || angle || angle != lastAngle
            || applySpacingToNextCharacter || m_textLengthSpacingInEffect;

        // If we already started a fragment, close it now.
        if (didStartTextFragment && shouldStartNewFragment) {
            applySpacingToNextCharacter = false;
            recordTextFragment(textBox);
        }

        // Eventually start a new fragment, if not yet done.
        if (!didStartTextFragment || shouldStartNewFragment) {
            ASSERT(!m_currentTextFragment.characterOffset);
            ASSERT(!m_currentTextFragment.length);

            didStartTextFragment = true;
            m_currentTextFragment.characterOffset = m_visualMetricsIterator.characterOffset();
            m_currentTextFragment.metricsListOffset = m_visualMetricsIterator.metricsListOffset();
            m_currentTextFragment.x = x;
            m_currentTextFragment.y = y;

            // Build fragment transformation.
            if (angle)
                m_currentTextFragment.transform.rotate(angle);

            if (textPathShiftX || textPathShiftY)
                m_currentTextFragment.transform.translate(textPathShiftX, textPathShiftY);

            // In vertical text, always rotate by 90 degrees regardless of fontOrientation.
            // Shaping engine takes care of the necessary orientation.
            if (m_isVerticalText)
                m_currentTextFragment.transform.rotate(90);

            m_currentTextFragment.isTextOnPath = m_inPathLayout && m_textPathScaling != 1;
            if (m_currentTextFragment.isTextOnPath) {
                if (m_isVerticalText)
                    m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(1, m_textPathScaling);
                else
                    m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(m_textPathScaling, 1);
            }
        }

        // Update current text position, after processing of the current character finished.
        if (m_inPathLayout) {
            updateCurrentTextPosition(x, y, glyphAdvance);
        } else {
            // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed.
            if (spacing)
                applySpacingToNextCharacter = true;

            float xNew = x - m_dx;
            float yNew = y - m_dy;

            if (m_isVerticalText)
                xNew -= baselineShift;
            else
                yNew += baselineShift;

            updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing);
        }

        advanceToNextLogicalCharacter(logicalMetrics);
        m_visualMetricsIterator.next();
        lastAngle = angle;
    }

    if (!didStartTextFragment)
        return;

    // Close last open fragment, if needed.
    recordTextFragment(textBox);
}
Пример #17
0
void SVGTextChunkBuilder::handleTextChunk(BoxListConstIterator boxStart,
                                          BoxListConstIterator boxEnd) {
  ASSERT(*boxStart);

  const LineLayoutSVGInlineText textLineLayout =
      LineLayoutSVGInlineText((*boxStart)->getLineLayoutItem());
  const ComputedStyle& style = textLineLayout.styleRef();

  // Handle 'lengthAdjust' property.
  float desiredTextLength = 0;
  SVGLengthAdjustType lengthAdjust = SVGLengthAdjustUnknown;
  if (SVGTextContentElement* textContentElement =
          SVGTextContentElement::elementFromLineLayoutItem(
              textLineLayout.parent())) {
    lengthAdjust =
        textContentElement->lengthAdjust()->currentValue()->enumValue();

    SVGLengthContext lengthContext(textContentElement);
    if (textContentElement->textLengthIsSpecifiedByUser())
      desiredTextLength =
          textContentElement->textLength()->currentValue()->value(
              lengthContext);
    else
      desiredTextLength = 0;
  }

  bool processTextLength = desiredTextLength > 0;
  bool processTextAnchor = needsTextAnchorAdjustment(style);
  if (!processTextAnchor && !processTextLength)
    return;

  bool isVerticalText = !style.isHorizontalWritingMode();

  // Calculate absolute length of whole text chunk (starting from text box
  // 'start', spanning 'length' text boxes).
  ChunkLengthAccumulator lengthAccumulator(isVerticalText);
  lengthAccumulator.processRange(boxStart, boxEnd);

  if (processTextLength) {
    float chunkLength = lengthAccumulator.length();
    if (lengthAdjust == SVGLengthAdjustSpacing) {
      float textLengthShift =
          (desiredTextLength - chunkLength) / lengthAccumulator.numCharacters();
      unsigned atCharacter = 0;
      for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) {
        Vector<SVGTextFragment>& fragments = (*boxIter)->textFragments();
        if (fragments.isEmpty())
          continue;
        processTextLengthSpacingCorrection(isVerticalText, textLengthShift,
                                           fragments, atCharacter);
      }

      // Fragments have been adjusted, we have to recalculate the chunk
      // length, to be able to apply the text-anchor shift.
      if (processTextAnchor) {
        lengthAccumulator.reset();
        lengthAccumulator.processRange(boxStart, boxEnd);
      }
    } else {
      ASSERT(lengthAdjust == SVGLengthAdjustSpacingAndGlyphs);
      float textLengthScale = desiredTextLength / chunkLength;
      float textLengthBias = 0;

      bool foundFirstFragment = false;
      for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) {
        SVGInlineTextBox* textBox = *boxIter;
        Vector<SVGTextFragment>& fragments = textBox->textFragments();
        if (fragments.isEmpty())
          continue;

        if (!foundFirstFragment) {
          foundFirstFragment = true;
          textLengthBias =
              computeTextLengthBias(fragments.first(), textLengthScale);
        }

        applyTextLengthScaleAdjustment(textLengthScale, textLengthBias,
                                       fragments);
      }
    }
  }

  if (!processTextAnchor)
    return;

  float textAnchorShift =
      calculateTextAnchorShift(style, lengthAccumulator.length());
  for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) {
    Vector<SVGTextFragment>& fragments = (*boxIter)->textFragments();
    if (fragments.isEmpty())
      continue;
    processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments);
  }
}