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