void SVGInlineTextBox::paintTextMatchMarker(GraphicsContext* context, const FloatPoint&, DocumentMarker* marker, RenderStyle* style, const Font& font) { // SVG is only interested in the TextMatch markers. if (marker->type() != DocumentMarker::TextMatch) return; RenderSVGInlineText* textRenderer = toRenderSVGInlineText(this->textRenderer()); ASSERT(textRenderer); FloatRect markerRect; AffineTransform fragmentTransform; for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if (!box->isSVGInlineTextBox()) continue; SVGInlineTextBox* textBox = toSVGInlineTextBox(box); int markerStartPosition = max<int>(marker->startOffset() - textBox->start(), 0); int markerEndPosition = min<int>(marker->endOffset() - textBox->start(), textBox->len()); if (markerStartPosition >= markerEndPosition) continue; const Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned textFragmentsSize = fragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { const SVGTextFragment& fragment = fragments.at(i); int fragmentStartPosition = markerStartPosition; int fragmentEndPosition = markerEndPosition; if (!textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) continue; FloatRect fragmentRect = textBox->selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style); fragment.buildFragmentTransform(fragmentTransform); bool fragmentTransformIsIdentity = fragmentTransform.isIdentity(); // Draw the marker highlight. if (renderer()->frame()->editor().markedTextMatchesAreHighlighted()) { Color color = marker->activeMatch() ? RenderTheme::theme().platformActiveTextSearchHighlightColor() : RenderTheme::theme().platformInactiveTextSearchHighlightColor(); GraphicsContextStateSaver stateSaver(*context); if (!fragmentTransformIsIdentity) context->concatCTM(fragmentTransform); context->setFillColor(color); context->fillRect(fragmentRect, color); } if (!fragmentTransformIsIdentity) fragmentRect = fragmentTransform.mapRect(fragmentRect); markerRect.unite(fragmentRect); } } toRenderedDocumentMarker(marker)->setRenderedRect(textRenderer->localToAbsoluteQuad(markerRect).enclosingBoundingBox()); }
void SVGInlineFlowBox::computeTextMatchMarkerRectForRenderer(RenderSVGInlineText* textRenderer) { ASSERT(textRenderer); Node* node = textRenderer->node(); if (!node || !node->inDocument()) return; RenderStyle* style = textRenderer->style(); ASSERT(style); Document* document = textRenderer->document(); Vector<DocumentMarker> markers = document->markers()->markersForNode(textRenderer->node()); Vector<DocumentMarker>::iterator markerEnd = markers.end(); for (Vector<DocumentMarker>::iterator markerIt = markers.begin(); markerIt != markerEnd; ++markerIt) { const DocumentMarker& marker = *markerIt; // SVG is only interessted in the TextMatch marker, for now. if (marker.type != DocumentMarker::TextMatch) continue; FloatRect markerRect; for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { ASSERT(box->isSVGInlineTextBox()); SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(box); int markerStartPosition = max<int>(marker.startOffset - textBox->start(), 0); int markerEndPosition = min<int>(marker.endOffset - textBox->start(), textBox->len()); if (markerStartPosition >= markerEndPosition) continue; int fragmentStartPosition = 0; int fragmentEndPosition = 0; const Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned textFragmentsSize = fragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { const SVGTextFragment& fragment = fragments.at(i); fragmentStartPosition = markerStartPosition; fragmentEndPosition = markerEndPosition; if (!textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) continue; FloatRect fragmentRect = textBox->selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style); if (!fragment.transform.isIdentity()) fragmentRect = fragment.transform.mapRect(fragmentRect); markerRect.unite(fragmentRect); } } document->markers()->setRenderedRectForMarker(node, marker, textRenderer->localToAbsoluteQuad(markerRect).enclosingBoundingBox()); } }
void SVGRootInlineBox::layoutChildBoxes(InlineFlowBox* start, FloatRect* childRect) { for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) { FloatRect boxRect; if (child->isSVGInlineTextBox()) { ASSERT(child->renderer().isSVGInlineText()); SVGInlineTextBox* textBox = toSVGInlineTextBox(child); boxRect = textBox->calculateBoundaries(); textBox->setX(boxRect.x()); textBox->setY(boxRect.y()); textBox->setLogicalWidth(boxRect.width()); textBox->setLogicalHeight(boxRect.height()); } else { // Skip generated content. if (!child->renderer().node()) continue; ASSERT_WITH_SECURITY_IMPLICATION(child->isInlineFlowBox()); SVGInlineFlowBox* flowBox = toSVGInlineFlowBox(child); layoutChildBoxes(flowBox); boxRect = flowBox->calculateBoundaries(); flowBox->setX(boxRect.x()); flowBox->setY(boxRect.y()); flowBox->setLogicalWidth(boxRect.width()); flowBox->setLogicalHeight(boxRect.height()); } if (childRect) childRect->unite(boxRect); } }
void SVGRootInlineBox::layoutChildBoxes(InlineFlowBox* start) { for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) { if (child->isSVGInlineTextBox()) { ASSERT(child->renderer()); ASSERT(child->renderer()->isSVGInlineText()); SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(child); IntRect boxRect = textBox->calculateBoundaries(); textBox->setX(boxRect.x()); textBox->setY(boxRect.y()); textBox->setLogicalWidth(boxRect.width()); textBox->setLogicalHeight(boxRect.height()); } else { ASSERT(child->isInlineFlowBox()); // Skip generated content. if (!child->renderer()->node()) continue; SVGInlineFlowBox* flowBox = static_cast<SVGInlineFlowBox*>(child); layoutChildBoxes(flowBox); IntRect boxRect = flowBox->calculateBoundaries(); flowBox->setX(boxRect.x()); flowBox->setY(boxRect.y()); flowBox->setLogicalWidth(boxRect.width()); flowBox->setLogicalHeight(boxRect.height()); } } }
void SVGTextLayoutEngine::finalizeTransformMatrices(Vector<SVGInlineTextBox*>& boxes) { unsigned boxCount = boxes.size(); if (!boxCount) return; for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { SVGInlineTextBox* textBox = boxes.at(boxPosition); AffineTransform textBoxTransformation = m_chunkLayoutBuilder.transformationForTextBox(textBox); if (textBoxTransformation.isIdentity()) continue; Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned fragmentCount = fragments.size(); for (unsigned i = 0; i < fragmentCount; ++i) { ASSERT(fragments[i].lengthAdjustTransform.isIdentity()); fragments[i].lengthAdjustTransform = textBoxTransformation; } } boxes.clear(); }
static inline void dumpTextBoxes(Vector<SVGInlineTextBox*>& boxes) { unsigned boxCount = boxes.size(); fprintf(stderr, "Dumping all text fragments in text sub tree, %i boxes\n", boxCount); for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { SVGInlineTextBox* textBox = boxes.at(boxPosition); Vector<SVGTextFragment>& fragments = textBox->textFragments(); fprintf(stderr, "-> Box %i: Dumping text fragments for SVGInlineTextBox, textBox=%p, textRenderer=%p\n", boxPosition, textBox, textBox->textRenderer()); fprintf(stderr, " textBox properties, start=%i, len=%i, box direction=%i\n", textBox->start(), textBox->len(), textBox->direction()); fprintf(stderr, " textRenderer properties, textLength=%i\n", textBox->textRenderer()->textLength()); const UChar* characters = textBox->textRenderer()->characters(); unsigned fragmentCount = fragments.size(); for (unsigned i = 0; i < fragmentCount; ++i) { SVGTextFragment& fragment = fragments.at(i); String fragmentString(characters + fragment.characterOffset, fragment.length); fprintf(stderr, " -> Fragment %i, x=%lf, y=%lf, width=%lf, height=%lf, characterOffset=%i, length=%i, characters='%s'\n" , i, fragment.x, fragment.y, fragment.width, fragment.height, fragment.characterOffset, fragment.length, fragmentString.utf8().data()); } } }
void SVGTextChunk::calculateLength(float& length, unsigned& characters) const { SVGTextFragment* lastFragment = 0; unsigned boxCount = m_boxes.size(); for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { SVGInlineTextBox* textBox = m_boxes.at(boxPosition); Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned size = fragments.size(); if (!size) continue; for (unsigned i = 0; i < size; ++i) { SVGTextFragment& fragment = fragments.at(i); characters += fragment.length; if (m_chunkStyle & VerticalText) length += fragment.height; else length += fragment.width; if (!lastFragment) { lastFragment = &fragment; continue; } // Resepect gap between chunks. if (m_chunkStyle & VerticalText) length += fragment.y - (lastFragment->y + lastFragment->height); else length += fragment.x - (lastFragment->x + lastFragment->width); lastFragment = &fragment; } } }
static Vector<SVGInlineTextBox*> findInlineTextBoxInTextChunks(const SVGTextContentElement* element, const Vector<SVGTextChunk>& chunks) { Vector<SVGTextChunk>::const_iterator it = chunks.begin(); const Vector<SVGTextChunk>::const_iterator end = chunks.end(); Vector<SVGInlineTextBox*> boxes; for (; it != end; ++it) { Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = it->boxes.begin(); const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = it->boxes.end(); for (; boxIt != boxEnd; ++boxIt) { SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(boxIt->box); Node* textElement = textBox->textObject()->parent()->element(); ASSERT(textElement); if (textElement == element || textElement->parent() == element) boxes.append(textBox); } } return boxes; }
static inline void reverseInlineBoxRangeAndValueListsIfNeeded(void* userData, Vector<InlineBox*>::iterator first, Vector<InlineBox*>::iterator last) { ASSERT(userData); Vector<SVGTextLayoutAttributes*>& attributes = *reinterpret_cast<Vector<SVGTextLayoutAttributes*>*>(userData); // This is a copy of std::reverse(first, last). It additionally assures that the metrics map within the renderers belonging to the InlineBoxes are reordered as well. while (true) { if (first == last || first == --last) return; if (!(*last)->isSVGInlineTextBox() || !(*first)->isSVGInlineTextBox()) { InlineBox* temp = *first; *first = *last; *last = temp; ++first; continue; } SVGInlineTextBox* firstTextBox = toSVGInlineTextBox(*first); SVGInlineTextBox* lastTextBox = toSVGInlineTextBox(*last); // Reordering is only necessary for BiDi text that is _absolutely_ positioned. if (firstTextBox->len() == 1 && firstTextBox->len() == lastTextBox->len()) { RenderSVGInlineText& firstContext = firstTextBox->renderer(); RenderSVGInlineText& lastContext = lastTextBox->renderer(); SVGTextLayoutAttributes* firstAttributes = 0; SVGTextLayoutAttributes* lastAttributes = 0; findFirstAndLastAttributesInVector(attributes, &firstContext, &lastContext, firstAttributes, lastAttributes); swapItemsInLayoutAttributes(firstAttributes, lastAttributes, firstTextBox->start(), lastTextBox->start()); } InlineBox* temp = *first; *first = *last; *last = temp; ++first; } }
void SVGInlineTextBoxPainter::paintTextMatchMarker(GraphicsContext* context, const LayoutPoint&, DocumentMarker* marker, const ComputedStyle& style, const Font& font) { // SVG is only interested in the TextMatch markers. if (marker->type() != DocumentMarker::TextMatch) return; LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(m_svgInlineTextBox.layoutObject()); AffineTransform fragmentTransform; for (InlineTextBox* box = textLayoutObject.firstTextBox(); box; box = box->nextTextBox()) { if (!box->isSVGInlineTextBox()) continue; SVGInlineTextBox* textBox = toSVGInlineTextBox(box); int markerStartPosition = std::max<int>(marker->startOffset() - textBox->start(), 0); int markerEndPosition = std::min<int>(marker->endOffset() - textBox->start(), textBox->len()); if (markerStartPosition >= markerEndPosition) continue; const Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned textFragmentsSize = fragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { const SVGTextFragment& fragment = fragments.at(i); int fragmentStartPosition = markerStartPosition; int fragmentEndPosition = markerEndPosition; if (!textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) continue; FloatRect fragmentRect = textBox->selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style); fragment.buildFragmentTransform(fragmentTransform); // Draw the marker highlight. if (m_svgInlineTextBox.layoutObject().frame()->editor().markedTextMatchesAreHighlighted()) { Color color = marker->activeMatch() ? LayoutTheme::theme().platformActiveTextSearchHighlightColor() : LayoutTheme::theme().platformInactiveTextSearchHighlightColor(); GraphicsContextStateSaver stateSaver(*context); if (!fragmentTransform.isIdentity()) context->concatCTM(fragmentTransform); context->setFillColor(color); context->fillRect(fragmentRect, color); } } } }
VisiblePosition RenderSVGInlineText::positionForPoint(const IntPoint& point) { SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(firstTextBox()); if (!textBox || textLength() == 0) return createVisiblePosition(0, DOWNSTREAM); SVGRootInlineBox* rootBox = textBox->svgRootInlineBox(); RenderBlock* object = rootBox ? rootBox->block() : 0; if (!object) return createVisiblePosition(0, DOWNSTREAM); int closestOffsetInBox = 0; // FIXME: This approach is wrong. The correct code would first find the // closest SVGInlineTextBox to the point, and *then* ask only that inline box // what the closest text offset to that point is. This code instead walks // through all boxes in order, so when you click "near" a box, you'll actually // end up returning the nearest offset in the last box, even if the // nearest offset to your click is contained in another box. for (SVGInlineTextBox* box = textBox; box; box = static_cast<SVGInlineTextBox*>(box->nextTextBox())) { if (box->svgCharacterHitsPosition(point.x() + object->x(), point.y() + object->y(), closestOffsetInBox)) { // If we're not at the end/start of the box, stop looking for other selected boxes. if (box->direction() == LTR) { if (closestOffsetInBox <= (int) box->end() + 1) break; } else { if (closestOffsetInBox > (int) box->start()) break; } } } return createVisiblePosition(closestOffsetInBox, DOWNSTREAM); }
static float cummulatedWidthOrHeightOfTextChunk(SVGTextChunk& chunk, bool calcWidthOnly) { float length = 0.0f; Vector<SVGChar>::iterator charIt = chunk.start; Vector<SVGInlineBoxCharacterRange>::iterator it = chunk.boxes.begin(); Vector<SVGInlineBoxCharacterRange>::iterator end = chunk.boxes.end(); for (; it != end; ++it) { SVGInlineBoxCharacterRange& range = *it; SVGInlineTextBox* box = static_cast<SVGInlineTextBox*>(range.box); RenderStyle* style = box->renderer()->style(); for (int i = range.startOffset; i < range.endOffset; ++i) { ASSERT(charIt <= chunk.end); // Determine how many characters - starting from the current - can be measured at once. // Important for non-absolute positioned non-latin1 text (ie. Arabic) where ie. the width // of a string is not the sum of the boundaries of all contained glyphs. Vector<SVGChar>::iterator itSearch = charIt + 1; Vector<SVGChar>::iterator endSearch = charIt + range.endOffset - i; while (itSearch != endSearch) { // No need to check for 'isHidden()' here as this function is not called for text paths. if (itSearch->drawnSeperated) break; itSearch++; } unsigned int positionOffset = itSearch - charIt; // Calculate width/height of subrange SVGInlineBoxCharacterRange subRange; subRange.box = range.box; subRange.startOffset = i; subRange.endOffset = i + positionOffset; if (calcWidthOnly) length += cummulatedWidthOfInlineBoxCharacterRange(subRange); else length += cummulatedHeightOfInlineBoxCharacterRange(subRange); // Calculate gap between the previous & current range // <text x="10 50 70">ABCD</text> - we need to take the gaps between A & B into account // so add "40" as width, and analogous for B & C, add "20" as width. if (itSearch > chunk.start && itSearch < chunk.end) { SVGChar& lastCharacter = *(itSearch - 1); SVGChar& currentCharacter = *itSearch; int charsConsumed = 0; float glyphWidth = 0.0f; float glyphHeight = 0.0f; String glyphName; String unicodeString; box->measureCharacter(style, i + positionOffset - 1, charsConsumed, glyphName, unicodeString, glyphWidth, glyphHeight); if (calcWidthOnly) length += currentCharacter.x - lastCharacter.x - glyphWidth; else length += currentCharacter.y - lastCharacter.y - glyphHeight; } // Advance processed characters i += positionOffset - 1; charIt = itSearch; } } ASSERT(charIt == chunk.end); return length; }
void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk) { bool processTextLength = chunk.hasDesiredTextLength(); bool processTextAnchor = chunk.hasTextAnchor(); if (!processTextAnchor && !processTextLength) return; const Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); unsigned boxCount = boxes.size(); if (!boxCount) return; // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes). float chunkLength = 0; unsigned chunkCharacters = 0; chunk.calculateLength(chunkLength, chunkCharacters); bool isVerticalText = chunk.isVerticalText(); if (processTextLength) { if (chunk.hasLengthAdjustSpacing()) { float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters; unsigned atCharacter = 0; for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments(); if (fragments.isEmpty()) continue; processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter); } } else { ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs()); float textLengthScale = chunk.desiredTextLength() / chunkLength; AffineTransform spacingAndGlyphsTransform; bool foundFirstFragment = false; for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { SVGInlineTextBox* textBox = boxes[boxPosition]; Vector<SVGTextFragment>& fragments = textBox->textFragments(); if (fragments.isEmpty()) continue; if (!foundFirstFragment) { foundFirstFragment = true; buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform); } m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform); } } } if (!processTextAnchor) return; // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift. if (processTextLength && chunk.hasLengthAdjustSpacing()) { chunkLength = 0; chunkCharacters = 0; chunk.calculateLength(chunkLength, chunkCharacters); } float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength); for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments(); if (fragments.isEmpty()) continue; processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments); } }
void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount) { SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart]; ASSERT(textBox); RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer()); ASSERT(textRenderer); const RenderStyle* style = textRenderer->style(); ASSERT(style); const SVGRenderStyle* svgStyle = style->svgStyle(); ASSERT(svgStyle); // Build chunk style flags. unsigned chunkStyle = SVGTextChunk::DefaultStyle; // Handle 'direction' property. if (!style->isLeftToRightDirection()) chunkStyle |= SVGTextChunk::RightToLeftText; // Handle 'writing-mode' property. if (svgStyle->isVerticalWritingMode()) chunkStyle |= SVGTextChunk::VerticalText; // Handle 'text-anchor' property. switch (svgStyle->textAnchor()) { case TA_START: break; case TA_MIDDLE: chunkStyle |= SVGTextChunk::MiddleAnchor; break; case TA_END: chunkStyle |= SVGTextChunk::EndAnchor; break; }; // Handle 'lengthAdjust' property. float desiredTextLength = 0; if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer->parent())) { desiredTextLength = textContentElement->specifiedTextLength().value(textContentElement); switch (textContentElement->lengthAdjust()) { case SVGTextContentElement::LENGTHADJUST_UNKNOWN: break; case SVGTextContentElement::LENGTHADJUST_SPACING: chunkStyle |= SVGTextChunk::LengthAdjustSpacing; break; case SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS: chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs; break; }; } SVGTextChunk chunk(chunkStyle, desiredTextLength); Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); for (unsigned i = boxStart; i < boxStart + boxCount; ++i) boxes.append(lineLayoutBoxes[i]); m_textChunks.append(chunk); }
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); } }