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