void writeSVGInlineText(TextStream& ts, const LayoutSVGInlineText& text, int indent) { writeStandardPrefix(ts, text, indent); ts << " " << enclosingIntRect(FloatRect(text.firstRunOrigin(), text.floatLinesBoundingBox().size())) << "\n"; writeResources(ts, text, indent); writeSVGInlineTextBoxes(ts, text, indent); }
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; }
static inline FloatRect calculateFragmentBoundaries(const LayoutSVGInlineText& textLayoutObject, const SVGTextFragment& fragment) { float scalingFactor = textLayoutObject.scalingFactor(); ASSERT(scalingFactor); float baseline = textLayoutObject.scaledFont().fontMetrics().floatAscent() / scalingFactor; AffineTransform fragmentTransform; FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); fragment.buildFragmentTransform(fragmentTransform); return fragmentTransform.mapRect(fragmentRect); }
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 void collectTextBoxesInLogicalOrder(const LayoutSVGInlineText& textLayoutObject, Vector<SVGInlineTextBox*>& textBoxes) { textBoxes.shrink(0); for (InlineTextBox* textBox = textLayoutObject.firstTextBox(); textBox; textBox = textBox->nextTextBox()) textBoxes.append(toSVGInlineTextBox(textBox)); std::sort(textBoxes.begin(), textBoxes.end(), InlineTextBox::compareByStart); }
static inline bool textShouldBePainted(LayoutSVGInlineText& textLayoutObject) { // Font::pixelSize(), returns FontDescription::computedPixelSize(), which returns "int(x + 0.5)". // If the absolute font size on screen is below x=0.5, don't render anything. return textLayoutObject.scaledFont().fontDescription().computedPixelSize(); }
void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, const LayoutSVGInlineText& text, const ComputedStyle& style) { if (m_inPathLayout && !m_textPathCalculator) return; const SVGComputedStyle& svgStyle = style.svgStyle(); // Find the start of the current text box in the metrics list. m_visualMetricsIterator.advanceToTextStart(&text, 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, &text); // 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. UChar currentCharacter = text.characterAt(m_visualMetricsIterator.characterOffset()); float orientationAngle = baselineLayout.calculateGlyphOrientationAngle(m_isVerticalText, svgStyle, currentCharacter); // Calculate glyph advance & x/y orientation shifts. float xOrientationShift = 0; float yOrientationShift = 0; float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, visualMetrics, orientationAngle, xOrientationShift, yOrientationShift); // 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; 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. xOrientationShift += m_dx + baselineShift; yOrientationShift -= 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. xOrientationShift -= scaledGlyphAdvance / 2; yOrientationShift += 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 || orientationAngle || 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 (xOrientationShift || yOrientationShift) m_currentTextFragment.transform.translate(xOrientationShift, yOrientationShift); if (orientationAngle) m_currentTextFragment.transform.rotate(orientationAngle); 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); }