void SVGTextLayoutAttributesBuilder::buildLayoutScopes(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter) { for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { if (child->isSVGInlineText()) { RenderSVGInlineText* text = toRenderSVGInlineText(child); if (!shouldPreserveAllWhiteSpace(text->style())) { const UChar* characters = text->characters(); unsigned textLength = text->textLength(); for (unsigned textPosition = 0; textPosition < textLength; ++textPosition) { const UChar& currentCharacter = characters[textPosition]; if (characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter)) continue; lastCharacter = currentCharacter; ++atCharacter; } } else atCharacter += text->textLength(); continue; } if (!child->isSVGInline()) continue; unsigned textContentStart = atCharacter; buildLayoutScopes(child, atCharacter, lastCharacter); LayoutScope scope; buildLayoutScope(scope, child, textContentStart, atCharacter - textContentStart); m_scopes.append(scope); } }
void writeSVGInlineText(TextStream& ts, const RenderSVGInlineText& text, int indent) { writeStandardPrefix(ts, text, indent); ts << " " << enclosingIntRect(FloatRect(text.firstRunOrigin(), text.floatLinesBoundingBox().size())) << "\n"; writeResources(ts, text, indent); writeSVGInlineTextBoxes(ts, text, indent); }
static inline bool findPreviousAndNextAttributes(RenderObject* root, RenderSVGInlineText* locateElement, SVGTextLayoutAttributes*& previous, SVGTextLayoutAttributes*& next) { ASSERT(root); ASSERT(locateElement); bool stopAfterNext = false; RenderObject* current = root->firstChild(); while (current) { if (current->isSVGInlineText()) { RenderSVGInlineText* text = toRenderSVGInlineText(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 (RenderObject* child = current->firstChild()) { current = child; continue; } } current = current->nextInPreOrderAfterChildren(root); } return false; }
FloatRect SVGInlineTextBox::selectionRectForTextFragment(const SVGTextFragment& fragment, int startPosition, int endPosition, RenderStyle* style) { ASSERT(startPosition < endPosition); ASSERT(style); FontCachePurgePreventer fontCachePurgePreventer; RenderSVGInlineText* textRenderer = toRenderSVGInlineText(this->textRenderer()); ASSERT(textRenderer); float scalingFactor = textRenderer->scalingFactor(); ASSERT(scalingFactor); const Font& scaledFont = textRenderer->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; }
FloatRect SVGInlineTextBox::calculateBoundaries() const { FloatRect textRect; RenderSVGInlineText* textRenderer = toRenderSVGInlineText(this->textRenderer()); ASSERT(textRenderer); float scalingFactor = textRenderer->scalingFactor(); ASSERT(scalingFactor); float baseline = textRenderer->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); FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) fragmentRect = fragmentTransform.mapRect(fragmentRect); textRect.unite(fragmentRect); } return textRect; }
void RenderSVGText::subtreeChildWillBeRemoved(RenderObject* 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 renderer 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. RenderSVGInlineText* text = toRenderSVGInlineText(child); bool stopAfterNext = false; SVGTextLayoutAttributes* previous = 0; SVGTextLayoutAttributes* next = 0; if (!documentBeingDestroyed()) findPreviousAndNextAttributes(this, text, stopAfterNext, previous, next); if (previous) affectedAttributes.append(previous); if (next) affectedAttributes.append(next); size_t position = m_layoutAttributes.find(text->layoutAttributes()); ASSERT(position != notFound); m_layoutAttributes.remove(position); }
static inline bool findPreviousAndNextAttributes(RenderObject* start, RenderSVGInlineText* locateElement, bool& stopAfterNext, SVGTextLayoutAttributes*& previous, SVGTextLayoutAttributes*& next) { ASSERT(start); ASSERT(locateElement); // FIXME: Make this iterative. for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { if (child->isSVGInlineText()) { RenderSVGInlineText* text = toRenderSVGInlineText(child); if (locateElement != text) { if (stopAfterNext) { next = text->layoutAttributes(); return true; } previous = text->layoutAttributes(); continue; } stopAfterNext = true; continue; } if (!child->isSVGInline()) continue; if (findPreviousAndNextAttributes(child, locateElement, stopAfterNext, previous, next)) return true; } return false; }
void write(TextStream& ts, const RenderSVGInlineText& text, int indent) { writeStandardPrefix(ts, text, indent); // Why not just linesBoundingBox()? ts << " " << FloatRect(text.firstRunOrigin(), text.linesBoundingBox().size()) << "\n"; writeSVGInlineText(ts, text, indent); }
void SVGInlineTextBox::paintTextWithShadows(GraphicsContext* context, RenderStyle* style, TextRun& textRun, const SVGTextFragment& fragment, int startPosition, int endPosition) { RenderSVGInlineText* textRenderer = toRenderSVGInlineText(this->textRenderer()); ASSERT(textRenderer); float scalingFactor = textRenderer->scalingFactor(); ASSERT(scalingFactor); const Font& scaledFont = textRenderer->scaledFont(); const ShadowData* shadow = style->textShadow(); FloatPoint textOrigin(fragment.x, fragment.y); FloatSize textSize(fragment.width, fragment.height); if (scalingFactor != 1) { textOrigin.scale(scalingFactor, scalingFactor); textSize.scale(scalingFactor); } FloatRect shadowRect(FloatPoint(textOrigin.x(), textOrigin.y() - scaledFont.fontMetrics().floatAscent()), textSize); do { if (!prepareGraphicsContextForTextPainting(context, scalingFactor, textRun, style)) break; FloatSize extraOffset; if (shadow) extraOffset = applyShadowToGraphicsContext(context, shadow, shadowRect, false /* stroked */, true /* opaque */, true /* horizontal */); AffineTransform originalTransform; if (scalingFactor != 1) { originalTransform = context->getCTM(); AffineTransform newTransform = originalTransform; newTransform.scale(1 / scalingFactor); normalizeTransform(newTransform); context->setCTM(newTransform); } scaledFont.drawText(context, textRun, textOrigin + extraOffset, startPosition, endPosition); if (scalingFactor != 1) context->setCTM(originalTransform); restoreGraphicsContextAfterTextPainting(context, textRun); if (!shadow) break; if (shadow->next()) context->restore(); else context->clearShadow(); shadow = shadow->next(); } while (shadow); }
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()); }
static inline void writeSVGInlineTextBox(TextStream& ts, SVGInlineTextBox* textBox, int indent) { Vector<SVGTextFragment>& fragments = textBox->textFragments(); if (fragments.isEmpty()) return; RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer()); ASSERT(textRenderer); const SVGRenderStyle* svgStyle = textRenderer->style()->svgStyle(); String text = textBox->textRenderer()->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(RenderObject* start, SVGTextLayoutAttributesBuilder* builder = 0) { for (RenderObject* descendant = start; descendant; descendant = descendant->nextInPreOrder(start)) { if (!descendant->isSVGInlineText()) continue; RenderSVGInlineText* text = toRenderSVGInlineText(descendant); text->updateScaledFont(); if (builder) builder->rebuildMetricsForTextRenderer(text); } }
static inline FloatRect calculateFragmentBoundaries(const RenderSVGInlineText& textRenderer, const SVGTextFragment& fragment) { float scalingFactor = textRenderer.scalingFactor(); ASSERT(scalingFactor); float baseline = textRenderer.scaledFont().fontMetrics().floatAscent() / scalingFactor; AffineTransform fragmentTransform; FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); fragment.buildFragmentTransform(fragmentTransform); return fragmentTransform.mapRect(fragmentRect); }
void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter) const { for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { if (child->isSVGInlineText()) { RenderSVGInlineText* text = toRenderSVGInlineText(child); const UChar* characters = text->characters(); unsigned textLength = text->textLength(); bool preserveWhiteSpace = shouldPreserveAllWhiteSpace(text->style()); SVGTextLayoutAttributes attributes; attributes.reserveCapacity(textLength); unsigned valueListPosition = atCharacter; unsigned metricsLength = 1; for (unsigned textPosition = 0; textPosition < textLength; textPosition += metricsLength) { const UChar& currentCharacter = characters[textPosition]; SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(text, textPosition, 1); metricsLength = metrics.length(); if (!preserveWhiteSpace && characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter)) { assignEmptyLayoutAttributesForCharacter(attributes); attributes.textMetricsValues().append(SVGTextMetrics::emptyMetrics()); continue; } assignLayoutAttributesForCharacter(attributes, metrics, valueListPosition); if (metricsLength > 1) { for (unsigned i = 0; i < metricsLength - 1; ++i) assignEmptyLayoutAttributesForCharacter(attributes); } lastCharacter = currentCharacter; valueListPosition += metricsLength; } #if DUMP_TEXT_LAYOUT_ATTRIBUTES > 0 fprintf(stderr, "\nDumping layout attributes for RenderSVGInlineText, renderer=%p, node=%p (atCharacter: %i)\n", text, text->node(), atCharacter); attributes.dump(); #endif text->storeLayoutAttributes(attributes); atCharacter = valueListPosition; continue; } if (!child->isSVGInline()) continue; propagateLayoutAttributes(child, atCharacter, lastCharacter); } }
void SVGInlineTextBox::paintTextWithShadows(GraphicsContext* context, RenderStyle* style, TextRun& textRun, const SVGTextFragment& fragment, int startPosition, int endPosition) { RenderSVGInlineText* textRenderer = toRenderSVGInlineText(this->textRenderer()); ASSERT(textRenderer); float scalingFactor = textRenderer->scalingFactor(); ASSERT(scalingFactor); const Font& scaledFont = textRenderer->scaledFont(); const ShadowList* shadowList = style->textShadow(); // 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); if (scalingFactor != 1) { textOrigin.scale(scalingFactor, scalingFactor); textSize.scale(scalingFactor); context->save(); context->scale(FloatSize(1 / scalingFactor, 1 / scalingFactor)); } if (hasShadow) { DrawLooper drawLooper; for (size_t i = shadowList->shadows().size(); i--; ) { const ShadowData& shadow = shadowList->shadows()[i]; FloatSize offset(shadow.x(), shadow.y()); drawLooper.addShadow(offset, shadow.blur(), shadow.color(), DrawLooper::ShadowRespectsTransforms, DrawLooper::ShadowRespectsAlpha); } drawLooper.addUnmodifiedContent(); context->setDrawLooper(drawLooper); } if (prepareGraphicsContextForTextPainting(context, scalingFactor, textRun, style)) { TextRunPaintInfo textRunPaintInfo(textRun); textRunPaintInfo.from = startPosition; textRunPaintInfo.to = endPosition; textRunPaintInfo.bounds = FloatRect(textOrigin, textSize); scaledFont.drawText(context, textRunPaintInfo, textOrigin); restoreGraphicsContextAfterTextPainting(context, textRun); } if (scalingFactor != 1) context->restore(); else if (hasShadow) context->clearShadow(); }
static inline void processRenderSVGInlineText(const RenderSVGInlineText& text, unsigned& atCharacter, bool& lastCharacterWasSpace) { if (text.style().whiteSpace() == PRE) { atCharacter += text.textLength(); return; } for (unsigned textPosition = 0, textLength = text.textLength(); textPosition < textLength; ++textPosition) { const UChar currentCharacter = text[textPosition]; if (currentCharacter == ' ' && lastCharacterWasSpace) continue; lastCharacterWasSpace = currentCharacter == ' '; ++atCharacter; } }
int SVGInlineTextBox::offsetForPositionInFragment(const SVGTextFragment& fragment, float position, bool includePartialGlyphs) const { RenderSVGInlineText* textRenderer = toRenderSVGInlineText(this->textRenderer()); ASSERT(textRenderer); float scalingFactor = textRenderer->scalingFactor(); ASSERT(scalingFactor); RenderStyle* style = textRenderer->style(); ASSERT(style); 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() + textRenderer->scaledFont().offsetForPosition(textRun, position * scalingFactor, includePartialGlyphs); }
void SVGTextLayoutEngine::layoutInlineTextBox(SVGInlineTextBox* textBox) { ASSERT(textBox); RenderSVGInlineText* text = toRenderSVGInlineText(textBox->textRenderer()); ASSERT(text); ASSERT(text->parent()); ASSERT(text->parent()->node()); ASSERT(text->parent()->node()->isSVGElement()); const RenderStyle* style = text->style(); ASSERT(style); textBox->clearTextFragments(); m_isVerticalText = style->svgStyle()->isVerticalWritingMode(); layoutTextOnLineOrPath(textBox, text, style); if (m_inPathLayout) { m_pathLayoutBoxes.append(textBox); return; } m_lineLayoutBoxes.append(textBox); }
void SVGTextMetricsCalculator::setupBidiRuns() { RenderStyle* style = m_text->style(); 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(); }
void write(TextStream& ts, const RenderSVGInlineText& text, int indent) { writeIndent(ts, indent); ts << text.renderName(); if (text.element()) { String tagName = getTagName(static_cast<SVGStyledElement*>(text.element())); if (!tagName.isEmpty()) ts << " {" << tagName << "}"; } ts << " at (" << text.xPos() << "," << text.yPos() << ") size " << text.width() << "x" << text.height() << "\n"; writeSVGInlineText(ts, text, indent); }
void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, const RenderSVGInlineText& text, const RenderStyle& style) { if (m_inPathLayout && !m_textPathCalculator) return; SVGElement* lengthContext = toSVGElement(text.parent()->node()); RenderObject* textParent = text.parent(); bool definesTextLength = textParent ? parentDefinesTextLength(textParent) : false; const SVGRenderStyle& svgStyle = style.svgStyle(); m_visualMetricsListOffset = 0; m_visualCharacterOffset = 0; const Vector<SVGTextMetrics>& visualMetricsValues = text.layoutAttributes()->textMetricsValues(); ASSERT(!visualMetricsValues.isEmpty()); const Font& font = style.font(); SVGTextLayoutEngineSpacing spacingLayout(font, style.effectiveZoom()); SVGTextLayoutEngineBaseline baselineLayout(font); bool didStartTextFragment = false; bool applySpacingToNextCharacter = false; float lastAngle = 0; float baselineShift = baselineLayout.calculateBaselineShift(svgStyle, lengthContext); baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, &text); // Main layout algorithm. while (true) { // Find the start of the current text box in this list, respecting ligatures. SVGTextMetrics visualMetrics(SVGTextMetrics::SkippedSpaceMetrics); if (!currentVisualCharacterMetrics(textBox, visualMetricsValues, visualMetrics)) break; if (visualMetrics.isEmpty()) { advanceToNextVisualCharacter(visualMetrics); continue; } SVGTextLayoutAttributes* logicalAttributes = 0; 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_visualCharacterOffset == textBox->start()) textBox->setStartsNewTextChunk(logicalAttributes->context()->characterStartsNewTextChunk(m_logicalCharacterOffset)); float angle = data.rotate == SVGTextLayoutAttributes::emptyValue() ? 0 : data.rotate; // Calculate glyph orientation angle. UChar currentCharacter = text.characterAt(m_visualCharacterOffset); 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 (y != SVGTextLayoutAttributes::emptyValue()) 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 (x != SVGTextLayoutAttributes::emptyValue()) 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); advanceToNextVisualCharacter(visualMetrics); 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 || definesTextLength; // If we already started a fragment, close it now. if (didStartTextFragment && shouldStartNewFragment) { applySpacingToNextCharacter = false; recordTextFragment(textBox, visualMetricsValues); } // 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_visualCharacterOffset; m_currentTextFragment.metricsListOffset = m_visualMetricsListOffset; 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); advanceToNextVisualCharacter(visualMetrics); lastAngle = angle; } if (!didStartTextFragment) return; // Close last open fragment, if needed. recordTextFragment(textBox, visualMetricsValues); }
void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* start, Vector<SVGTextLayoutAttributes>& allAttributes, unsigned& atCharacter, UChar& lastCharacter) const { for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { if (child->isSVGInlineText()) { RenderSVGInlineText* text = toRenderSVGInlineText(child); const UChar* characters = text->characters(); unsigned textLength = text->textLength(); bool preserveWhiteSpace = shouldPreserveAllWhiteSpace(text->style()); SVGTextLayoutAttributes attributes(text); attributes.reserveCapacity(textLength); unsigned valueListPosition = atCharacter; unsigned metricsLength = 1; SVGTextMetrics lastMetrics(SVGTextMetrics::SkippedSpaceMetrics); for (unsigned textPosition = 0; textPosition < textLength; textPosition += metricsLength) { const UChar& currentCharacter = characters[textPosition]; SVGTextMetrics startToCurrentMetrics; SVGTextMetrics currentMetrics; unsigned valueListAdvance = 0; if (U16_IS_LEAD(currentCharacter) && (textPosition + 1) < textLength && U16_IS_TRAIL(characters[textPosition + 1])) { // Handle surrogate pairs. startToCurrentMetrics = SVGTextMetrics::measureCharacterRange(text, 0, textPosition + 2); currentMetrics = SVGTextMetrics::measureCharacterRange(text, textPosition, 2); metricsLength = currentMetrics.length(); valueListAdvance = 1; } else { // Handle BMP characters. startToCurrentMetrics = SVGTextMetrics::measureCharacterRange(text, 0, textPosition + 1); currentMetrics = SVGTextMetrics::measureCharacterRange(text, textPosition, 1); metricsLength = currentMetrics.length(); valueListAdvance = metricsLength; } if (!metricsLength) break; // Frequent case for Arabic text: when measuring a single character the arabic isolated form is taken // when rendering the glyph "in context" (with it's surrounding characters) it changes due to shaping. // So whenever runWidthAdvance != currentMetrics.width(), we are processing a text run whose length is // not equal to the sum of the individual lengths of the glyphs, when measuring them isolated. float runWidthAdvance = startToCurrentMetrics.width() - lastMetrics.width(); if (runWidthAdvance != currentMetrics.width()) currentMetrics.setWidth(runWidthAdvance); lastMetrics = startToCurrentMetrics; if (!preserveWhiteSpace && characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter)) { attributes.positioningLists().appendEmptyValues(); attributes.textMetricsValues().append(SVGTextMetrics(SVGTextMetrics::SkippedSpaceMetrics)); continue; } SVGTextLayoutAttributes::PositioningLists& positioningLists = attributes.positioningLists(); positioningLists.appendValuesFromPosition(m_positioningLists, valueListPosition); attributes.textMetricsValues().append(currentMetrics); // Pad x/y/dx/dy/rotate value lists with empty values, if the metrics span more than one character. if (metricsLength > 1) { for (unsigned i = 0; i < metricsLength - 1; ++i) positioningLists.appendEmptyValues(); } lastCharacter = currentCharacter; valueListPosition += valueListAdvance; } #if DUMP_TEXT_LAYOUT_ATTRIBUTES > 0 fprintf(stderr, "\nDumping layout attributes for RenderSVGInlineText, renderer=%p, node=%p (atCharacter: %i)\n", text, text->node(), atCharacter); fprintf(stderr, "BiDi properties: unicode-bidi=%i, block direction=%i\n", text->style()->unicodeBidi(), text->style()->direction()); attributes.dump(); #endif text->storeLayoutAttributes(attributes); allAttributes.append(attributes); atCharacter = valueListPosition; continue; } if (!child->isSVGInline()) continue; propagateLayoutAttributes(child, allAttributes, atCharacter, lastCharacter); } }
void SVGInlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit, LayoutUnit) { ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); ASSERT(truncation() == cNoTruncation); if (renderer()->style()->visibility() != VISIBLE) return; // Note: We're explicitely 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. RenderObject* parentRenderer = parent()->renderer(); ASSERT(parentRenderer); bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; bool hasSelection = !parentRenderer->document().printing() && selectionState() != RenderObject::SelectionNone; if (!hasSelection && paintSelectedTextOnly) return; RenderSVGInlineText* textRenderer = toRenderSVGInlineText(this->textRenderer()); ASSERT(textRenderer); if (!textShouldBePainted(textRenderer)) return; RenderStyle* style = parentRenderer->style(); ASSERT(style); paintDocumentMarkers(paintInfo.context, paintOffset, style, textRenderer->scaledFont(), true); const SVGRenderStyle* svgStyle = style->svgStyle(); ASSERT(svgStyle); bool hasFill = svgStyle->hasFill(); bool hasVisibleStroke = svgStyle->hasVisibleStroke(); RenderStyle* selectionStyle = style; if (hasSelection) { selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION); if (selectionStyle) { const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle(); ASSERT(svgSelectionStyle); if (!hasFill) hasFill = svgSelectionStyle->hasFill(); if (!hasVisibleStroke) hasVisibleStroke = svgSelectionStyle->hasVisibleStroke(); } else selectionStyle = style; } if (textRenderer->frame() && textRenderer->frame()->view() && textRenderer->frame()->view()->paintBehavior() & PaintBehaviorRenderingSVGMask) { hasFill = true; hasVisibleStroke = false; } AffineTransform fragmentTransform; unsigned textFragmentsSize = m_textFragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { SVGTextFragment& fragment = m_textFragments.at(i); ASSERT(!m_paintingResource); GraphicsContextStateSaver stateSaver(*paintInfo.context, false); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) { stateSaver.save(); paintInfo.context->concatCTM(fragmentTransform); } // Spec: All text decorations except line-through should be drawn before the text is filled and stroked; thus, the text is rendered on top of these decorations. unsigned decorations = style->textDecorationsInEffect(); if (decorations & TextDecorationUnderline) paintDecoration(paintInfo.context, TextDecorationUnderline, fragment); if (decorations & TextDecorationOverline) paintDecoration(paintInfo.context, TextDecorationOverline, fragment); for (int i = 0; i < 3; i++) { switch (svgStyle->paintOrderType(i)) { case PT_FILL: // Fill text if (hasFill) { m_paintingResourceMode = ApplyToFillMode | ApplyToTextMode; paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); } break; case PT_STROKE: // Stroke text if (hasVisibleStroke) { m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode; paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); } break; case PT_MARKERS: // Markers don't apply to text break; default: ASSERT_NOT_REACHED(); break; } } // Spec: Line-through should be drawn after the text is filled and stroked; thus, the line-through is rendered on top of the text. if (decorations & TextDecorationLineThrough) paintDecoration(paintInfo.context, TextDecorationLineThrough, fragment); m_paintingResourceMode = ApplyToDefaultMode; } ASSERT(!m_paintingResource); }
static inline void writeSVGInlineText(TextStream& ts, const RenderSVGInlineText& text, int indent) { for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) writeSVGInlineTextBox(ts, static_cast<SVGInlineTextBox*>(box), indent); }
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); }
static inline bool textShouldBePainted(const RenderSVGInlineText& textRenderer) { // 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 textRenderer.scaledFont().pixelSize(); }