void LayoutSVGText::subtreeChildWillBeRemoved(LayoutObject* child, Vector<SVGTextLayoutAttributes*, 2>& affectedAttributes) { ASSERT(child); if (!shouldHandleSubtreeMutations()) return; checkLayoutAttributesConsistency(this, m_layoutAttributes); // The positioning elements cache depends on the size of each text layoutObject in the // subtree. If this changes, clear the cache. It's going to be rebuilt below. m_layoutAttributesBuilder.clearTextPositioningElements(); if (m_layoutAttributes.isEmpty() || !child->isSVGInlineText()) return; // This logic requires that the 'text' child is still inserted in the tree. LayoutSVGInlineText* text = toLayoutSVGInlineText(child); SVGTextLayoutAttributes* previous = nullptr; SVGTextLayoutAttributes* next = nullptr; if (!documentBeingDestroyed()) findPreviousAndNextAttributes(this, text, previous, next); if (previous) affectedAttributes.append(previous); if (next) affectedAttributes.append(next); size_t position = m_layoutAttributes.find(text->layoutAttributes()); ASSERT(position != kNotFound); m_layoutAttributes.remove(position); }
static inline bool findPreviousAndNextAttributes(LayoutSVGText* root, LayoutSVGInlineText* locateElement, SVGTextLayoutAttributes*& previous, SVGTextLayoutAttributes*& next) { ASSERT(root); ASSERT(locateElement); bool stopAfterNext = false; LayoutObject* current = root->firstChild(); while (current) { if (current->isSVGInlineText()) { LayoutSVGInlineText* text = toLayoutSVGInlineText(current); if (locateElement != text) { if (stopAfterNext) { next = text->layoutAttributes(); return true; } previous = text->layoutAttributes(); } else { stopAfterNext = true; } } else if (current->isSVGInline()) { // Descend into text content (if possible). if (LayoutObject* child = toLayoutSVGInline(current)->firstChild()) { current = child; continue; } } current = current->nextInPreOrderAfterChildren(root); } return false; }
// 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; LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(*layoutObject); ASSERT(textLayoutObject.style()); // TODO(fs): Allow filtering the search earlier, since we should be // able to trivially reject (prune) at least some of the queries. collectTextBoxesInLogicalOrder(textLayoutObject, textBoxes); for (const SVGInlineTextBox* textBox : textBoxes) { if (queryTextBox(queryData, textBox, fragmentCallback)) return; queryData->currentOffset += textBox->len(); } } }
static inline void collectLayoutAttributes(LayoutObject* text, Vector<SVGTextLayoutAttributes*>& attributes) { for (LayoutObject* descendant = text; descendant; descendant = descendant->nextInPreOrder(text)) { if (descendant->isSVGInlineText()) attributes.append(toLayoutSVGInlineText(descendant)->layoutAttributes()); } }
void SVGInlineTextBoxPainter::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { ASSERT(paintInfo.shouldPaintWithinRoot(&m_svgInlineTextBox.layoutObject())); ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); ASSERT(m_svgInlineTextBox.truncation() == cNoTruncation); if (m_svgInlineTextBox.layoutObject().style()->visibility() != VISIBLE) return; // We're explicitly not supporting composition & custom underlines and custom highlighters -- unlike InlineTextBox. // If we ever need that for SVG, it's very easy to refactor and reuse the code. if (paintInfo.phase == PaintPhaseSelection && !shouldPaintSelection()) return; LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(m_svgInlineTextBox.layoutObject()); if (!textShouldBePainted(textLayoutObject)) return; DisplayItem::Type displayItemType = DisplayItem::paintPhaseToDrawingType(paintInfo.phase); if (!DrawingRecorder::useCachedDrawingIfPossible(*paintInfo.context, m_svgInlineTextBox, displayItemType)) { LayoutObject& parentLayoutObject = m_svgInlineTextBox.parent()->layoutObject(); const ComputedStyle& style = parentLayoutObject.styleRef(); DrawingRecorder recorder(*paintInfo.context, m_svgInlineTextBox, displayItemType, paintInfo.rect); InlineTextBoxPainter(m_svgInlineTextBox).paintDocumentMarkers( paintInfo.context, paintOffset, style, textLayoutObject.scaledFont(), true); if (!m_svgInlineTextBox.textFragments().isEmpty()) paintTextFragments(paintInfo, parentLayoutObject); } }
static inline void writeSVGInlineTextBox(TextStream& ts, SVGInlineTextBox* textBox, int indent) { Vector<SVGTextFragment>& fragments = textBox->textFragments(); if (fragments.isEmpty()) return; LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(textBox->layoutObject()); const SVGComputedStyle& svgStyle = textLayoutObject.style()->svgStyle(); String text = textBox->layoutObject().text(); unsigned fragmentsSize = fragments.size(); for (unsigned i = 0; i < fragmentsSize; ++i) { SVGTextFragment& fragment = fragments.at(i); writeIndent(ts, indent + 1); unsigned startOffset = fragment.characterOffset; unsigned endOffset = fragment.characterOffset + fragment.length; // FIXME: Remove this hack, once the new text layout engine is completly landed. We want to preserve the old layout test results for now. ts << "chunk 1 "; ETextAnchor anchor = svgStyle.textAnchor(); bool isVerticalText = svgStyle.isVerticalWritingMode(); if (anchor == TA_MIDDLE) { ts << "(middle anchor"; if (isVerticalText) ts << ", vertical"; ts << ") "; } else if (anchor == TA_END) { ts << "(end anchor"; if (isVerticalText) ts << ", vertical"; ts << ") "; } else if (isVerticalText) { ts << "(vertical) "; } startOffset -= textBox->start(); endOffset -= textBox->start(); // </hack> ts << "text run " << i + 1 << " at (" << fragment.x << "," << fragment.y << ")"; ts << " startOffset " << startOffset << " endOffset " << endOffset; if (isVerticalText) ts << " height " << fragment.height; else ts << " width " << fragment.width; if (!textBox->isLeftToRightDirection() || textBox->dirOverride()) { ts << (textBox->isLeftToRightDirection() ? " LTR" : " RTL"); if (textBox->dirOverride()) ts << " override"; } ts << ": " << quoteAndEscapeNonPrintables(text.substring(fragment.characterOffset, fragment.length)) << "\n"; } }
static inline void updateFontInAllDescendants(LayoutObject* start, SVGTextLayoutAttributesBuilder* builder = nullptr) { for (LayoutObject* descendant = start; descendant; descendant = descendant->nextInPreOrder(start)) { if (!descendant->isSVGInlineText()) continue; LayoutSVGInlineText* text = toLayoutSVGInlineText(descendant); text->updateScaledFont(); if (builder) builder->rebuildMetricsForTextLayoutObject(text); } }
static bool queryTextBox(QueryData* queryData, const SVGInlineTextBox* textBox, ProcessTextFragmentCallback fragmentCallback) { queryData->textBox = textBox; queryData->textLayoutObject = &toLayoutSVGInlineText(textBox->layoutObject()); queryData->isVerticalText = textBox->layoutObject().style()->svgStyle().isVerticalWritingMode(); // Loop over all text fragments in this text box, firing a callback for each. for (const SVGTextFragment& fragment : textBox->textFragments()) { if (fragmentCallback(queryData, fragment)) return true; } return false; }
void LayoutSVGText::subtreeStyleDidChange() { if (!shouldHandleSubtreeMutations() || documentBeingDestroyed()) return; checkLayoutAttributesConsistency(this, m_layoutAttributes); // Only update the metrics cache, but not the text positioning element cache // nor the layout attributes cached in the leaf #text layoutObjects. FontCachePurgePreventer fontCachePurgePreventer; for (LayoutObject* descendant = firstChild(); descendant; descendant = descendant->nextInPreOrder(this)) { if (descendant->isSVGInlineText()) m_layoutAttributesBuilder.rebuildMetricsForTextLayoutObject(toLayoutSVGInlineText(descendant)); } }
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); } } } }
void SVGInlineTextBoxPainter::paintSelectionBackground(const PaintInfo& paintInfo) { if (m_svgInlineTextBox.layoutObject().style()->visibility() != VISIBLE) return; ASSERT(!m_svgInlineTextBox.layoutObject().document().printing()); if (paintInfo.phase == PaintPhaseSelection || !shouldPaintSelection()) return; Color backgroundColor = m_svgInlineTextBox.layoutObject().selectionBackgroundColor(); if (!backgroundColor.alpha()) return; LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(m_svgInlineTextBox.layoutObject()); if (!textShouldBePainted(textLayoutObject)) return; const ComputedStyle& style = m_svgInlineTextBox.parent()->layoutObject().styleRef(); int startPosition, endPosition; m_svgInlineTextBox.selectionStartEnd(startPosition, endPosition); int fragmentStartPosition = 0; int fragmentEndPosition = 0; AffineTransform fragmentTransform; unsigned textFragmentsSize = m_svgInlineTextBox.textFragments().size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { SVGTextFragment& fragment = m_svgInlineTextBox.textFragments().at(i); fragmentStartPosition = startPosition; fragmentEndPosition = endPosition; if (!m_svgInlineTextBox.mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) continue; GraphicsContextStateSaver stateSaver(*paintInfo.context); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) paintInfo.context->concatCTM(fragmentTransform); paintInfo.context->setFillColor(backgroundColor); paintInfo.context->fillRect(m_svgInlineTextBox.selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style), backgroundColor); } }
static void walkTree(LayoutSVGText* start, LayoutSVGInlineText* stopAtLeaf, MeasureTextData* data) { LayoutObject* child = start->firstChild(); while (child) { if (child->isSVGInlineText()) { LayoutSVGInlineText* text = toLayoutSVGInlineText(child); measureTextLayoutObject(text, data, !stopAtLeaf || stopAtLeaf == text); if (stopAtLeaf && stopAtLeaf == text) return; } else if (child->isSVGInline()) { // Visit children of text content elements. if (LayoutObject* inlineChild = toLayoutSVGInline(child)->firstChild()) { child = inlineChild; continue; } } child = child->nextInPreOrderAfterChildren(start); } }
int CharacterNumberAtPositionData::characterNumberWithin(const LayoutObject* queryRoot) const { // http://www.w3.org/TR/SVG/single-page.html#text-__svg__SVGTextContentElement__getCharNumAtPosition // "If no such character exists, a value of -1 is returned." if (!hitLayoutObject) return -1; ASSERT(queryRoot); int characterNumber = offsetInTextNode; // Accumulate the lengths of all the text nodes preceding the target layout // object within the queried root, to get the complete character number. for (const LayoutObject* layoutObject = hitLayoutObject->previousInPreOrder(queryRoot); layoutObject; layoutObject = layoutObject->previousInPreOrder(queryRoot)) { if (!layoutObject->isSVGInlineText()) continue; characterNumber += toLayoutSVGInlineText(layoutObject)->resolvedTextLength(); } return characterNumber; }
int SVGInlineTextBox::offsetForPositionInFragment(const SVGTextFragment& fragment, FloatWillBeLayoutUnit position, bool includePartialGlyphs) const { LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(this->layoutObject()); float scalingFactor = textLayoutObject.scalingFactor(); ASSERT(scalingFactor); const ComputedStyle& style = textLayoutObject.styleRef(); TextRun textRun = constructTextRun(style, fragment); // Eventually handle lengthAdjust="spacingAndGlyphs". // FIXME: Handle vertical text. AffineTransform fragmentTransform; fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) textRun.setHorizontalGlyphStretch(narrowPrecisionToFloat(fragmentTransform.xScale())); return fragment.characterOffset - start() + textLayoutObject.scaledFont().offsetForPosition(textRun, position * scalingFactor, includePartialGlyphs); }
void SVGTextLayoutEngine::layoutInlineTextBox(SVGInlineTextBox* textBox) { ASSERT(textBox); LayoutSVGInlineText& text = toLayoutSVGInlineText(textBox->layoutObject()); ASSERT(text.parent()); ASSERT(text.parent()->node()); ASSERT(text.parent()->node()->isSVGElement()); const ComputedStyle& style = text.styleRef(); textBox->clearTextFragments(); m_isVerticalText = style.svgStyle().isVerticalWritingMode(); layoutTextOnLineOrPath(textBox, text, style); if (m_inPathLayout) return; m_lineLayoutBoxes.append(textBox); }
void LayoutSVGText::subtreeTextDidChange(LayoutSVGInlineText* text) { ASSERT(text); ASSERT(!beingDestroyed()); if (!everHadLayout()) { ASSERT(m_layoutAttributes.isEmpty()); ASSERT(!m_layoutAttributesBuilder.numberOfTextPositioningElements()); return; } // Always protect the cache before clearing text positioning elements when the cache will subsequently be rebuilt. FontCachePurgePreventer fontCachePurgePreventer; // The positioning elements cache depends on the size of each text layoutObject in the // subtree. If this changes, clear the cache. It's going to be rebuilt below. m_layoutAttributesBuilder.clearTextPositioningElements(); for (LayoutObject* descendant = text; descendant; descendant = descendant->nextInPreOrder(text)) { if (descendant->isSVGInlineText()) m_layoutAttributesBuilder.buildLayoutAttributesForText(toLayoutSVGInlineText(descendant)); } }
FloatRect SVGInlineTextBox::selectionRectForTextFragment(const SVGTextFragment& fragment, int startPosition, int endPosition, const ComputedStyle& style) { ASSERT(startPosition < endPosition); LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(this->layoutObject()); float scalingFactor = textLayoutObject.scalingFactor(); ASSERT(scalingFactor); const Font& scaledFont = textLayoutObject.scaledFont(); const FontMetrics& scaledFontMetrics = scaledFont.fontMetrics(); 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; }
FloatRectWillBeLayoutRect SVGInlineTextBox::calculateBoundaries() const { FloatRectWillBeLayoutRect textRect; LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(this->layoutObject()); float scalingFactor = textLayoutObject.scalingFactor(); ASSERT(scalingFactor); FloatWillBeLayoutUnit baseline = textLayoutObject.scaledFont().fontMetrics().floatAscent() / scalingFactor; AffineTransform fragmentTransform; unsigned textFragmentsSize = m_textFragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { const SVGTextFragment& fragment = m_textFragments.at(i); FloatRectWillBeLayoutRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); fragment.buildFragmentTransform(fragmentTransform); fragmentRect = fragmentTransform.mapRect(fragmentRect.toFloatRect()); textRect.unite(fragmentRect); } return textRect; }
bool SVGLayoutSupport::isLayoutableTextNode(const LayoutObject* object) { ASSERT(object->isText()); // <br> is marked as text, but is not handled by the SVG layout code-path. return object->isSVGInlineText() && !toLayoutSVGInlineText(object)->hasEmptyText(); }
void SVGInlineTextBoxPainter::paintTextWithShadows(const PaintInfo& paintInfo, const ComputedStyle& style, TextRun& textRun, const SVGTextFragment& fragment, int startPosition, int endPosition, LayoutSVGResourceMode resourceMode) { LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(m_svgInlineTextBox.layoutObject()); float scalingFactor = textLayoutObject.scalingFactor(); ASSERT(scalingFactor); const Font& scaledFont = textLayoutObject.scaledFont(); const ShadowList* shadowList = style.textShadow(); GraphicsContext* context = paintInfo.context; // Text shadows are disabled when printing. http://crbug.com/258321 bool hasShadow = shadowList && !context->printing(); FloatPoint textOrigin(fragment.x, fragment.y); FloatSize textSize(fragment.width, fragment.height); AffineTransform paintServerTransform; const AffineTransform* additionalPaintServerTransform = 0; GraphicsContextStateSaver stateSaver(*context, false); if (scalingFactor != 1) { textOrigin.scale(scalingFactor, scalingFactor); textSize.scale(scalingFactor); stateSaver.save(); context->scale(1 / scalingFactor, 1 / scalingFactor); // Adjust the paint-server coordinate space. paintServerTransform.scale(scalingFactor); additionalPaintServerTransform = &paintServerTransform; } SkPaint paint; if (!SVGPaintContext::paintForLayoutObject(paintInfo, style, m_svgInlineTextBox.parent()->layoutObject(), resourceMode, paint, additionalPaintServerTransform)) return; paint.setAntiAlias(true); if (hasShadow) { OwnPtr<DrawLooperBuilder> drawLooperBuilder = shadowList->createDrawLooper(DrawLooperBuilder::ShadowRespectsAlpha, style.visitedDependentColor(CSSPropertyColor)); RefPtr<SkDrawLooper> drawLooper = drawLooperBuilder->detachDrawLooper(); paint.setLooper(drawLooper.get()); } if (resourceMode == ApplyToStrokeMode) { StrokeData strokeData; SVGLayoutSupport::applyStrokeStyleToStrokeData(strokeData, style, m_svgInlineTextBox.parent()->layoutObject()); if (style.svgStyle().vectorEffect() != VE_NON_SCALING_STROKE) strokeData.setThickness(strokeData.thickness() * scalingFactor); strokeData.setupPaint(&paint); } TextRunPaintInfo textRunPaintInfo(textRun); textRunPaintInfo.from = startPosition; textRunPaintInfo.to = endPosition; float baseline = scaledFont.fontMetrics().floatAscent(); textRunPaintInfo.bounds = FloatRect(textOrigin.x(), textOrigin.y() - baseline, textSize.width(), textSize.height()); context->drawText(scaledFont, textRunPaintInfo, textOrigin, paint); }