bool SVGTextLayoutEngine::currentLogicalCharacterMetrics(SVGTextLayoutAttributes& logicalAttributes, SVGTextMetrics& logicalMetrics) { logicalMetrics = SVGTextMetrics::emptyMetrics(); Vector<SVGTextMetrics>& textMetricsValues = logicalAttributes.textMetricsValues(); unsigned textMetricsSize = textMetricsValues.size(); while (true) { if (m_logicalMetricsListOffset == textMetricsSize) { if (!currentLogicalCharacterAttributes(logicalAttributes)) return false; textMetricsValues = logicalAttributes.textMetricsValues(); textMetricsSize = textMetricsValues.size(); continue; } ASSERT(textMetricsSize); ASSERT(m_logicalMetricsListOffset < textMetricsSize); logicalMetrics = textMetricsValues.at(m_logicalMetricsListOffset); if (logicalMetrics == SVGTextMetrics::emptyMetrics() || (!logicalMetrics.width() && !logicalMetrics.height())) { advanceToNextLogicalCharacter(logicalMetrics); continue; } // Stop if we found the next valid logical text metrics object. return true; } ASSERT_NOT_REACHED(); return true; }
void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const { SVGTextLayoutAttributes* layoutAttributes = queryData->textRenderer->layoutAttributes(); Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes->textMetricsValues(); unsigned textMetricsOffset = fragment.metricsListOffset; // Compute the offset of the fragment within the box, since that's the // space <startPosition, endPosition> is in (and that's what we need). int fragmentOffsetInBox = fragment.characterOffset - queryData->textBox->start(); int fragmentEndInBox = fragmentOffsetInBox + fragment.length; // Find the text metrics cell that start at or contain the character startPosition. while (fragmentOffsetInBox < fragmentEndInBox) { SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset]; int glyphEnd = fragmentOffsetInBox + metrics.length(); if (startPosition < glyphEnd) break; fragmentOffsetInBox = glyphEnd; textMetricsOffset++; } startPosition = fragmentOffsetInBox; // Find the text metrics cell that contain or ends at the character endPosition. while (fragmentOffsetInBox < fragmentEndInBox) { SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset]; fragmentOffsetInBox += metrics.length(); if (fragmentOffsetInBox >= endPosition) break; textMetricsOffset++; } endPosition = fragmentOffsetInBox; }
void SVGTextLayoutAttributesBuilder::assignEmptyLayoutAttributesForCharacter(SVGTextLayoutAttributes& attributes) const { attributes.xValues().append(SVGTextLayoutAttributes::emptyValue()); attributes.yValues().append(SVGTextLayoutAttributes::emptyValue()); attributes.dxValues().append(SVGTextLayoutAttributes::emptyValue()); attributes.dyValues().append(SVGTextLayoutAttributes::emptyValue()); attributes.rotateValues().append(SVGTextLayoutAttributes::emptyValue()); // This doesn't add an empty value to textMetricsValues() on purpose! }
static void measureTextRenderer(RenderSVGInlineText* text, MeasureTextData* data, bool processRenderer) { ASSERT(text); SVGTextLayoutAttributes* attributes = text->layoutAttributes(); Vector<SVGTextMetrics>* textMetricsValues = &attributes->textMetricsValues(); if (processRenderer) { if (data->allCharactersMap) attributes->clear(); else textMetricsValues->clear(); } SVGTextMetricsCalculator calculator(text); bool preserveWhiteSpace = text->style()->whiteSpace() == PRE; unsigned surrogatePairCharacters = 0; unsigned skippedCharacters = 0; unsigned textPosition = 0; unsigned textLength = calculator.textLength(); SVGTextMetrics currentMetrics; for (; textPosition < textLength; textPosition += currentMetrics.length()) { currentMetrics = calculator.computeMetricsForCharacter(textPosition); if (!currentMetrics.length()) break; bool characterIsWhiteSpace = calculator.characterIsWhiteSpace(textPosition); if (characterIsWhiteSpace && !preserveWhiteSpace && data->lastCharacterWasWhiteSpace) { if (processRenderer) textMetricsValues->append(SVGTextMetrics(SVGTextMetrics::SkippedSpaceMetrics)); if (data->allCharactersMap) skippedCharacters += currentMetrics.length(); continue; } if (processRenderer) { if (data->allCharactersMap) { const SVGCharacterDataMap::const_iterator it = data->allCharactersMap->find(data->valueListPosition + textPosition - skippedCharacters - surrogatePairCharacters + 1); if (it != data->allCharactersMap->end()) attributes->characterDataMap().set(textPosition + 1, it->value); } textMetricsValues->append(currentMetrics); } if (data->allCharactersMap && calculator.characterStartsSurrogatePair(textPosition)) surrogatePairCharacters++; data->lastCharacterWasWhiteSpace = characterIsWhiteSpace; } if (!data->allCharactersMap) return; data->valueListPosition += textPosition - skippedCharacters; }
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 SVGTextMetricsBuilder::measureTextRenderer(RenderSVGInlineText* text, MeasureTextData* data) { ASSERT(text); SVGTextLayoutAttributes* attributes = text->layoutAttributes(); Vector<SVGTextMetrics>* textMetricsValues = &attributes->textMetricsValues(); if (data->processRenderer) { if (data->allCharactersMap) attributes->clear(); else textMetricsValues->clear(); } initializeMeasurementWithTextRenderer(text); bool preserveWhiteSpace = text->style()->whiteSpace() == PRE; int surrogatePairCharacters = 0; while (advance()) { const UChar* currentCharacter = m_run.data16(m_textPosition); if (*currentCharacter == ' ' && !preserveWhiteSpace && (!data->lastCharacter || *data->lastCharacter == ' ')) { if (data->processRenderer) textMetricsValues->append(SVGTextMetrics(SVGTextMetrics::SkippedSpaceMetrics)); if (data->allCharactersMap) data->skippedCharacters += m_currentMetrics.length(); continue; } if (data->processRenderer) { if (data->allCharactersMap) { const SVGCharacterDataMap::const_iterator it = data->allCharactersMap->find(data->valueListPosition + m_textPosition - data->skippedCharacters - surrogatePairCharacters + 1); if (it != data->allCharactersMap->end()) attributes->characterDataMap().set(m_textPosition + 1, it->value); } textMetricsValues->append(m_currentMetrics); } if (data->allCharactersMap && currentCharacterStartsSurrogatePair()) surrogatePairCharacters++; data->lastCharacter = currentCharacter; } if (!data->allCharactersMap) return; data->valueListPosition += m_textPosition - data->skippedCharacters; data->skippedCharacters = 0; }
void RenderSVGText::subtreeChildWasAdded(RenderObject* child) { ASSERT(child); if (!shouldHandleSubtreeMutations() || documentBeingDestroyed()) return; // Always protect the cache before clearing text positioning elements when the cache will subsequently be rebuilt. FontCachePurgePreventer fontCachePurgePreventer; // The positioning elements cache doesn't include the new 'child' yet. Clear the // cache, as the next buildLayoutAttributesForTextRenderer() call rebuilds it. m_layoutAttributesBuilder.clearTextPositioningElements(); if (!child->isSVGInlineText() && !child->isSVGInline()) return; // Detect changes in layout attributes and only measure those text parts that have changed! Vector<SVGTextLayoutAttributes*> newLayoutAttributes; collectLayoutAttributes(this, newLayoutAttributes); if (newLayoutAttributes.isEmpty()) { ASSERT(m_layoutAttributes.isEmpty()); return; } // Compare m_layoutAttributes with newLayoutAttributes to figure out which attribute got added. size_t size = newLayoutAttributes.size(); SVGTextLayoutAttributes* attributes = 0; for (size_t i = 0; i < size; ++i) { attributes = newLayoutAttributes[i]; if (m_layoutAttributes.find(attributes) == notFound) { // Every time this is invoked, there's only a single new entry in the newLayoutAttributes list, compared to the old in m_layoutAttributes. bool stopAfterNext = false; SVGTextLayoutAttributes* previous = 0; SVGTextLayoutAttributes* next = 0; ASSERT_UNUSED(child, &attributes->context() == child); findPreviousAndNextAttributes(this, &attributes->context(), stopAfterNext, previous, next); if (previous) m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(previous->context()); m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(attributes->context()); if (next) m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(next->context()); break; } } #ifndef NDEBUG // Verify that m_layoutAttributes only differs by a maximum of one entry. for (size_t i = 0; i < size; ++i) ASSERT(m_layoutAttributes.find(newLayoutAttributes[i]) != notFound || newLayoutAttributes[i] == attributes); #endif m_layoutAttributes = newLayoutAttributes; }
static inline void findFirstAndLastAttributesInVector(Vector<SVGTextLayoutAttributes*>& attributes, RenderSVGInlineText* firstContext, RenderSVGInlineText* lastContext, SVGTextLayoutAttributes*& first, SVGTextLayoutAttributes*& last) { first = 0; last = 0; unsigned attributesSize = attributes.size(); for (unsigned i = 0; i < attributesSize; ++i) { SVGTextLayoutAttributes* current = attributes[i]; if (!first && firstContext == ¤t->context()) first = current; if (!last && lastContext == ¤t->context()) last = current; if (first && last) break; } ASSERT(first); ASSERT(last); }
void SVGTextLayoutAttributesBuilder::assignLayoutAttributesForCharacter(SVGTextLayoutAttributes& attributes, SVGTextMetrics& metrics, unsigned valueListPosition) const { attributes.xValues().append(nextLayoutValue(XValueAttribute, valueListPosition)); attributes.yValues().append(nextLayoutValue(YValueAttribute, valueListPosition)); attributes.dxValues().append(nextLayoutValue(DxValueAttribute, valueListPosition)); attributes.dyValues().append(nextLayoutValue(DyValueAttribute, valueListPosition)); attributes.rotateValues().append(nextLayoutValue(RotateValueAttribute, valueListPosition)); attributes.textMetricsValues().append(metrics); }
void RenderSVGText::rebuildLayoutAttributes(Vector<SVGTextLayoutAttributes*>& affectedAttributes) { // Detect changes in layout attributes and only measure those text parts that have changed! Vector<SVGTextLayoutAttributes*> newLayoutAttributes; recursiveCollectLayoutAttributes(this, newLayoutAttributes); if (newLayoutAttributes.isEmpty()) { m_layoutAttributes.clear(); return; } // Compare m_layoutAttributes with newLayoutAttributes to figure out which attributes got added/removed. size_t size = newLayoutAttributes.size(); for (size_t i = 0; i < size; ++i) { SVGTextLayoutAttributes* attributes = newLayoutAttributes[i]; if (m_layoutAttributes.find(attributes) == notFound) m_layoutAttributesBuilder.rebuildMetricsForTextRenderer(attributes->context()); } size = affectedAttributes.size(); for (size_t i = 0; i < size; ++i) m_layoutAttributesBuilder.rebuildMetricsForTextRenderer(affectedAttributes[i]->context()); m_layoutAttributes = newLayoutAttributes; }
bool SVGTextLayoutEngine::currentLogicalCharacterAttributes(SVGTextLayoutAttributes& logicalAttributes) { if (m_layoutAttributes.isEmpty()) return false; logicalAttributes = m_layoutAttributes.first(); if (m_logicalCharacterOffset != logicalAttributes.xValues().size()) return true; m_layoutAttributes.remove(0); if (m_layoutAttributes.isEmpty()) return false; logicalAttributes = m_layoutAttributes.first(); m_logicalMetricsListOffset = 0; m_logicalCharacterOffset = 0; return true; }
void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, LineLayoutSVGInlineText textLineLayout, const ComputedStyle& style) { if (m_inPathLayout && !m_textPathCalculator) return; // Find the start of the current text box in the metrics list. m_visualMetricsIterator.advanceToTextStart(&textLineLayout, 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, textLineLayout); // 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. // Font::width() calculates the resolved FontOrientation for each character, // but is not exposed today to avoid the API complexity. UChar32 currentCharacter = textLineLayout.codepointAt(m_visualMetricsIterator.characterOffset()); FontOrientation fontOrientation = font.fontDescription().orientation(); fontOrientation = adjustOrientationForCharacterInMixedVertical(fontOrientation, currentCharacter); // Calculate glyph advance. // Shaping engine takes care of x/y orientation shifts for different fontOrientation values. float glyphAdvance = visualMetrics.advance(fontOrientation); // 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; float textPathShiftX = 0; float textPathShiftY = 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. textPathShiftX += m_dx + baselineShift; textPathShiftY -= 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. textPathShiftX -= scaledGlyphAdvance / 2; textPathShiftY += 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 || 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 (textPathShiftX || textPathShiftY) m_currentTextFragment.transform.translate(textPathShiftX, textPathShiftY); // In vertical text, always rotate by 90 degrees regardless of fontOrientation. // Shaping engine takes care of the necessary orientation. if (m_isVerticalText) m_currentTextFragment.transform.rotate(90); 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); }
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); }
void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, int& startPosition, int& endPosition) const { SVGTextLayoutAttributes* layoutAttributes = queryData->textRenderer->layoutAttributes(); Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes->textMetricsValues(); unsigned boxStart = queryData->textBox->start(); unsigned boxLength = queryData->textBox->len(); unsigned textMetricsOffset = 0; unsigned textMetricsSize = textMetricsValues.size(); unsigned positionOffset = 0; unsigned positionSize = layoutAttributes->context()->textLength(); bool alterStartPosition = true; bool alterEndPosition = true; int lastPositionOffset = -1; for (; textMetricsOffset < textMetricsSize && positionOffset < positionSize; ++textMetricsOffset) { SVGTextMetrics& metrics = textMetricsValues[textMetricsOffset]; // Advance to text box start location. if (positionOffset < boxStart) { positionOffset += metrics.length(); continue; } // Stop if we've finished processing this text box. if (positionOffset >= boxStart + boxLength) break; // If the start position maps to a character in the metrics list, we don't need to modify it. if (startPosition == static_cast<int>(positionOffset)) alterStartPosition = false; // If the start position maps to a character in the metrics list, we don't need to modify it. if (endPosition == static_cast<int>(positionOffset)) alterEndPosition = false; // Detect ligatures. if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) { if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) { startPosition = lastPositionOffset; alterStartPosition = false; } if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) { endPosition = positionOffset; alterEndPosition = false; } } if (!alterStartPosition && !alterEndPosition) break; lastPositionOffset = positionOffset; positionOffset += metrics.length(); } if (!alterStartPosition && !alterEndPosition) return; if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) { if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) { startPosition = lastPositionOffset; alterStartPosition = false; } if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) { endPosition = positionOffset; alterEndPosition = false; } } }