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