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 | |
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_textPathLength(0) |
51 | , m_textPathCurrentOffset(0) |
52 | , m_textPathSpacing(0) |
53 | , m_textPathScaling(1) |
54 | { |
55 | ASSERT(!m_layoutAttributes.isEmpty()); |
56 | } |
57 | |
58 | void 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 | |
75 | void 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 | |
87 | void 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 | |
114 | void 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 | |
145 | bool 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 | |
165 | void 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 | |
203 | void 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 | |
214 | void 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 |
236 | static 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 | |
261 | void 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 | |
285 | void 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 | |
311 | bool 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 | |
332 | bool 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 | |
362 | bool 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 | |
390 | void SVGTextLayoutEngine::advanceToNextLogicalCharacter(const SVGTextMetrics& logicalMetrics) |
391 | { |
392 | ++m_logicalMetricsListOffset; |
393 | m_logicalCharacterOffset += logicalMetrics.length(); |
394 | } |
395 | |
396 | void SVGTextLayoutEngine::advanceToNextVisualCharacter(const SVGTextMetrics& visualMetrics) |
397 | { |
398 | ++m_visualMetricsListOffset; |
399 | m_visualCharacterOffset += visualMetrics.length(); |
400 | } |
401 | |
402 | void 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 | |