bool SVGTextLayoutEngine::currentVisualCharacterMetrics(SVGInlineTextBox* textBox, RenderSVGInlineText* text, SVGTextMetrics& metrics) { SVGTextLayoutAttributes& attributes = text->layoutAttributes(); Vector<SVGTextMetrics>& textMetricsValues = attributes.textMetricsValues(); ASSERT(!textMetricsValues.isEmpty()); unsigned textMetricsSize = textMetricsValues.size(); unsigned boxStart = textBox->start(); unsigned boxLength = textBox->len(); if (m_visualMetricsListOffset == textMetricsSize) return false; while (m_visualMetricsListOffset < textMetricsSize) { SVGTextMetrics& visualMetrics = textMetricsValues.at(m_visualMetricsListOffset); // Advance to text box start location. if (m_visualCharacterOffset < boxStart) { advanceToNextVisualCharacter(visualMetrics); continue; } // Stop if we've finished processing this text box. if (m_visualCharacterOffset >= boxStart + boxLength) return false; metrics = visualMetrics; return true; } return false; }
bool SVGTextLayoutEngine::currentVisualCharacterMetrics(SVGInlineTextBox* textBox, Vector<SVGTextMetrics>& visualMetricsValues, SVGTextMetrics& visualMetrics) { ASSERT(!visualMetricsValues.isEmpty()); unsigned textMetricsSize = visualMetricsValues.size(); unsigned boxStart = textBox->start(); unsigned boxLength = textBox->len(); if (m_visualMetricsListOffset == textMetricsSize) return false; while (m_visualMetricsListOffset < textMetricsSize) { // Advance to text box start location. if (m_visualCharacterOffset < boxStart) { advanceToNextVisualCharacter(visualMetricsValues[m_visualMetricsListOffset]); continue; } // Stop if we've finished processing this text box. if (m_visualCharacterOffset >= boxStart + boxLength) return false; visualMetrics = visualMetricsValues[m_visualMetricsListOffset]; return true; } return false; }
void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, RenderSVGInlineText* text, const RenderStyle* style) { SVGElement* lengthContext = static_cast<SVGElement*>(text->parent()->node()); RenderObject* textParent = text->parent(); bool definesTextLength = textParent ? parentDefinesTextLength(textParent) : false; const SVGRenderStyle* svgStyle = style->svgStyle(); ASSERT(svgStyle); m_visualMetricsListOffset = 0; m_visualCharacterOffset = 0; Vector<SVGTextMetrics>& textMetricsValues = text->layoutAttributes().textMetricsValues(); const UChar* characters = text->characters(); const Font& font = style->font(); SVGTextLayoutEngineSpacing spacingLayout(font); 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::emptyMetrics(); if (!currentVisualCharacterMetrics(textBox, text, visualMetrics)) break; if (visualMetrics == SVGTextMetrics::emptyMetrics()) { advanceToNextVisualCharacter(visualMetrics); continue; } SVGTextLayoutAttributes logicalAttributes; if (!currentLogicalCharacterAttributes(logicalAttributes)) break; SVGTextMetrics logicalMetrics = SVGTextMetrics::emptyMetrics(); if (!currentLogicalCharacterMetrics(logicalAttributes, logicalMetrics)) break; Vector<float>& xValues = logicalAttributes.xValues(); Vector<float>& yValues = logicalAttributes.yValues(); Vector<float>& dxValues = logicalAttributes.dxValues(); Vector<float>& dyValues = logicalAttributes.dyValues(); Vector<float>& rotateValues = logicalAttributes.rotateValues(); float x = xValues.at(m_logicalCharacterOffset); float y = yValues.at(m_logicalCharacterOffset); // 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 = 0; if (!rotateValues.isEmpty()) { float newAngle = rotateValues.at(m_logicalCharacterOffset); if (newAngle != SVGTextLayoutAttributes::emptyValue()) angle = newAngle; } // Calculate glyph orientation angle. const UChar* currentCharacter = characters + 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. updateCharacerPositionIfNeeded(x, y); // Apply dx/dy value adjustments to current text position, if needed. updateRelativePositionAdjustmentsIfNeeded(dxValues, dyValues); // Calculate SVG Fonts kerning, if needed. float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, visualMetrics.glyph()); // Calculate CSS 'kerning', 'letter-spacing' and 'word-spacing' for next character, if needed. float spacing = spacingLayout.calculateCSSKerningAndSpacing(svgStyle, lengthContext, 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 - kerning; 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 - kerning; 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; bool ok = false; FloatPoint point = m_textPath.pointAtLength(textPathOffset, ok); ASSERT(ok); x = point.x(); y = point.y(); angle = m_textPath.normalAngleAtLength(textPathOffset, ok); ASSERT(ok); // 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; y -= kerning; } else { x -= kerning; y -= baselineShift; } x += m_dx; y += m_dy; } // Determine wheter we have to start a new fragment. bool shouldStartNewFragment = false; if (m_dx || m_dy) shouldStartNewFragment = true; if (!shouldStartNewFragment && (m_isVerticalText || m_inPathLayout)) shouldStartNewFragment = true; if (!shouldStartNewFragment && (angle || angle != lastAngle || orientationAngle)) shouldStartNewFragment = true; if (!shouldStartNewFragment && (kerning || applySpacingToNextCharacter || definesTextLength)) shouldStartNewFragment = true; // If we already started a fragment, close it now. if (didStartTextFragment && shouldStartNewFragment) { applySpacingToNextCharacter = false; recordTextFragment(textBox, textMetricsValues); } // 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, textMetricsValues); }