Home | History | Annotate | Download | only in svg
      1 /*
      2  * Copyright (C) Research In Motion Limited 2010-2012. All rights reserved.
      3  *
      4  * This library is free software; you can redistribute it and/or
      5  * modify it under the terms of the GNU Library General Public
      6  * License as published by the Free Software Foundation; either
      7  * version 2 of the License, or (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * Library General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Library General Public License
     15  * along with this library; see the file COPYING.LIB.  If not, write to
     16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     17  * Boston, MA 02110-1301, USA.
     18  */
     19 
     20 #include "config.h"
     21 
     22 #include "core/rendering/svg/SVGTextLayoutEngine.h"
     23 
     24 #include "core/rendering/svg/RenderSVGInlineText.h"
     25 #include "core/rendering/svg/RenderSVGTextPath.h"
     26 #include "core/rendering/svg/SVGInlineTextBox.h"
     27 #include "core/rendering/svg/SVGTextLayoutEngineBaseline.h"
     28 #include "core/rendering/svg/SVGTextLayoutEngineSpacing.h"
     29 #include "core/svg/SVGElement.h"
     30 #include "core/svg/SVGLengthContext.h"
     31 
     32 // Set to a value > 0 to dump the text fragments
     33 #define DUMP_TEXT_FRAGMENTS 0
     34 
     35 namespace WebCore {
     36 
     37 SVGTextLayoutEngine::SVGTextLayoutEngine(Vector<SVGTextLayoutAttributes*>& layoutAttributes)
     38     : m_layoutAttributes(layoutAttributes)
     39     , m_layoutAttributesPosition(0)
     40     , m_logicalCharacterOffset(0)
     41     , m_logicalMetricsListOffset(0)
     42     , m_visualCharacterOffset(0)
     43     , m_visualMetricsListOffset(0)
     44     , m_x(0)
     45     , m_y(0)
     46     , m_dx(0)
     47     , m_dy(0)
     48     , m_isVerticalText(false)
     49     , m_inPathLayout(false)
     50     , m_textPathCalculator(0)
     51     , m_textPathLength(0)
     52     , m_textPathCurrentOffset(0)
     53     , m_textPathSpacing(0)
     54     , m_textPathScaling(1)
     55 {
     56     ASSERT(!m_layoutAttributes.isEmpty());
     57 }
     58 
     59 void SVGTextLayoutEngine::updateCharacerPositionIfNeeded(float& x, float& y)
     60 {
     61     if (m_inPathLayout)
     62         return;
     63 
     64     // Replace characters x/y position, with the current text position plus any
     65     // relative adjustments, if it doesn't specify an absolute position itself.
     66     if (x == SVGTextLayoutAttributes::emptyValue())
     67         x = m_x + m_dx;
     68 
     69     if (y == SVGTextLayoutAttributes::emptyValue())
     70         y = m_y + m_dy;
     71 
     72     m_dx = 0;
     73     m_dy = 0;
     74 }
     75 
     76 void SVGTextLayoutEngine::updateCurrentTextPosition(float x, float y, float glyphAdvance)
     77 {
     78     // Update current text position after processing the character.
     79     if (m_isVerticalText) {
     80         m_x = x;
     81         m_y = y + glyphAdvance;
     82     } else {
     83         m_x = x + glyphAdvance;
     84         m_y = y;
     85     }
     86 }
     87 
     88 void SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded(float dx, float dy)
     89 {
     90     // Update relative positioning information.
     91     if (dx == SVGTextLayoutAttributes::emptyValue() && dy == SVGTextLayoutAttributes::emptyValue())
     92         return;
     93 
     94     if (dx == SVGTextLayoutAttributes::emptyValue())
     95         dx = 0;
     96     if (dy == SVGTextLayoutAttributes::emptyValue())
     97         dy = 0;
     98 
     99     if (m_inPathLayout) {
    100         if (m_isVerticalText) {
    101             m_dx += dx;
    102             m_dy = dy;
    103         } else {
    104             m_dx = dx;
    105             m_dy += dy;
    106         }
    107 
    108         return;
    109     }
    110 
    111     m_dx = dx;
    112     m_dy = dy;
    113 }
    114 
    115 void SVGTextLayoutEngine::recordTextFragment(SVGInlineTextBox* textBox, Vector<SVGTextMetrics>& textMetricsValues)
    116 {
    117     ASSERT(!m_currentTextFragment.length);
    118     ASSERT(m_visualMetricsListOffset > 0);
    119 
    120     // Figure out length of fragment.
    121     m_currentTextFragment.length = m_visualCharacterOffset - m_currentTextFragment.characterOffset;
    122 
    123     // Figure out fragment metrics.
    124     SVGTextMetrics& lastCharacterMetrics = textMetricsValues.at(m_visualMetricsListOffset - 1);
    125     m_currentTextFragment.width = lastCharacterMetrics.width();
    126     m_currentTextFragment.height = lastCharacterMetrics.height();
    127 
    128     if (m_currentTextFragment.length > 1) {
    129         // SVGTextLayoutAttributesBuilder assures that the length of the range is equal to the sum of the individual lengths of the glyphs.
    130         float length = 0;
    131         if (m_isVerticalText) {
    132             for (unsigned i = m_currentTextFragment.metricsListOffset; i < m_visualMetricsListOffset; ++i)
    133                 length += textMetricsValues.at(i).height();
    134             m_currentTextFragment.height = length;
    135         } else {
    136             for (unsigned i = m_currentTextFragment.metricsListOffset; i < m_visualMetricsListOffset; ++i)
    137                 length += textMetricsValues.at(i).width();
    138             m_currentTextFragment.width = length;
    139         }
    140     }
    141 
    142     textBox->textFragments().append(m_currentTextFragment);
    143     m_currentTextFragment = SVGTextFragment();
    144 }
    145 
    146 bool SVGTextLayoutEngine::parentDefinesTextLength(RenderObject* parent) const
    147 {
    148     RenderObject* currentParent = parent;
    149     while (currentParent) {
    150         if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(currentParent)) {
    151             SVGLengthContext lengthContext(textContentElement);
    152             if (textContentElement->lengthAdjust()->currentValue()->enumValue() == SVGLengthAdjustSpacing && textContentElement->textLengthIsSpecifiedByUser())
    153                 return true;
    154         }
    155 
    156         if (currentParent->isSVGText())
    157             return false;
    158 
    159         currentParent = currentParent->parent();
    160     }
    161 
    162     ASSERT_NOT_REACHED();
    163     return false;
    164 }
    165 
    166 void SVGTextLayoutEngine::beginTextPathLayout(RenderObject* object, SVGTextLayoutEngine& lineLayout)
    167 {
    168     ASSERT(object);
    169 
    170     m_inPathLayout = true;
    171     RenderSVGTextPath* textPath = toRenderSVGTextPath(object);
    172 
    173     Path path = textPath->layoutPath();
    174     if (path.isEmpty())
    175         return;
    176     m_textPathCalculator = new Path::PositionCalculator(path);
    177     m_textPathStartOffset = textPath->startOffset();
    178     m_textPathLength = path.length();
    179     if (m_textPathStartOffset > 0 && m_textPathStartOffset <= 1)
    180         m_textPathStartOffset *= m_textPathLength;
    181 
    182     float totalLength = 0;
    183     unsigned totalCharacters = 0;
    184 
    185     lineLayout.m_chunkLayoutBuilder.buildTextChunks(lineLayout.m_lineLayoutBoxes);
    186     const Vector<SVGTextChunk>& textChunks = lineLayout.m_chunkLayoutBuilder.textChunks();
    187 
    188     unsigned size = textChunks.size();
    189     for (unsigned i = 0; i < size; ++i) {
    190         const SVGTextChunk& chunk = textChunks.at(i);
    191 
    192         float length = 0;
    193         unsigned characters = 0;
    194         chunk.calculateLength(length, characters);
    195 
    196         // Handle text-anchor as additional start offset for text paths.
    197         m_textPathStartOffset += chunk.calculateTextAnchorShift(length);
    198 
    199         totalLength += length;
    200         totalCharacters += characters;
    201     }
    202 
    203     m_textPathCurrentOffset = m_textPathStartOffset;
    204 
    205     // Eventually handle textLength adjustments.
    206     SVGLengthAdjustType lengthAdjust = SVGLengthAdjustUnknown;
    207     float desiredTextLength = 0;
    208 
    209     if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textPath)) {
    210         SVGLengthContext lengthContext(textContentElement);
    211         lengthAdjust = textContentElement->lengthAdjust()->currentValue()->enumValue();
    212         if (textContentElement->textLengthIsSpecifiedByUser())
    213             desiredTextLength = textContentElement->textLength()->currentValue()->value(lengthContext);
    214         else
    215             desiredTextLength = 0;
    216     }
    217 
    218     if (!desiredTextLength)
    219         return;
    220 
    221     if (lengthAdjust == SVGLengthAdjustSpacing)
    222         m_textPathSpacing = (desiredTextLength - totalLength) / totalCharacters;
    223     else
    224         m_textPathScaling = desiredTextLength / totalLength;
    225 }
    226 
    227 void SVGTextLayoutEngine::endTextPathLayout()
    228 {
    229     m_inPathLayout = false;
    230     delete m_textPathCalculator;
    231     m_textPathCalculator = 0;
    232     m_textPathLength = 0;
    233     m_textPathStartOffset = 0;
    234     m_textPathCurrentOffset = 0;
    235     m_textPathSpacing = 0;
    236     m_textPathScaling = 1;
    237 }
    238 
    239 void SVGTextLayoutEngine::layoutInlineTextBox(SVGInlineTextBox* textBox)
    240 {
    241     ASSERT(textBox);
    242 
    243     RenderSVGInlineText& text = toRenderSVGInlineText(textBox->textRenderer());
    244     ASSERT(text.parent());
    245     ASSERT(text.parent()->node());
    246     ASSERT(text.parent()->node()->isSVGElement());
    247 
    248     const RenderStyle* style = text.style();
    249     ASSERT(style);
    250 
    251     textBox->clearTextFragments();
    252     m_isVerticalText = style->svgStyle()->isVerticalWritingMode();
    253     layoutTextOnLineOrPath(textBox, &text, style);
    254 
    255     if (m_inPathLayout) {
    256         m_pathLayoutBoxes.append(textBox);
    257         return;
    258     }
    259 
    260     m_lineLayoutBoxes.append(textBox);
    261 }
    262 
    263 #if DUMP_TEXT_FRAGMENTS > 0
    264 static inline void dumpTextBoxes(Vector<SVGInlineTextBox*>& boxes)
    265 {
    266     unsigned boxCount = boxes.size();
    267     fprintf(stderr, "Dumping all text fragments in text sub tree, %i boxes\n", boxCount);
    268 
    269     for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    270         SVGInlineTextBox* textBox = boxes.at(boxPosition);
    271         Vector<SVGTextFragment>& fragments = textBox->textFragments();
    272         fprintf(stderr, "-> Box %i: Dumping text fragments for SVGInlineTextBox, textBox=%p, textRenderer=%p\n", boxPosition, textBox, textBox->textRenderer());
    273         fprintf(stderr, "        textBox properties, start=%i, len=%i, box direction=%i\n", textBox->start(), textBox->len(), textBox->direction());
    274         fprintf(stderr, "   textRenderer properties, textLength=%i\n", textBox->textRenderer()->textLength());
    275 
    276         String characters = textBox->textRenderer()->text();
    277 
    278         unsigned fragmentCount = fragments.size();
    279         for (unsigned i = 0; i < fragmentCount; ++i) {
    280             SVGTextFragment& fragment = fragments.at(i);
    281             String fragmentString = characters.substring(fragment.characterOffset, fragment.length);
    282             fprintf(stderr, "    -> Fragment %i, x=%lf, y=%lf, width=%lf, height=%lf, characterOffset=%i, length=%i, characters='%s'\n"
    283                           , i, fragment.x, fragment.y, fragment.width, fragment.height, fragment.characterOffset, fragment.length, fragmentString.utf8().data());
    284         }
    285     }
    286 }
    287 #endif
    288 
    289 void SVGTextLayoutEngine::finalizeTransformMatrices(Vector<SVGInlineTextBox*>& boxes)
    290 {
    291     unsigned boxCount = boxes.size();
    292     if (!boxCount)
    293         return;
    294 
    295     AffineTransform textBoxTransformation;
    296     for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    297         SVGInlineTextBox* textBox = boxes.at(boxPosition);
    298         Vector<SVGTextFragment>& fragments = textBox->textFragments();
    299 
    300         unsigned fragmentCount = fragments.size();
    301         for (unsigned i = 0; i < fragmentCount; ++i) {
    302             m_chunkLayoutBuilder.transformationForTextBox(textBox, textBoxTransformation);
    303             if (textBoxTransformation.isIdentity())
    304                 continue;
    305             ASSERT(fragments[i].lengthAdjustTransform.isIdentity());
    306             fragments[i].lengthAdjustTransform = textBoxTransformation;
    307         }
    308     }
    309 
    310     boxes.clear();
    311 }
    312 
    313 void SVGTextLayoutEngine::finishLayout()
    314 {
    315     // After all text fragments are stored in their correpsonding SVGInlineTextBoxes, we can layout individual text chunks.
    316     // Chunk layouting is only performed for line layout boxes, not for path layout, where it has already been done.
    317     m_chunkLayoutBuilder.layoutTextChunks(m_lineLayoutBoxes);
    318 
    319     // Finalize transform matrices, after the chunk layout corrections have been applied, and all fragment x/y positions are finalized.
    320     if (!m_lineLayoutBoxes.isEmpty()) {
    321 #if DUMP_TEXT_FRAGMENTS > 0
    322         fprintf(stderr, "Line layout: ");
    323         dumpTextBoxes(m_lineLayoutBoxes);
    324 #endif
    325 
    326         finalizeTransformMatrices(m_lineLayoutBoxes);
    327     }
    328 
    329     if (!m_pathLayoutBoxes.isEmpty()) {
    330 #if DUMP_TEXT_FRAGMENTS > 0
    331         fprintf(stderr, "Path layout: ");
    332         dumpTextBoxes(m_pathLayoutBoxes);
    333 #endif
    334 
    335         finalizeTransformMatrices(m_pathLayoutBoxes);
    336     }
    337 }
    338 
    339 bool SVGTextLayoutEngine::currentLogicalCharacterAttributes(SVGTextLayoutAttributes*& logicalAttributes)
    340 {
    341     if (m_layoutAttributesPosition == m_layoutAttributes.size())
    342         return false;
    343 
    344     logicalAttributes = m_layoutAttributes[m_layoutAttributesPosition];
    345     ASSERT(logicalAttributes);
    346 
    347     if (m_logicalCharacterOffset != logicalAttributes->context()->textLength())
    348         return true;
    349 
    350     ++m_layoutAttributesPosition;
    351     if (m_layoutAttributesPosition == m_layoutAttributes.size())
    352         return false;
    353 
    354     logicalAttributes = m_layoutAttributes[m_layoutAttributesPosition];
    355     m_logicalMetricsListOffset = 0;
    356     m_logicalCharacterOffset = 0;
    357     return true;
    358 }
    359 
    360 bool SVGTextLayoutEngine::currentLogicalCharacterMetrics(SVGTextLayoutAttributes*& logicalAttributes, SVGTextMetrics& logicalMetrics)
    361 {
    362     Vector<SVGTextMetrics>* textMetricsValues = &logicalAttributes->textMetricsValues();
    363     unsigned textMetricsSize = textMetricsValues->size();
    364     while (true) {
    365         if (m_logicalMetricsListOffset == textMetricsSize) {
    366             if (!currentLogicalCharacterAttributes(logicalAttributes))
    367                 return false;
    368 
    369             textMetricsValues = &logicalAttributes->textMetricsValues();
    370             textMetricsSize = textMetricsValues->size();
    371             continue;
    372         }
    373 
    374         ASSERT(textMetricsSize);
    375         ASSERT(m_logicalMetricsListOffset < textMetricsSize);
    376         logicalMetrics = textMetricsValues->at(m_logicalMetricsListOffset);
    377         if (logicalMetrics.isEmpty() || (!logicalMetrics.width() && !logicalMetrics.height())) {
    378             advanceToNextLogicalCharacter(logicalMetrics);
    379             continue;
    380         }
    381 
    382         // Stop if we found the next valid logical text metrics object.
    383         return true;
    384     }
    385 
    386     ASSERT_NOT_REACHED();
    387     return true;
    388 }
    389 
    390 bool SVGTextLayoutEngine::currentVisualCharacterMetrics(SVGInlineTextBox* textBox, Vector<SVGTextMetrics>& visualMetricsValues, SVGTextMetrics& visualMetrics)
    391 {
    392     ASSERT(!visualMetricsValues.isEmpty());
    393     unsigned textMetricsSize = visualMetricsValues.size();
    394     unsigned boxStart = textBox->start();
    395     unsigned boxLength = textBox->len();
    396 
    397     while (m_visualMetricsListOffset < textMetricsSize) {
    398         // Advance to text box start location.
    399         if (m_visualCharacterOffset < boxStart) {
    400             advanceToNextVisualCharacter(visualMetricsValues[m_visualMetricsListOffset]);
    401             continue;
    402         }
    403 
    404         // Stop if we've finished processing this text box.
    405         if (m_visualCharacterOffset >= boxStart + boxLength)
    406             return false;
    407 
    408         visualMetrics = visualMetricsValues[m_visualMetricsListOffset];
    409         return true;
    410     }
    411 
    412     return false;
    413 }
    414 
    415 void SVGTextLayoutEngine::advanceToNextLogicalCharacter(const SVGTextMetrics& logicalMetrics)
    416 {
    417     ++m_logicalMetricsListOffset;
    418     m_logicalCharacterOffset += logicalMetrics.length();
    419 }
    420 
    421 void SVGTextLayoutEngine::advanceToNextVisualCharacter(const SVGTextMetrics& visualMetrics)
    422 {
    423     ++m_visualMetricsListOffset;
    424     m_visualCharacterOffset += visualMetrics.length();
    425 }
    426 
    427 void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, RenderSVGInlineText* text, const RenderStyle* style)
    428 {
    429     if (m_inPathLayout && !m_textPathCalculator)
    430         return;
    431 
    432     SVGElement* lengthContext = toSVGElement(text->parent()->node());
    433 
    434     RenderObject* textParent = text->parent();
    435     bool definesTextLength = textParent ? parentDefinesTextLength(textParent) : false;
    436 
    437     const SVGRenderStyle* svgStyle = style->svgStyle();
    438     ASSERT(svgStyle);
    439 
    440     m_visualMetricsListOffset = 0;
    441     m_visualCharacterOffset = 0;
    442 
    443     Vector<SVGTextMetrics>& visualMetricsValues = text->layoutAttributes()->textMetricsValues();
    444     ASSERT(!visualMetricsValues.isEmpty());
    445 
    446     const Font& font = style->font();
    447 
    448     SVGTextLayoutEngineSpacing spacingLayout(font, style->effectiveZoom());
    449     SVGTextLayoutEngineBaseline baselineLayout(font);
    450 
    451     bool didStartTextFragment = false;
    452     bool applySpacingToNextCharacter = false;
    453 
    454     float lastAngle = 0;
    455     float baselineShift = baselineLayout.calculateBaselineShift(svgStyle, lengthContext);
    456     baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, text);
    457 
    458     // Main layout algorithm.
    459     while (true) {
    460         // Find the start of the current text box in this list, respecting ligatures.
    461         SVGTextMetrics visualMetrics(SVGTextMetrics::SkippedSpaceMetrics);
    462         if (!currentVisualCharacterMetrics(textBox, visualMetricsValues, visualMetrics))
    463             break;
    464 
    465         if (visualMetrics.isEmpty()) {
    466             advanceToNextVisualCharacter(visualMetrics);
    467             continue;
    468         }
    469 
    470         SVGTextLayoutAttributes* logicalAttributes = 0;
    471         if (!currentLogicalCharacterAttributes(logicalAttributes))
    472             break;
    473 
    474         ASSERT(logicalAttributes);
    475         SVGTextMetrics logicalMetrics(SVGTextMetrics::SkippedSpaceMetrics);
    476         if (!currentLogicalCharacterMetrics(logicalAttributes, logicalMetrics))
    477             break;
    478 
    479         SVGCharacterDataMap& characterDataMap = logicalAttributes->characterDataMap();
    480         SVGCharacterData data;
    481         SVGCharacterDataMap::iterator it = characterDataMap.find(m_logicalCharacterOffset + 1);
    482         if (it != characterDataMap.end())
    483             data = it->value;
    484 
    485         float x = data.x;
    486         float y = data.y;
    487 
    488         // When we've advanced to the box start offset, determine using the original x/y values,
    489         // whether this character starts a new text chunk, before doing any further processing.
    490         if (m_visualCharacterOffset == textBox->start())
    491             textBox->setStartsNewTextChunk(logicalAttributes->context()->characterStartsNewTextChunk(m_logicalCharacterOffset));
    492 
    493         float angle = data.rotate == SVGTextLayoutAttributes::emptyValue() ? 0 : data.rotate;
    494 
    495         // Calculate glyph orientation angle.
    496         UChar currentCharacter = text->characterAt(m_visualCharacterOffset);
    497         float orientationAngle = baselineLayout.calculateGlyphOrientationAngle(m_isVerticalText, svgStyle, currentCharacter);
    498 
    499         // Calculate glyph advance & x/y orientation shifts.
    500         float xOrientationShift = 0;
    501         float yOrientationShift = 0;
    502         float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, visualMetrics, orientationAngle, xOrientationShift, yOrientationShift);
    503 
    504         // Assign current text position to x/y values, if needed.
    505         updateCharacerPositionIfNeeded(x, y);
    506 
    507         // Apply dx/dy value adjustments to current text position, if needed.
    508         updateRelativePositionAdjustmentsIfNeeded(data.dx, data.dy);
    509 
    510         // Calculate SVG Fonts kerning, if needed.
    511         float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, visualMetrics.glyph());
    512 
    513         // Calculate CSS 'letter-spacing' and 'word-spacing' for next character, if needed.
    514         float spacing = spacingLayout.calculateCSSSpacing(currentCharacter);
    515 
    516         float textPathOffset = 0;
    517         if (m_inPathLayout) {
    518             float scaledGlyphAdvance = glyphAdvance * m_textPathScaling;
    519             if (m_isVerticalText) {
    520                 // If there's an absolute y position available, it marks the beginning of a new position along the path.
    521                 if (y != SVGTextLayoutAttributes::emptyValue())
    522                     m_textPathCurrentOffset = y + m_textPathStartOffset;
    523 
    524                 m_textPathCurrentOffset += m_dy - kerning;
    525                 m_dy = 0;
    526 
    527                 // Apply dx/dy correction and setup translations that move to the glyph midpoint.
    528                 xOrientationShift += m_dx + baselineShift;
    529                 yOrientationShift -= scaledGlyphAdvance / 2;
    530             } else {
    531                 // If there's an absolute x position available, it marks the beginning of a new position along the path.
    532                 if (x != SVGTextLayoutAttributes::emptyValue())
    533                     m_textPathCurrentOffset = x + m_textPathStartOffset;
    534 
    535                 m_textPathCurrentOffset += m_dx - kerning;
    536                 m_dx = 0;
    537 
    538                 // Apply dx/dy correction and setup translations that move to the glyph midpoint.
    539                 xOrientationShift -= scaledGlyphAdvance / 2;
    540                 yOrientationShift += m_dy - baselineShift;
    541             }
    542 
    543             // Calculate current offset along path.
    544             textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2;
    545 
    546             // Move to next character.
    547             m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling;
    548 
    549             // Skip character, if we're before the path.
    550             if (textPathOffset < 0) {
    551                 advanceToNextLogicalCharacter(logicalMetrics);
    552                 advanceToNextVisualCharacter(visualMetrics);
    553                 continue;
    554             }
    555 
    556             // Stop processing, if the next character lies behind the path.
    557             if (textPathOffset > m_textPathLength)
    558                 break;
    559 
    560             FloatPoint point;
    561             bool ok = m_textPathCalculator->pointAndNormalAtLength(textPathOffset, point, angle);
    562             ASSERT_UNUSED(ok, ok);
    563             x = point.x();
    564             y = point.y();
    565 
    566             // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle!
    567             if (m_isVerticalText)
    568                 angle -= 90;
    569         } else {
    570             // Apply all previously calculated shift values.
    571             if (m_isVerticalText) {
    572                 x += baselineShift;
    573                 y -= kerning;
    574             } else {
    575                 x -= kerning;
    576                 y -= baselineShift;
    577             }
    578 
    579             x += m_dx;
    580             y += m_dy;
    581         }
    582 
    583         // Determine whether we have to start a new fragment.
    584         bool shouldStartNewFragment = m_dx || m_dy || m_isVerticalText || m_inPathLayout || angle || angle != lastAngle
    585             || orientationAngle || kerning || applySpacingToNextCharacter || definesTextLength;
    586 
    587         // If we already started a fragment, close it now.
    588         if (didStartTextFragment && shouldStartNewFragment) {
    589             applySpacingToNextCharacter = false;
    590             recordTextFragment(textBox, visualMetricsValues);
    591         }
    592 
    593         // Eventually start a new fragment, if not yet done.
    594         if (!didStartTextFragment || shouldStartNewFragment) {
    595             ASSERT(!m_currentTextFragment.characterOffset);
    596             ASSERT(!m_currentTextFragment.length);
    597 
    598             didStartTextFragment = true;
    599             m_currentTextFragment.characterOffset = m_visualCharacterOffset;
    600             m_currentTextFragment.metricsListOffset = m_visualMetricsListOffset;
    601             m_currentTextFragment.x = x;
    602             m_currentTextFragment.y = y;
    603 
    604             // Build fragment transformation.
    605             if (angle)
    606                 m_currentTextFragment.transform.rotate(angle);
    607 
    608             if (xOrientationShift || yOrientationShift)
    609                 m_currentTextFragment.transform.translate(xOrientationShift, yOrientationShift);
    610 
    611             if (orientationAngle)
    612                 m_currentTextFragment.transform.rotate(orientationAngle);
    613 
    614             m_currentTextFragment.isTextOnPath = m_inPathLayout && m_textPathScaling != 1;
    615             if (m_currentTextFragment.isTextOnPath) {
    616                 if (m_isVerticalText)
    617                     m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(1, m_textPathScaling);
    618                 else
    619                     m_currentTextFragment.lengthAdjustTransform.scaleNonUniform(m_textPathScaling, 1);
    620             }
    621         }
    622 
    623         // Update current text position, after processing of the current character finished.
    624         if (m_inPathLayout)
    625             updateCurrentTextPosition(x, y, glyphAdvance);
    626         else {
    627             // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed.
    628             if (spacing)
    629                 applySpacingToNextCharacter = true;
    630 
    631             float xNew = x - m_dx;
    632             float yNew = y - m_dy;
    633 
    634             if (m_isVerticalText)
    635                 xNew -= baselineShift;
    636             else
    637                 yNew += baselineShift;
    638 
    639             updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing);
    640         }
    641 
    642         advanceToNextLogicalCharacter(logicalMetrics);
    643         advanceToNextVisualCharacter(visualMetrics);
    644         lastAngle = angle;
    645     }
    646 
    647     if (!didStartTextFragment)
    648         return;
    649 
    650     // Close last open fragment, if needed.
    651     recordTextFragment(textBox, visualMetricsValues);
    652 }
    653 
    654 }
    655