| 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 | |