1 /* 2 * This file is part of the WebKit project. 3 * 4 * Copyright (C) 2006 Oliver Hunt <ojh16 (at) student.canterbury.ac.nz> 5 * (C) 2006 Apple Computer Inc. 6 * (C) 2007 Nikolas Zimmermann <zimmermann (at) kde.org> 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 * 23 */ 24 25 #include "config.h" 26 27 #if ENABLE(SVG) 28 #include "SVGRootInlineBox.h" 29 30 #include "Editor.h" 31 #include "Frame.h" 32 #include "GraphicsContext.h" 33 #include "RenderBlock.h" 34 #include "RenderSVGRoot.h" 35 #include "SVGInlineFlowBox.h" 36 #include "SVGInlineTextBox.h" 37 #include "SVGFontElement.h" 38 #include "SVGPaintServer.h" 39 #include "SVGRenderStyleDefs.h" 40 #include "SVGRenderSupport.h" 41 #include "SVGResourceFilter.h" 42 #include "SVGTextPositioningElement.h" 43 #include "SVGURIReference.h" 44 #include "Text.h" 45 #include "UnicodeRange.h" 46 47 #include <float.h> 48 49 // Text chunk creation is complex and the whole process 50 // can easily be traced by setting this variable > 0. 51 #define DEBUG_CHUNK_BUILDING 0 52 53 namespace WebCore { 54 55 static inline bool isVerticalWritingMode(const SVGRenderStyle* style) 56 { 57 return style->writingMode() == WM_TBRL || style->writingMode() == WM_TB; 58 } 59 60 static inline EAlignmentBaseline dominantBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font) 61 { 62 ASSERT(text); 63 64 const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0; 65 ASSERT(style); 66 67 const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0; 68 69 EDominantBaseline baseline = style->dominantBaseline(); 70 if (baseline == DB_AUTO) { 71 if (isVerticalText) 72 baseline = DB_CENTRAL; 73 else 74 baseline = DB_ALPHABETIC; 75 } 76 77 switch (baseline) { 78 case DB_USE_SCRIPT: 79 // TODO: The dominant-baseline and the baseline-table components are set by 80 // determining the predominant script of the character data content. 81 return AB_ALPHABETIC; 82 case DB_NO_CHANGE: 83 { 84 if (parentStyle) 85 return dominantBaselineToShift(isVerticalText, text->parent(), font); 86 87 ASSERT_NOT_REACHED(); 88 return AB_AUTO; 89 } 90 case DB_RESET_SIZE: 91 { 92 if (parentStyle) 93 return dominantBaselineToShift(isVerticalText, text->parent(), font); 94 95 ASSERT_NOT_REACHED(); 96 return AB_AUTO; 97 } 98 case DB_IDEOGRAPHIC: 99 return AB_IDEOGRAPHIC; 100 case DB_ALPHABETIC: 101 return AB_ALPHABETIC; 102 case DB_HANGING: 103 return AB_HANGING; 104 case DB_MATHEMATICAL: 105 return AB_MATHEMATICAL; 106 case DB_CENTRAL: 107 return AB_CENTRAL; 108 case DB_MIDDLE: 109 return AB_MIDDLE; 110 case DB_TEXT_AFTER_EDGE: 111 return AB_TEXT_AFTER_EDGE; 112 case DB_TEXT_BEFORE_EDGE: 113 return AB_TEXT_BEFORE_EDGE; 114 default: 115 ASSERT_NOT_REACHED(); 116 return AB_AUTO; 117 } 118 } 119 120 static inline float alignmentBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font) 121 { 122 ASSERT(text); 123 124 const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0; 125 ASSERT(style); 126 127 const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0; 128 129 EAlignmentBaseline baseline = style->alignmentBaseline(); 130 if (baseline == AB_AUTO) { 131 if (parentStyle && style->dominantBaseline() == DB_AUTO) 132 baseline = dominantBaselineToShift(isVerticalText, text->parent(), font); 133 else 134 baseline = dominantBaselineToShift(isVerticalText, text, font); 135 136 ASSERT(baseline != AB_AUTO); 137 } 138 139 // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling 140 switch (baseline) { 141 case AB_BASELINE: 142 { 143 if (parentStyle) 144 return dominantBaselineToShift(isVerticalText, text->parent(), font); 145 146 return 0.0f; 147 } 148 case AB_BEFORE_EDGE: 149 case AB_TEXT_BEFORE_EDGE: 150 return font.ascent(); 151 case AB_MIDDLE: 152 return font.xHeight() / 2.0f; 153 case AB_CENTRAL: 154 // Not needed, we're taking this into account already for vertical text! 155 // return (font.ascent() - font.descent()) / 2.0f; 156 return 0.0f; 157 case AB_AFTER_EDGE: 158 case AB_TEXT_AFTER_EDGE: 159 case AB_IDEOGRAPHIC: 160 return font.descent(); 161 case AB_ALPHABETIC: 162 return 0.0f; 163 case AB_HANGING: 164 return font.ascent() * 8.0f / 10.0f; 165 case AB_MATHEMATICAL: 166 return font.ascent() / 2.0f; 167 default: 168 ASSERT_NOT_REACHED(); 169 return 0.0f; 170 } 171 } 172 173 static inline float glyphOrientationToAngle(const SVGRenderStyle* svgStyle, bool isVerticalText, const UChar& character) 174 { 175 switch (isVerticalText ? svgStyle->glyphOrientationVertical() : svgStyle->glyphOrientationHorizontal()) { 176 case GO_AUTO: 177 { 178 // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees. 179 // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees. 180 unsigned int unicodeRange = findCharUnicodeRange(character); 181 if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic) 182 return 90.0f; 183 184 return 0.0f; 185 } 186 case GO_90DEG: 187 return 90.0f; 188 case GO_180DEG: 189 return 180.0f; 190 case GO_270DEG: 191 return 270.0f; 192 case GO_0DEG: 193 default: 194 return 0.0f; 195 } 196 } 197 198 static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle) 199 { 200 return fabsf(fmodf(orientationAngle, 180.0f)) == 0.0f; 201 } 202 203 static inline float calculateGlyphAdvanceAndShiftRespectingOrientation(bool isVerticalText, float orientationAngle, float glyphWidth, float glyphHeight, const Font& font, SVGChar& svgChar, float& xOrientationShift, float& yOrientationShift) 204 { 205 bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(orientationAngle); 206 207 // The function is based on spec requirements: 208 // 209 // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of 210 // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph. 211 // 212 // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of 213 // 180 degrees,then the current text position is incremented according to the horizontal metrics of the glyph. 214 215 // vertical orientation handling 216 if (isVerticalText) { 217 if (orientationAngle == 0.0f) { 218 xOrientationShift = -glyphWidth / 2.0f; 219 yOrientationShift = font.ascent(); 220 } else if (orientationAngle == 90.0f) { 221 xOrientationShift = -glyphHeight; 222 yOrientationShift = font.descent(); 223 svgChar.orientationShiftY = -font.ascent(); 224 } else if (orientationAngle == 270.0f) { 225 xOrientationShift = glyphHeight; 226 yOrientationShift = font.descent(); 227 svgChar.orientationShiftX = -glyphWidth; 228 svgChar.orientationShiftY = -font.ascent(); 229 } else if (orientationAngle == 180.0f) { 230 yOrientationShift = font.ascent(); 231 svgChar.orientationShiftX = -glyphWidth / 2.0f; 232 svgChar.orientationShiftY = font.ascent() - font.descent(); 233 } 234 235 // vertical advance calculation 236 if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) 237 return glyphWidth; 238 239 return glyphHeight; 240 } 241 242 // horizontal orientation handling 243 if (orientationAngle == 90.0f) { 244 xOrientationShift = glyphWidth / 2.0f; 245 yOrientationShift = -font.descent(); 246 svgChar.orientationShiftX = -glyphWidth / 2.0f - font.descent(); 247 svgChar.orientationShiftY = font.descent(); 248 } else if (orientationAngle == 270.0f) { 249 xOrientationShift = -glyphWidth / 2.0f; 250 yOrientationShift = -font.descent(); 251 svgChar.orientationShiftX = -glyphWidth / 2.0f + font.descent(); 252 svgChar.orientationShiftY = glyphHeight; 253 } else if (orientationAngle == 180.0f) { 254 xOrientationShift = glyphWidth / 2.0f; 255 svgChar.orientationShiftX = -glyphWidth / 2.0f; 256 svgChar.orientationShiftY = font.ascent() - font.descent(); 257 } 258 259 // horizontal advance calculation 260 if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) 261 return glyphHeight; 262 263 return glyphWidth; 264 } 265 266 static inline void startTextChunk(SVGTextChunkLayoutInfo& info) 267 { 268 info.chunk.boxes.clear(); 269 info.chunk.boxes.append(SVGInlineBoxCharacterRange()); 270 271 info.chunk.start = info.it; 272 info.assignChunkProperties = true; 273 } 274 275 static inline void closeTextChunk(SVGTextChunkLayoutInfo& info) 276 { 277 ASSERT(!info.chunk.boxes.last().isOpen()); 278 ASSERT(info.chunk.boxes.last().isClosed()); 279 280 info.chunk.end = info.it; 281 ASSERT(info.chunk.end >= info.chunk.start); 282 283 info.svgTextChunks.append(info.chunk); 284 } 285 286 RenderSVGRoot* findSVGRootObject(RenderObject* start) 287 { 288 // Find associated root inline box. 289 while (start && !start->isSVGRoot()) 290 start = start->parent(); 291 ASSERT(start); 292 return toRenderSVGRoot(start); 293 } 294 295 static inline FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>& chars) 296 { 297 return topLeftPositionOfCharacterRange(chars.begin(), chars.end()); 298 } 299 300 FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>::iterator it, Vector<SVGChar>::iterator end) 301 { 302 float lowX = FLT_MAX, lowY = FLT_MAX; 303 for (; it != end; ++it) { 304 if (it->isHidden()) 305 continue; 306 307 float x = (*it).x; 308 float y = (*it).y; 309 310 if (x < lowX) 311 lowX = x; 312 313 if (y < lowY) 314 lowY = y; 315 } 316 317 return FloatPoint(lowX, lowY); 318 } 319 320 // Helper function 321 static float calculateKerning(RenderObject* item) 322 { 323 const Font& font = item->style()->font(); 324 const SVGRenderStyle* svgStyle = item->style()->svgStyle(); 325 326 float kerning = 0.0f; 327 if (CSSPrimitiveValue* primitive = static_cast<CSSPrimitiveValue*>(svgStyle->kerning())) { 328 kerning = primitive->getFloatValue(); 329 330 if (primitive->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE && font.pixelSize() > 0) 331 kerning = kerning / 100.0f * font.pixelSize(); 332 } 333 334 return kerning; 335 } 336 337 // Helper class for paint() 338 struct SVGRootInlineBoxPaintWalker { 339 SVGRootInlineBoxPaintWalker(SVGRootInlineBox* rootBox, SVGResourceFilter* rootFilter, RenderObject::PaintInfo paintInfo, int tx, int ty) 340 : m_rootBox(rootBox) 341 , m_chunkStarted(false) 342 , m_paintInfo(paintInfo) 343 , m_savedInfo(paintInfo) 344 , m_boundingBox(tx + rootBox->x(), ty + rootBox->y(), rootBox->width(), rootBox->height()) 345 , m_filter(0) 346 , m_rootFilter(rootFilter) 347 , m_fillPaintServer(0) 348 , m_strokePaintServer(0) 349 , m_fillPaintServerObject(0) 350 , m_strokePaintServerObject(0) 351 , m_tx(tx) 352 , m_ty(ty) 353 { 354 } 355 356 ~SVGRootInlineBoxPaintWalker() 357 { 358 ASSERT(!m_filter); 359 ASSERT(!m_fillPaintServer); 360 ASSERT(!m_fillPaintServerObject); 361 ASSERT(!m_strokePaintServer); 362 ASSERT(!m_strokePaintServerObject); 363 ASSERT(!m_chunkStarted); 364 } 365 366 bool mayHaveSelection(InlineBox* box) const 367 { 368 int selectionStart = 0, selectionEnd = 0; 369 box->renderer()->selectionStartEnd(selectionStart, selectionEnd); 370 return selectionStart < selectionEnd; 371 } 372 373 void teardownFillPaintServer() 374 { 375 if (!m_fillPaintServer) 376 return; 377 378 m_fillPaintServer->teardown(m_paintInfo.context, m_fillPaintServerObject, ApplyToFillTargetType, true); 379 380 m_fillPaintServer = 0; 381 m_fillPaintServerObject = 0; 382 } 383 384 void teardownStrokePaintServer() 385 { 386 if (!m_strokePaintServer) 387 return; 388 389 m_strokePaintServer->teardown(m_paintInfo.context, m_strokePaintServerObject, ApplyToStrokeTargetType, true); 390 391 m_strokePaintServer = 0; 392 m_strokePaintServerObject = 0; 393 } 394 395 void chunkStartCallback(InlineBox* box) 396 { 397 ASSERT(!m_chunkStarted); 398 m_chunkStarted = true; 399 400 InlineFlowBox* flowBox = box->parent(); 401 402 // Initialize text rendering 403 RenderObject* object = flowBox->renderer(); 404 ASSERT(object); 405 406 m_savedInfo = m_paintInfo; 407 m_paintInfo.context->save(); 408 409 // FIXME: Why is this done here instead of in RenderSVGText? 410 if (!flowBox->isRootInlineBox()) 411 SVGRenderBase::prepareToRenderSVGContent(object, m_paintInfo, m_boundingBox, m_filter, m_rootFilter); 412 } 413 414 void chunkEndCallback(InlineBox* box) 415 { 416 ASSERT(m_chunkStarted); 417 m_chunkStarted = false; 418 419 InlineFlowBox* flowBox = box->parent(); 420 421 RenderObject* object = flowBox->renderer(); 422 ASSERT(object); 423 424 // Clean up last used paint server 425 teardownFillPaintServer(); 426 teardownStrokePaintServer(); 427 428 // Finalize text rendering 429 if (!flowBox->isRootInlineBox()) { 430 SVGRenderBase::finishRenderSVGContent(object, m_paintInfo, m_filter, m_savedInfo.context); 431 m_filter = 0; 432 } 433 434 // Restore context & repaint rect 435 m_paintInfo.context->restore(); 436 m_paintInfo.rect = m_savedInfo.rect; 437 } 438 439 bool chunkSetupBackgroundCallback(InlineBox* /*box*/) 440 { 441 m_textPaintInfo.subphase = SVGTextPaintSubphaseBackground; 442 return true; 443 } 444 445 bool chunkSetupFillCallback(InlineBox* box) 446 { 447 InlineFlowBox* flowBox = box->parent(); 448 449 // Setup fill paint server 450 RenderObject* object = flowBox->renderer(); 451 ASSERT(object); 452 453 ASSERT(!m_strokePaintServer); 454 teardownFillPaintServer(); 455 456 m_textPaintInfo.subphase = SVGTextPaintSubphaseGlyphFill; 457 m_fillPaintServer = SVGPaintServer::fillPaintServer(object->style(), object); 458 if (m_fillPaintServer) { 459 m_fillPaintServer->setup(m_paintInfo.context, object, ApplyToFillTargetType, true); 460 m_fillPaintServerObject = object; 461 return true; 462 } 463 464 return false; 465 } 466 467 bool chunkSetupFillSelectionCallback(InlineBox* box) 468 { 469 InlineFlowBox* flowBox = box->parent(); 470 471 // Setup fill paint server 472 RenderObject* object = flowBox->renderer(); 473 ASSERT(object); 474 RenderStyle* style = object->getCachedPseudoStyle(SELECTION); 475 if (!style) 476 style = object->style(); 477 478 ASSERT(!m_strokePaintServer); 479 teardownFillPaintServer(); 480 481 if (!mayHaveSelection(box)) 482 return false; 483 484 m_textPaintInfo.subphase = SVGTextPaintSubphaseGlyphFillSelection; 485 m_fillPaintServer = SVGPaintServer::fillPaintServer(style, object); 486 if (m_fillPaintServer) { 487 m_fillPaintServer->setup(m_paintInfo.context, object, style, ApplyToFillTargetType, true); 488 m_fillPaintServerObject = object; 489 return true; 490 } 491 492 return false; 493 } 494 495 bool chunkSetupStrokeCallback(InlineBox* box) 496 { 497 InlineFlowBox* flowBox = box->parent(); 498 499 // Setup stroke paint server 500 RenderObject* object = flowBox->renderer(); 501 ASSERT(object); 502 503 // If we're both stroked & filled, teardown fill paint server before stroking. 504 teardownFillPaintServer(); 505 teardownStrokePaintServer(); 506 507 m_textPaintInfo.subphase = SVGTextPaintSubphaseGlyphStroke; 508 m_strokePaintServer = SVGPaintServer::strokePaintServer(object->style(), object); 509 510 if (m_strokePaintServer) { 511 m_strokePaintServer->setup(m_paintInfo.context, object, ApplyToStrokeTargetType, true); 512 m_strokePaintServerObject = object; 513 return true; 514 } 515 516 return false; 517 } 518 519 bool chunkSetupStrokeSelectionCallback(InlineBox* box) 520 { 521 InlineFlowBox* flowBox = box->parent(); 522 523 // Setup stroke paint server 524 RenderObject* object = flowBox->renderer(); 525 ASSERT(object); 526 RenderStyle* style = object->getCachedPseudoStyle(SELECTION); 527 if (!style) 528 style = object->style(); 529 530 // If we're both stroked & filled, teardown fill paint server before stroking. 531 teardownFillPaintServer(); 532 teardownStrokePaintServer(); 533 534 if (!mayHaveSelection(box)) 535 return false; 536 537 m_textPaintInfo.subphase = SVGTextPaintSubphaseGlyphStrokeSelection; 538 m_strokePaintServer = SVGPaintServer::strokePaintServer(style, object); 539 if (m_strokePaintServer) { 540 m_strokePaintServer->setup(m_paintInfo.context, object, style, ApplyToStrokeTargetType, true); 541 m_strokePaintServerObject = object; 542 return true; 543 } 544 545 return false; 546 } 547 548 bool chunkSetupForegroundCallback(InlineBox* /*box*/) 549 { 550 teardownFillPaintServer(); 551 teardownStrokePaintServer(); 552 553 m_textPaintInfo.subphase = SVGTextPaintSubphaseForeground; 554 555 return true; 556 } 557 558 SVGPaintServer* activePaintServer() const 559 { 560 switch (m_textPaintInfo.subphase) { 561 case SVGTextPaintSubphaseGlyphFill: 562 case SVGTextPaintSubphaseGlyphFillSelection: 563 ASSERT(m_fillPaintServer); 564 return m_fillPaintServer; 565 case SVGTextPaintSubphaseGlyphStroke: 566 case SVGTextPaintSubphaseGlyphStrokeSelection: 567 ASSERT(m_strokePaintServer); 568 return m_strokePaintServer; 569 case SVGTextPaintSubphaseBackground: 570 case SVGTextPaintSubphaseForeground: 571 default: 572 return 0; 573 } 574 } 575 576 void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const AffineTransform& chunkCtm, 577 const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end) 578 { 579 RenderText* text = textBox->textRenderer(); 580 ASSERT(text); 581 582 RenderStyle* styleToUse = text->style(textBox->isFirstLineStyle()); 583 ASSERT(styleToUse); 584 585 startOffset += textBox->start(); 586 587 int textDecorations = styleToUse->textDecorationsInEffect(); 588 589 int textWidth = 0; 590 IntPoint decorationOrigin; 591 SVGTextDecorationInfo info; 592 593 if (!chunkCtm.isIdentity()) 594 m_paintInfo.context->concatCTM(chunkCtm); 595 596 for (Vector<SVGChar>::iterator it = start; it != end; ++it) { 597 if (it->isHidden()) 598 continue; 599 600 // Determine how many characters - starting from the current - can be drawn at once. 601 Vector<SVGChar>::iterator itSearch = it + 1; 602 while (itSearch != end) { 603 if (itSearch->drawnSeperated || itSearch->isHidden()) 604 break; 605 606 itSearch++; 607 } 608 609 const UChar* stringStart = text->characters() + startOffset + (it - start); 610 unsigned int stringLength = itSearch - it; 611 612 // Paint decorations, that have to be drawn before the text gets drawn 613 if (textDecorations != TDNONE && m_paintInfo.phase != PaintPhaseSelection) { 614 textWidth = styleToUse->font().width(svgTextRunForInlineTextBox(stringStart, stringLength, styleToUse, textBox, (*it).x)); 615 decorationOrigin = IntPoint((int) (*it).x, (int) (*it).y - styleToUse->font().ascent()); 616 info = m_rootBox->retrievePaintServersForTextDecoration(text); 617 } 618 619 if (textDecorations & UNDERLINE && textWidth != 0.0f) 620 textBox->paintDecoration(UNDERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info); 621 622 if (textDecorations & OVERLINE && textWidth != 0.0f) 623 textBox->paintDecoration(OVERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info); 624 625 // Paint text 626 m_textPaintInfo.activePaintServer = activePaintServer(); 627 textBox->paintCharacters(m_paintInfo, m_tx, m_ty, *it, stringStart, stringLength, m_textPaintInfo); 628 629 // Paint decorations, that have to be drawn afterwards 630 if (textDecorations & LINE_THROUGH && textWidth != 0.0f) 631 textBox->paintDecoration(LINE_THROUGH, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info); 632 633 // Skip processed characters 634 it = itSearch - 1; 635 } 636 637 if (!chunkCtm.isIdentity()) 638 m_paintInfo.context->concatCTM(chunkCtm.inverse()); 639 } 640 641 private: 642 SVGRootInlineBox* m_rootBox; 643 bool m_chunkStarted : 1; 644 645 RenderObject::PaintInfo m_paintInfo; 646 RenderObject::PaintInfo m_savedInfo; 647 648 FloatRect m_boundingBox; 649 SVGResourceFilter* m_filter; 650 SVGResourceFilter* m_rootFilter; 651 652 SVGPaintServer* m_fillPaintServer; 653 SVGPaintServer* m_strokePaintServer; 654 655 RenderObject* m_fillPaintServerObject; 656 RenderObject* m_strokePaintServerObject; 657 658 int m_tx; 659 int m_ty; 660 661 SVGTextPaintInfo m_textPaintInfo; 662 }; 663 664 void SVGRootInlineBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty) 665 { 666 if (paintInfo.context->paintingDisabled() || paintInfo.phase != PaintPhaseForeground) 667 return; 668 669 RenderObject::PaintInfo savedInfo(paintInfo); 670 paintInfo.context->save(); 671 672 SVGResourceFilter* filter = 0; 673 FloatRect boundingBox(tx + x(), ty + y(), width(), height()); 674 675 // Initialize text rendering 676 if (SVGRenderBase::prepareToRenderSVGContent(renderer(), paintInfo, boundingBox, filter)) { 677 // Render text, chunk-by-chunk 678 SVGRootInlineBoxPaintWalker walkerCallback(this, filter, paintInfo, tx, ty); 679 SVGTextChunkWalker<SVGRootInlineBoxPaintWalker> walker(&walkerCallback, 680 &SVGRootInlineBoxPaintWalker::chunkPortionCallback, 681 &SVGRootInlineBoxPaintWalker::chunkStartCallback, 682 &SVGRootInlineBoxPaintWalker::chunkEndCallback, 683 &SVGRootInlineBoxPaintWalker::chunkSetupBackgroundCallback, 684 &SVGRootInlineBoxPaintWalker::chunkSetupFillCallback, 685 &SVGRootInlineBoxPaintWalker::chunkSetupFillSelectionCallback, 686 &SVGRootInlineBoxPaintWalker::chunkSetupStrokeCallback, 687 &SVGRootInlineBoxPaintWalker::chunkSetupStrokeSelectionCallback, 688 &SVGRootInlineBoxPaintWalker::chunkSetupForegroundCallback); 689 690 walkTextChunks(&walker); 691 } 692 693 // Finalize text rendering 694 SVGRenderBase::finishRenderSVGContent(renderer(), paintInfo, filter, savedInfo.context); 695 paintInfo.context->restore(); 696 } 697 698 int SVGRootInlineBox::placeBoxesHorizontally(int, int& leftPosition, int& rightPosition, bool&) 699 { 700 // Remove any offsets caused by RTL text layout 701 leftPosition = 0; 702 rightPosition = 0; 703 return 0; 704 } 705 706 int SVGRootInlineBox::verticallyAlignBoxes(int) 707 { 708 // height is set by layoutInlineBoxes. 709 return height(); 710 } 711 712 float cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range) 713 { 714 ASSERT(!range.isOpen()); 715 ASSERT(range.isClosed()); 716 ASSERT(range.box->isInlineTextBox()); 717 718 InlineTextBox* textBox = static_cast<InlineTextBox*>(range.box); 719 RenderText* text = textBox->textRenderer(); 720 RenderStyle* style = text->style(); 721 722 return style->font().floatWidth(svgTextRunForInlineTextBox(text->characters() + textBox->start() + range.startOffset, range.endOffset - range.startOffset, style, textBox, 0)); 723 } 724 725 float cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range) 726 { 727 ASSERT(!range.isOpen()); 728 ASSERT(range.isClosed()); 729 ASSERT(range.box->isInlineTextBox()); 730 731 InlineTextBox* textBox = static_cast<InlineTextBox*>(range.box); 732 RenderText* text = textBox->textRenderer(); 733 const Font& font = text->style()->font(); 734 735 return (range.endOffset - range.startOffset) * (font.ascent() + font.descent()); 736 } 737 738 TextRun svgTextRunForInlineTextBox(const UChar* c, int len, RenderStyle* style, const InlineTextBox* textBox, float xPos) 739 { 740 ASSERT(textBox); 741 ASSERT(style); 742 743 TextRun run(c, len, false, static_cast<int>(xPos), textBox->toAdd(), textBox->direction() == RTL, textBox->m_dirOverride || style->visuallyOrdered()); 744 745 #if ENABLE(SVG_FONTS) 746 run.setReferencingRenderObject(textBox->textRenderer()->parent()); 747 #endif 748 749 // We handle letter & word spacing ourselves 750 run.disableSpacing(); 751 return run; 752 } 753 754 static float cummulatedWidthOrHeightOfTextChunk(SVGTextChunk& chunk, bool calcWidthOnly) 755 { 756 float length = 0.0f; 757 Vector<SVGChar>::iterator charIt = chunk.start; 758 759 Vector<SVGInlineBoxCharacterRange>::iterator it = chunk.boxes.begin(); 760 Vector<SVGInlineBoxCharacterRange>::iterator end = chunk.boxes.end(); 761 762 for (; it != end; ++it) { 763 SVGInlineBoxCharacterRange& range = *it; 764 765 SVGInlineTextBox* box = static_cast<SVGInlineTextBox*>(range.box); 766 RenderStyle* style = box->renderer()->style(); 767 768 for (int i = range.startOffset; i < range.endOffset; ++i) { 769 ASSERT(charIt <= chunk.end); 770 771 // Determine how many characters - starting from the current - can be measured at once. 772 // Important for non-absolute positioned non-latin1 text (ie. Arabic) where ie. the width 773 // of a string is not the sum of the boundaries of all contained glyphs. 774 Vector<SVGChar>::iterator itSearch = charIt + 1; 775 Vector<SVGChar>::iterator endSearch = charIt + range.endOffset - i; 776 while (itSearch != endSearch) { 777 // No need to check for 'isHidden()' here as this function is not called for text paths. 778 if (itSearch->drawnSeperated) 779 break; 780 781 itSearch++; 782 } 783 784 unsigned int positionOffset = itSearch - charIt; 785 786 // Calculate width/height of subrange 787 SVGInlineBoxCharacterRange subRange; 788 subRange.box = range.box; 789 subRange.startOffset = i; 790 subRange.endOffset = i + positionOffset; 791 792 if (calcWidthOnly) 793 length += cummulatedWidthOfInlineBoxCharacterRange(subRange); 794 else 795 length += cummulatedHeightOfInlineBoxCharacterRange(subRange); 796 797 // Calculate gap between the previous & current range 798 // <text x="10 50 70">ABCD</text> - we need to take the gaps between A & B into account 799 // so add "40" as width, and analogous for B & C, add "20" as width. 800 if (itSearch > chunk.start && itSearch < chunk.end) { 801 SVGChar& lastCharacter = *(itSearch - 1); 802 SVGChar& currentCharacter = *itSearch; 803 804 int offset = box->direction() == RTL ? box->end() - i - positionOffset + 1 : box->start() + i + positionOffset - 1; 805 806 // FIXME: does this need to change to handle multichar glyphs? 807 int charsConsumed = 1; 808 String glyphName; 809 if (calcWidthOnly) { 810 float lastGlyphWidth = box->calculateGlyphWidth(style, offset, 0, charsConsumed, glyphName); 811 length += currentCharacter.x - lastCharacter.x - lastGlyphWidth; 812 } else { 813 float lastGlyphHeight = box->calculateGlyphHeight(style, offset, 0); 814 length += currentCharacter.y - lastCharacter.y - lastGlyphHeight; 815 } 816 } 817 818 // Advance processed characters 819 i += positionOffset - 1; 820 charIt = itSearch; 821 } 822 } 823 824 ASSERT(charIt == chunk.end); 825 return length; 826 } 827 828 static float cummulatedWidthOfTextChunk(SVGTextChunk& chunk) 829 { 830 return cummulatedWidthOrHeightOfTextChunk(chunk, true); 831 } 832 833 static float cummulatedHeightOfTextChunk(SVGTextChunk& chunk) 834 { 835 return cummulatedWidthOrHeightOfTextChunk(chunk, false); 836 } 837 838 static float calculateTextAnchorShiftForTextChunk(SVGTextChunk& chunk, ETextAnchor anchor) 839 { 840 float shift = 0.0f; 841 842 if (chunk.isVerticalText) 843 shift = cummulatedHeightOfTextChunk(chunk); 844 else 845 shift = cummulatedWidthOfTextChunk(chunk); 846 847 if (anchor == TA_MIDDLE) 848 shift *= -0.5f; 849 else 850 shift *= -1.0f; 851 852 return shift; 853 } 854 855 static void applyTextAnchorToTextChunk(SVGTextChunk& chunk) 856 { 857 // This method is not called for chunks containing chars aligned on a path. 858 // -> all characters are visible, no need to check for "isHidden()" anywhere. 859 860 if (chunk.anchor == TA_START) 861 return; 862 863 float shift = calculateTextAnchorShiftForTextChunk(chunk, chunk.anchor); 864 865 // Apply correction to chunk 866 Vector<SVGChar>::iterator chunkIt = chunk.start; 867 for (; chunkIt != chunk.end; ++chunkIt) { 868 SVGChar& curChar = *chunkIt; 869 870 if (chunk.isVerticalText) 871 curChar.y += shift; 872 else 873 curChar.x += shift; 874 } 875 876 // Move inline boxes 877 Vector<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin(); 878 Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = chunk.boxes.end(); 879 880 for (; boxIt != boxEnd; ++boxIt) { 881 SVGInlineBoxCharacterRange& range = *boxIt; 882 883 InlineBox* curBox = range.box; 884 ASSERT(curBox->isInlineTextBox()); 885 886 // Move target box 887 if (chunk.isVerticalText) 888 curBox->setY(curBox->y() + static_cast<int>(shift)); 889 else 890 curBox->setX(curBox->x() + static_cast<int>(shift)); 891 } 892 } 893 894 static float calculateTextLengthCorrectionForTextChunk(SVGTextChunk& chunk, ELengthAdjust lengthAdjust, float& computedLength) 895 { 896 if (chunk.textLength <= 0.0f) 897 return 0.0f; 898 899 float computedWidth = cummulatedWidthOfTextChunk(chunk); 900 float computedHeight = cummulatedHeightOfTextChunk(chunk); 901 902 if ((computedWidth <= 0.0f && !chunk.isVerticalText) || 903 (computedHeight <= 0.0f && chunk.isVerticalText)) 904 return 0.0f; 905 906 if (chunk.isVerticalText) 907 computedLength = computedHeight; 908 else 909 computedLength = computedWidth; 910 911 if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) { 912 if (chunk.isVerticalText) 913 chunk.ctm.scaleNonUniform(1.0f, chunk.textLength / computedLength); 914 else 915 chunk.ctm.scaleNonUniform(chunk.textLength / computedLength, 1.0f); 916 917 return 0.0f; 918 } 919 920 return (chunk.textLength - computedLength) / float(chunk.end - chunk.start); 921 } 922 923 static void applyTextLengthCorrectionToTextChunk(SVGTextChunk& chunk) 924 { 925 // This method is not called for chunks containing chars aligned on a path. 926 // -> all characters are visible, no need to check for "isHidden()" anywhere. 927 928 // lengthAdjust="spacingAndGlyphs" is handled by modifying chunk.ctm 929 float computedLength = 0.0f; 930 float spacingToApply = calculateTextLengthCorrectionForTextChunk(chunk, chunk.lengthAdjust, computedLength); 931 932 if (!chunk.ctm.isIdentity() && chunk.lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) { 933 SVGChar& firstChar = *(chunk.start); 934 935 // Assure we apply the chunk scaling in the right origin 936 AffineTransform newChunkCtm(chunk.ctm); 937 newChunkCtm.translateRight(firstChar.x, firstChar.y); 938 newChunkCtm.translate(-firstChar.x, -firstChar.y); 939 940 chunk.ctm = newChunkCtm; 941 } 942 943 // Apply correction to chunk 944 if (spacingToApply != 0.0f) { 945 Vector<SVGChar>::iterator chunkIt = chunk.start; 946 for (; chunkIt != chunk.end; ++chunkIt) { 947 SVGChar& curChar = *chunkIt; 948 curChar.drawnSeperated = true; 949 950 if (chunk.isVerticalText) 951 curChar.y += (chunkIt - chunk.start) * spacingToApply; 952 else 953 curChar.x += (chunkIt - chunk.start) * spacingToApply; 954 } 955 } 956 } 957 958 void SVGRootInlineBox::computePerCharacterLayoutInformation() 959 { 960 // Clean up any previous layout information 961 m_svgChars.clear(); 962 m_svgTextChunks.clear(); 963 964 // Build layout information for all contained render objects 965 SVGCharacterLayoutInfo info(m_svgChars); 966 buildLayoutInformation(this, info); 967 968 // Now all layout information are available for every character 969 // contained in any of our child inline/flow boxes. Build list 970 // of text chunks now, to be able to apply text-anchor shifts. 971 buildTextChunks(m_svgChars, m_svgTextChunks, this); 972 973 // Layout all text chunks 974 // text-anchor needs to be applied to individual chunks. 975 layoutTextChunks(); 976 977 // Finally the top left position of our box is known. 978 // Propogate this knownledge to our RenderSVGText parent. 979 FloatPoint topLeft = topLeftPositionOfCharacterRange(m_svgChars); 980 block()->setLocation((int) floorf(topLeft.x()), (int) floorf(topLeft.y())); 981 982 // Layout all InlineText/Flow boxes 983 // BEWARE: This requires the root top/left position to be set correctly before! 984 layoutInlineBoxes(); 985 } 986 987 void SVGRootInlineBox::buildLayoutInformation(InlineFlowBox* start, SVGCharacterLayoutInfo& info) 988 { 989 if (start->isRootInlineBox()) { 990 ASSERT(start->renderer()->node()->hasTagName(SVGNames::textTag)); 991 992 SVGTextPositioningElement* positioningElement = static_cast<SVGTextPositioningElement*>(start->renderer()->node()); 993 ASSERT(positioningElement); 994 ASSERT(positioningElement->parentNode()); 995 996 info.addLayoutInformation(positioningElement); 997 } 998 999 LastGlyphInfo lastGlyph; 1000 1001 for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) { 1002 if (curr->renderer()->isText()) 1003 buildLayoutInformationForTextBox(info, static_cast<InlineTextBox*>(curr), lastGlyph); 1004 else { 1005 ASSERT(curr->isInlineFlowBox()); 1006 InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(curr); 1007 1008 if (!flowBox->renderer()->node()) 1009 continue; // Skip generated content. 1010 1011 bool isAnchor = flowBox->renderer()->node()->hasTagName(SVGNames::aTag); 1012 bool isTextPath = flowBox->renderer()->node()->hasTagName(SVGNames::textPathTag); 1013 1014 if (!isTextPath && !isAnchor) { 1015 SVGTextPositioningElement* positioningElement = static_cast<SVGTextPositioningElement*>(flowBox->renderer()->node()); 1016 ASSERT(positioningElement); 1017 ASSERT(positioningElement->parentNode()); 1018 1019 info.addLayoutInformation(positioningElement); 1020 } else if (!isAnchor) { 1021 info.setInPathLayout(true); 1022 1023 // Handle text-anchor/textLength on path, which is special. 1024 SVGTextContentElement* textContent = 0; 1025 Node* node = flowBox->renderer()->node(); 1026 if (node && node->isSVGElement()) 1027 textContent = static_cast<SVGTextContentElement*>(node); 1028 ASSERT(textContent); 1029 1030 ELengthAdjust lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); 1031 ETextAnchor anchor = flowBox->renderer()->style()->svgStyle()->textAnchor(); 1032 float textAnchorStartOffset = 0.0f; 1033 1034 // Initialize sub-layout. We need to create text chunks from the textPath 1035 // children using our standard layout code, to be able to measure the 1036 // text length using our normal methods and not textPath specific hacks. 1037 Vector<SVGChar> tempChars; 1038 Vector<SVGTextChunk> tempChunks; 1039 1040 SVGCharacterLayoutInfo tempInfo(tempChars); 1041 buildLayoutInformation(flowBox, tempInfo); 1042 1043 buildTextChunks(tempChars, tempChunks, flowBox); 1044 1045 Vector<SVGTextChunk>::iterator it = tempChunks.begin(); 1046 Vector<SVGTextChunk>::iterator end = tempChunks.end(); 1047 1048 float computedLength = 0.0f; 1049 1050 for (; it != end; ++it) { 1051 SVGTextChunk& chunk = *it; 1052 1053 // Apply text-length calculation 1054 info.pathExtraAdvance += calculateTextLengthCorrectionForTextChunk(chunk, lengthAdjust, computedLength); 1055 1056 if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) { 1057 info.pathTextLength += computedLength; 1058 info.pathChunkLength += chunk.textLength; 1059 } 1060 1061 // Calculate text-anchor start offset 1062 if (anchor == TA_START) 1063 continue; 1064 1065 textAnchorStartOffset += calculateTextAnchorShiftForTextChunk(chunk, anchor); 1066 } 1067 1068 info.addLayoutInformation(flowBox, textAnchorStartOffset); 1069 } 1070 1071 float shiftxSaved = info.shiftx; 1072 float shiftySaved = info.shifty; 1073 1074 buildLayoutInformation(flowBox, info); 1075 info.processedChunk(shiftxSaved, shiftySaved); 1076 1077 if (isTextPath) 1078 info.setInPathLayout(false); 1079 } 1080 } 1081 } 1082 1083 void SVGRootInlineBox::layoutInlineBoxes() 1084 { 1085 int lowX = INT_MAX; 1086 int lowY = INT_MAX; 1087 int highX = INT_MIN; 1088 int highY = INT_MIN; 1089 1090 // Layout all child boxes 1091 Vector<SVGChar>::iterator it = m_svgChars.begin(); 1092 layoutInlineBoxes(this, it, lowX, highX, lowY, highY); 1093 ASSERT(it == m_svgChars.end()); 1094 } 1095 1096 void SVGRootInlineBox::layoutInlineBoxes(InlineFlowBox* start, Vector<SVGChar>::iterator& it, int& lowX, int& highX, int& lowY, int& highY) 1097 { 1098 for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) { 1099 RenderStyle* style = curr->renderer()->style(); 1100 if (curr->renderer()->isText()) { 1101 SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(curr); 1102 unsigned length = textBox->len(); 1103 1104 SVGChar curChar = *it; 1105 ASSERT(it != m_svgChars.end()); 1106 1107 FloatRect stringRect; 1108 for (unsigned i = 0; i < length; ++i) { 1109 ASSERT(it != m_svgChars.end()); 1110 1111 if (it->isHidden()) { 1112 ++it; 1113 continue; 1114 } 1115 1116 stringRect.unite(textBox->calculateGlyphBoundaries(style, textBox->start() + i, *it)); 1117 ++it; 1118 } 1119 1120 IntRect enclosedStringRect = enclosingIntRect(stringRect); 1121 1122 int minX = enclosedStringRect.x(); 1123 int maxX = minX + enclosedStringRect.width(); 1124 1125 int minY = enclosedStringRect.y(); 1126 int maxY = minY + enclosedStringRect.height(); 1127 1128 curr->setX(minX - block()->x()); 1129 curr->setWidth(enclosedStringRect.width()); 1130 1131 curr->setY(minY - block()->y()); 1132 textBox->setHeight(enclosedStringRect.height()); 1133 1134 if (minX < lowX) 1135 lowX = minX; 1136 1137 if (maxX > highX) 1138 highX = maxX; 1139 1140 if (minY < lowY) 1141 lowY = minY; 1142 1143 if (maxY > highY) 1144 highY = maxY; 1145 } else { 1146 ASSERT(curr->isInlineFlowBox()); 1147 1148 int minX = INT_MAX; 1149 int minY = INT_MAX; 1150 int maxX = INT_MIN; 1151 int maxY = INT_MIN; 1152 1153 InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(curr); 1154 1155 if (!flowBox->renderer()->node()) 1156 continue; // Skip generated content. 1157 1158 layoutInlineBoxes(flowBox, it, minX, maxX, minY, maxY); 1159 1160 curr->setX(minX - block()->x()); 1161 curr->setWidth(maxX - minX); 1162 1163 curr->setY(minY - block()->y()); 1164 static_cast<SVGInlineFlowBox*>(curr)->setHeight(maxY - minY); 1165 1166 if (minX < lowX) 1167 lowX = minX; 1168 1169 if (maxX > highX) 1170 highX = maxX; 1171 1172 if (minY < lowY) 1173 lowY = minY; 1174 1175 if (maxY > highY) 1176 highY = maxY; 1177 } 1178 } 1179 1180 if (start->isSVGRootInlineBox()) { 1181 int top = lowY - block()->y(); 1182 int bottom = highY - block()->y(); 1183 1184 start->setX(lowX - block()->x()); 1185 start->setY(top); 1186 1187 start->setWidth(highX - lowX); 1188 static_cast<SVGRootInlineBox*>(start)->setHeight(highY - lowY); 1189 1190 start->computeVerticalOverflow(top, bottom, true); 1191 static_cast<SVGRootInlineBox*>(start)->setLineTopBottomPositions(top, bottom); 1192 } 1193 } 1194 1195 void SVGRootInlineBox::buildLayoutInformationForTextBox(SVGCharacterLayoutInfo& info, InlineTextBox* textBox, LastGlyphInfo& lastGlyph) 1196 { 1197 RenderText* text = textBox->textRenderer(); 1198 ASSERT(text); 1199 1200 RenderStyle* style = text->style(textBox->isFirstLineStyle()); 1201 ASSERT(style); 1202 1203 const Font& font = style->font(); 1204 SVGInlineTextBox* svgTextBox = static_cast<SVGInlineTextBox*>(textBox); 1205 1206 unsigned length = textBox->len(); 1207 1208 const SVGRenderStyle* svgStyle = style->svgStyle(); 1209 bool isVerticalText = isVerticalWritingMode(svgStyle); 1210 1211 int charsConsumed = 0; 1212 for (unsigned i = 0; i < length; i += charsConsumed) { 1213 SVGChar svgChar; 1214 1215 if (info.inPathLayout()) 1216 svgChar.pathData = SVGCharOnPath::create(); 1217 1218 float glyphWidth = 0.0f; 1219 float glyphHeight = 0.0f; 1220 1221 int extraCharsAvailable = length - i - 1; 1222 1223 String unicodeStr; 1224 String glyphName; 1225 if (textBox->direction() == RTL) { 1226 glyphWidth = svgTextBox->calculateGlyphWidth(style, textBox->end() - i, extraCharsAvailable, charsConsumed, glyphName); 1227 glyphHeight = svgTextBox->calculateGlyphHeight(style, textBox->end() - i, extraCharsAvailable); 1228 unicodeStr = String(textBox->textRenderer()->text()->characters() + textBox->end() - i, charsConsumed); 1229 } else { 1230 glyphWidth = svgTextBox->calculateGlyphWidth(style, textBox->start() + i, extraCharsAvailable, charsConsumed, glyphName); 1231 glyphHeight = svgTextBox->calculateGlyphHeight(style, textBox->start() + i, extraCharsAvailable); 1232 unicodeStr = String(textBox->textRenderer()->text()->characters() + textBox->start() + i, charsConsumed); 1233 } 1234 1235 bool assignedX = false; 1236 bool assignedY = false; 1237 1238 if (info.xValueAvailable() && (!info.inPathLayout() || (info.inPathLayout() && !isVerticalText))) { 1239 if (!isVerticalText) 1240 svgChar.newTextChunk = true; 1241 1242 assignedX = true; 1243 svgChar.drawnSeperated = true; 1244 info.curx = info.xValueNext(); 1245 } 1246 1247 if (info.yValueAvailable() && (!info.inPathLayout() || (info.inPathLayout() && isVerticalText))) { 1248 if (isVerticalText) 1249 svgChar.newTextChunk = true; 1250 1251 assignedY = true; 1252 svgChar.drawnSeperated = true; 1253 info.cury = info.yValueNext(); 1254 } 1255 1256 float dx = 0.0f; 1257 float dy = 0.0f; 1258 1259 // Apply x-axis shift 1260 if (info.dxValueAvailable()) { 1261 svgChar.drawnSeperated = true; 1262 1263 dx = info.dxValueNext(); 1264 info.dx += dx; 1265 1266 if (!info.inPathLayout()) 1267 info.curx += dx; 1268 } 1269 1270 // Apply y-axis shift 1271 if (info.dyValueAvailable()) { 1272 svgChar.drawnSeperated = true; 1273 1274 dy = info.dyValueNext(); 1275 info.dy += dy; 1276 1277 if (!info.inPathLayout()) 1278 info.cury += dy; 1279 } 1280 1281 // Take letter & word spacing and kerning into account 1282 float spacing = font.letterSpacing() + calculateKerning(textBox->renderer()->node()->renderer()); 1283 1284 const UChar* currentCharacter = text->characters() + (textBox->direction() == RTL ? textBox->end() - i : textBox->start() + i); 1285 const UChar* lastCharacter = 0; 1286 1287 if (textBox->direction() == RTL) { 1288 if (i < textBox->end()) 1289 lastCharacter = text->characters() + textBox->end() - i + 1; 1290 } else { 1291 if (i > 0) 1292 lastCharacter = text->characters() + textBox->start() + i - 1; 1293 } 1294 1295 if (info.nextDrawnSeperated || spacing != 0.0f) { 1296 info.nextDrawnSeperated = false; 1297 svgChar.drawnSeperated = true; 1298 } 1299 1300 if (currentCharacter && Font::treatAsSpace(*currentCharacter) && lastCharacter && !Font::treatAsSpace(*lastCharacter)) { 1301 spacing += font.wordSpacing(); 1302 1303 if (spacing != 0.0f && !info.inPathLayout()) 1304 info.nextDrawnSeperated = true; 1305 } 1306 1307 float orientationAngle = glyphOrientationToAngle(svgStyle, isVerticalText, *currentCharacter); 1308 1309 float xOrientationShift = 0.0f; 1310 float yOrientationShift = 0.0f; 1311 float glyphAdvance = calculateGlyphAdvanceAndShiftRespectingOrientation(isVerticalText, orientationAngle, glyphWidth, glyphHeight, 1312 font, svgChar, xOrientationShift, yOrientationShift); 1313 1314 // Handle textPath layout mode 1315 if (info.inPathLayout()) { 1316 float extraAdvance = isVerticalText ? dy : dx; 1317 float newOffset = FLT_MIN; 1318 1319 if (assignedX && !isVerticalText) 1320 newOffset = info.curx; 1321 else if (assignedY && isVerticalText) 1322 newOffset = info.cury; 1323 1324 float correctedGlyphAdvance = glyphAdvance; 1325 1326 // Handle lengthAdjust="spacingAndGlyphs" by specifying per-character scale operations 1327 if (info.pathTextLength > 0.0f && info.pathChunkLength > 0.0f) { 1328 if (isVerticalText) { 1329 svgChar.pathData->yScale = info.pathChunkLength / info.pathTextLength; 1330 spacing *= svgChar.pathData->yScale; 1331 correctedGlyphAdvance *= svgChar.pathData->yScale; 1332 } else { 1333 svgChar.pathData->xScale = info.pathChunkLength / info.pathTextLength; 1334 spacing *= svgChar.pathData->xScale; 1335 correctedGlyphAdvance *= svgChar.pathData->xScale; 1336 } 1337 } 1338 1339 // Handle letter & word spacing on text path 1340 float pathExtraAdvance = info.pathExtraAdvance; 1341 info.pathExtraAdvance += spacing; 1342 1343 svgChar.pathData->hidden = !info.nextPathLayoutPointAndAngle(correctedGlyphAdvance, extraAdvance, newOffset); 1344 svgChar.drawnSeperated = true; 1345 1346 info.pathExtraAdvance = pathExtraAdvance; 1347 } 1348 1349 // Apply rotation 1350 if (info.angleValueAvailable()) 1351 info.angle = info.angleValueNext(); 1352 1353 // Apply baseline-shift 1354 if (info.baselineShiftValueAvailable()) { 1355 svgChar.drawnSeperated = true; 1356 float shift = info.baselineShiftValueNext(); 1357 1358 if (isVerticalText) 1359 info.shiftx += shift; 1360 else 1361 info.shifty -= shift; 1362 } 1363 1364 // Take dominant-baseline / alignment-baseline into account 1365 yOrientationShift += alignmentBaselineToShift(isVerticalText, text, font); 1366 1367 svgChar.x = info.curx; 1368 svgChar.y = info.cury; 1369 svgChar.angle = info.angle; 1370 1371 // For text paths any shift (dx/dy/baseline-shift) has to be applied after the rotation 1372 if (!info.inPathLayout()) { 1373 svgChar.x += info.shiftx + xOrientationShift; 1374 svgChar.y += info.shifty + yOrientationShift; 1375 1376 if (orientationAngle != 0.0f) 1377 svgChar.angle += orientationAngle; 1378 1379 if (svgChar.angle != 0.0f) 1380 svgChar.drawnSeperated = true; 1381 } else { 1382 svgChar.pathData->orientationAngle = orientationAngle; 1383 1384 if (isVerticalText) 1385 svgChar.angle -= 90.0f; 1386 1387 svgChar.pathData->xShift = info.shiftx + xOrientationShift; 1388 svgChar.pathData->yShift = info.shifty + yOrientationShift; 1389 1390 // Translate to glyph midpoint 1391 if (isVerticalText) { 1392 svgChar.pathData->xShift += info.dx; 1393 svgChar.pathData->yShift -= glyphAdvance / 2.0f; 1394 } else { 1395 svgChar.pathData->xShift -= glyphAdvance / 2.0f; 1396 svgChar.pathData->yShift += info.dy; 1397 } 1398 } 1399 1400 double kerning = 0.0; 1401 #if ENABLE(SVG_FONTS) 1402 SVGFontElement* svgFont = 0; 1403 if (style->font().isSVGFont()) 1404 svgFont = style->font().svgFont(); 1405 1406 if (lastGlyph.isValid && style->font().isSVGFont()) { 1407 SVGHorizontalKerningPair kerningPair; 1408 if (svgFont->getHorizontalKerningPairForStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeStr, glyphName, kerningPair)) 1409 kerning = kerningPair.kerning; 1410 } 1411 1412 if (style->font().isSVGFont()) { 1413 lastGlyph.unicode = unicodeStr; 1414 lastGlyph.glyphName = glyphName; 1415 lastGlyph.isValid = true; 1416 } else 1417 lastGlyph.isValid = false; 1418 #endif 1419 1420 svgChar.x -= (float)kerning; 1421 1422 // Advance to new position 1423 if (isVerticalText) { 1424 svgChar.drawnSeperated = true; 1425 info.cury += glyphAdvance + spacing; 1426 } else 1427 info.curx += glyphAdvance + spacing - (float)kerning; 1428 1429 // Advance to next character group 1430 for (int k = 0; k < charsConsumed; ++k) { 1431 info.svgChars.append(svgChar); 1432 info.processedSingleCharacter(); 1433 svgChar.drawnSeperated = false; 1434 svgChar.newTextChunk = false; 1435 } 1436 } 1437 } 1438 1439 void SVGRootInlineBox::buildTextChunks(Vector<SVGChar>& svgChars, Vector<SVGTextChunk>& svgTextChunks, InlineFlowBox* start) 1440 { 1441 SVGTextChunkLayoutInfo info(svgTextChunks); 1442 info.it = svgChars.begin(); 1443 info.chunk.start = svgChars.begin(); 1444 info.chunk.end = svgChars.begin(); 1445 1446 buildTextChunks(svgChars, start, info); 1447 ASSERT(info.it == svgChars.end()); 1448 } 1449 1450 void SVGRootInlineBox::buildTextChunks(Vector<SVGChar>& svgChars, InlineFlowBox* start, SVGTextChunkLayoutInfo& info) 1451 { 1452 #if DEBUG_CHUNK_BUILDING > 1 1453 fprintf(stderr, " -> buildTextChunks(start=%p)\n", start); 1454 #endif 1455 1456 for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) { 1457 if (curr->renderer()->isText()) { 1458 InlineTextBox* textBox = static_cast<InlineTextBox*>(curr); 1459 1460 unsigned length = textBox->len(); 1461 if (!length) 1462 continue; 1463 1464 #if DEBUG_CHUNK_BUILDING > 1 1465 fprintf(stderr, " -> Handle inline text box (%p) with %i characters (start: %i, end: %i), handlingTextPath=%i\n", 1466 textBox, length, textBox->start(), textBox->end(), (int) info.handlingTextPath); 1467 #endif 1468 1469 RenderText* text = textBox->textRenderer(); 1470 ASSERT(text); 1471 ASSERT(text->node()); 1472 1473 SVGTextContentElement* textContent = 0; 1474 Node* node = text->node()->parent(); 1475 while (node && node->isSVGElement() && !textContent) { 1476 if (static_cast<SVGElement*>(node)->isTextContent()) 1477 textContent = static_cast<SVGTextContentElement*>(node); 1478 else 1479 node = node->parentNode(); 1480 } 1481 ASSERT(textContent); 1482 1483 // Start new character range for the first chunk 1484 bool isFirstCharacter = info.svgTextChunks.isEmpty() && info.chunk.start == info.it && info.chunk.start == info.chunk.end; 1485 if (isFirstCharacter) { 1486 ASSERT(info.chunk.boxes.isEmpty()); 1487 info.chunk.boxes.append(SVGInlineBoxCharacterRange()); 1488 } else 1489 ASSERT(!info.chunk.boxes.isEmpty()); 1490 1491 // Walk string to find out new chunk positions, if existent 1492 for (unsigned i = 0; i < length; ++i) { 1493 ASSERT(info.it != svgChars.end()); 1494 1495 SVGInlineBoxCharacterRange& range = info.chunk.boxes.last(); 1496 if (range.isOpen()) { 1497 range.box = curr; 1498 range.startOffset = (i == 0 ? 0 : i - 1); 1499 1500 #if DEBUG_CHUNK_BUILDING > 1 1501 fprintf(stderr, " | -> Range is open! box=%p, startOffset=%i\n", range.box, range.startOffset); 1502 #endif 1503 } 1504 1505 // If a new (or the first) chunk has been started, record it's text-anchor and writing mode. 1506 if (info.assignChunkProperties) { 1507 info.assignChunkProperties = false; 1508 1509 info.chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle()); 1510 info.chunk.isTextPath = info.handlingTextPath; 1511 info.chunk.anchor = text->style()->svgStyle()->textAnchor(); 1512 info.chunk.textLength = textContent->textLength().value(textContent); 1513 info.chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); 1514 1515 #if DEBUG_CHUNK_BUILDING > 1 1516 fprintf(stderr, " | -> Assign chunk properties, isVerticalText=%i, anchor=%i\n", info.chunk.isVerticalText, info.chunk.anchor); 1517 #endif 1518 } 1519 1520 if (i > 0 && !isFirstCharacter && (*info.it).newTextChunk) { 1521 // Close mid chunk & character range 1522 ASSERT(!range.isOpen()); 1523 ASSERT(!range.isClosed()); 1524 1525 range.endOffset = i; 1526 closeTextChunk(info); 1527 1528 #if DEBUG_CHUNK_BUILDING > 1 1529 fprintf(stderr, " | -> Close mid-text chunk, at endOffset: %i and starting new mid chunk!\n", range.endOffset); 1530 #endif 1531 1532 // Prepare for next chunk, if we're not at the end 1533 startTextChunk(info); 1534 if (i + 1 == length) { 1535 #if DEBUG_CHUNK_BUILDING > 1 1536 fprintf(stderr, " | -> Record last chunk of inline text box!\n"); 1537 #endif 1538 1539 startTextChunk(info); 1540 SVGInlineBoxCharacterRange& range = info.chunk.boxes.last(); 1541 1542 info.assignChunkProperties = false; 1543 info.chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle()); 1544 info.chunk.isTextPath = info.handlingTextPath; 1545 info.chunk.anchor = text->style()->svgStyle()->textAnchor(); 1546 info.chunk.textLength = textContent->textLength().value(textContent); 1547 info.chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); 1548 1549 range.box = curr; 1550 range.startOffset = i; 1551 1552 ASSERT(!range.isOpen()); 1553 ASSERT(!range.isClosed()); 1554 } 1555 } 1556 1557 // This should only hold true for the first character of the first chunk 1558 if (isFirstCharacter) 1559 isFirstCharacter = false; 1560 1561 ++info.it; 1562 } 1563 1564 #if DEBUG_CHUNK_BUILDING > 1 1565 fprintf(stderr, " -> Finished inline text box!\n"); 1566 #endif 1567 1568 SVGInlineBoxCharacterRange& range = info.chunk.boxes.last(); 1569 if (!range.isOpen() && !range.isClosed()) { 1570 #if DEBUG_CHUNK_BUILDING > 1 1571 fprintf(stderr, " -> Last range not closed - closing with endOffset: %i\n", length); 1572 #endif 1573 1574 // Current text chunk is not yet closed. Finish the current range, but don't start a new chunk. 1575 range.endOffset = length; 1576 1577 if (info.it != svgChars.end()) { 1578 #if DEBUG_CHUNK_BUILDING > 1 1579 fprintf(stderr, " -> Not at last character yet!\n"); 1580 #endif 1581 1582 // If we're not at the end of the last box to be processed, and if the next 1583 // character starts a new chunk, then close the current chunk and start a new one. 1584 if ((*info.it).newTextChunk) { 1585 #if DEBUG_CHUNK_BUILDING > 1 1586 fprintf(stderr, " -> Next character starts new chunk! Closing current chunk, and starting a new one...\n"); 1587 #endif 1588 1589 closeTextChunk(info); 1590 startTextChunk(info); 1591 } else { 1592 // Just start a new character range 1593 info.chunk.boxes.append(SVGInlineBoxCharacterRange()); 1594 1595 #if DEBUG_CHUNK_BUILDING > 1 1596 fprintf(stderr, " -> Next character does NOT start a new chunk! Starting new character range...\n"); 1597 #endif 1598 } 1599 } else { 1600 #if DEBUG_CHUNK_BUILDING > 1 1601 fprintf(stderr, " -> Closing final chunk! Finished processing!\n"); 1602 #endif 1603 1604 // Close final chunk, once we're at the end of the last box 1605 closeTextChunk(info); 1606 } 1607 } 1608 } else { 1609 ASSERT(curr->isInlineFlowBox()); 1610 InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(curr); 1611 1612 if (!flowBox->renderer()->node()) 1613 continue; // Skip generated content. 1614 1615 bool isTextPath = flowBox->renderer()->node()->hasTagName(SVGNames::textPathTag); 1616 1617 #if DEBUG_CHUNK_BUILDING > 1 1618 fprintf(stderr, " -> Handle inline flow box (%p), isTextPath=%i\n", flowBox, (int) isTextPath); 1619 #endif 1620 1621 if (isTextPath) 1622 info.handlingTextPath = true; 1623 1624 buildTextChunks(svgChars, flowBox, info); 1625 1626 if (isTextPath) 1627 info.handlingTextPath = false; 1628 } 1629 } 1630 1631 #if DEBUG_CHUNK_BUILDING > 1 1632 fprintf(stderr, " <- buildTextChunks(start=%p)\n", start); 1633 #endif 1634 } 1635 1636 const Vector<SVGTextChunk>& SVGRootInlineBox::svgTextChunks() const 1637 { 1638 return m_svgTextChunks; 1639 } 1640 1641 void SVGRootInlineBox::layoutTextChunks() 1642 { 1643 Vector<SVGTextChunk>::iterator it = m_svgTextChunks.begin(); 1644 Vector<SVGTextChunk>::iterator end = m_svgTextChunks.end(); 1645 1646 for (; it != end; ++it) { 1647 SVGTextChunk& chunk = *it; 1648 1649 #if DEBUG_CHUNK_BUILDING > 0 1650 { 1651 fprintf(stderr, "Handle TEXT CHUNK! anchor=%i, textLength=%f, lengthAdjust=%i, isVerticalText=%i, isTextPath=%i start=%p, end=%p -> dist: %i\n", 1652 (int) chunk.anchor, chunk.textLength, (int) chunk.lengthAdjust, (int) chunk.isVerticalText, 1653 (int) chunk.isTextPath, chunk.start, chunk.end, (unsigned int) (chunk.end - chunk.start)); 1654 1655 Vector<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin(); 1656 Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = chunk.boxes.end(); 1657 1658 unsigned int i = 0; 1659 for (; boxIt != boxEnd; ++boxIt) { 1660 SVGInlineBoxCharacterRange& range = *boxIt; i++; 1661 fprintf(stderr, " -> RANGE %i STARTOFFSET: %i, ENDOFFSET: %i, BOX: %p\n", i, range.startOffset, range.endOffset, range.box); 1662 } 1663 } 1664 #endif 1665 1666 if (chunk.isTextPath) 1667 continue; 1668 1669 // text-path & textLength, with lengthAdjust="spacing" is already handled for textPath layouts. 1670 applyTextLengthCorrectionToTextChunk(chunk); 1671 1672 // text-anchor is already handled for textPath layouts. 1673 applyTextAnchorToTextChunk(chunk); 1674 } 1675 } 1676 1677 static inline void addPaintServerToTextDecorationInfo(ETextDecoration decoration, SVGTextDecorationInfo& info, RenderObject* object) 1678 { 1679 if (object->style()->svgStyle()->hasFill()) 1680 info.fillServerMap.set(decoration, object); 1681 1682 if (object->style()->svgStyle()->hasStroke()) 1683 info.strokeServerMap.set(decoration, object); 1684 } 1685 1686 SVGTextDecorationInfo SVGRootInlineBox::retrievePaintServersForTextDecoration(RenderObject* start) 1687 { 1688 ASSERT(start); 1689 1690 Vector<RenderObject*> parentChain; 1691 while ((start = start->parent())) { 1692 parentChain.prepend(start); 1693 1694 // Stop at our direct <text> parent. 1695 if (start->isSVGText()) 1696 break; 1697 } 1698 1699 Vector<RenderObject*>::iterator it = parentChain.begin(); 1700 Vector<RenderObject*>::iterator end = parentChain.end(); 1701 1702 SVGTextDecorationInfo info; 1703 1704 for (; it != end; ++it) { 1705 RenderObject* object = *it; 1706 ASSERT(object); 1707 1708 RenderStyle* style = object->style(); 1709 ASSERT(style); 1710 1711 int decorations = style->textDecoration(); 1712 if (decorations != NONE) { 1713 if (decorations & OVERLINE) 1714 addPaintServerToTextDecorationInfo(OVERLINE, info, object); 1715 1716 if (decorations & UNDERLINE) 1717 addPaintServerToTextDecorationInfo(UNDERLINE, info, object); 1718 1719 if (decorations & LINE_THROUGH) 1720 addPaintServerToTextDecorationInfo(LINE_THROUGH, info, object); 1721 } 1722 } 1723 1724 return info; 1725 } 1726 1727 void SVGRootInlineBox::walkTextChunks(SVGTextChunkWalkerBase* walker, const SVGInlineTextBox* textBox) 1728 { 1729 ASSERT(walker); 1730 1731 Vector<SVGTextChunk>::iterator it = m_svgTextChunks.begin(); 1732 Vector<SVGTextChunk>::iterator itEnd = m_svgTextChunks.end(); 1733 1734 for (; it != itEnd; ++it) { 1735 SVGTextChunk& curChunk = *it; 1736 1737 Vector<SVGInlineBoxCharacterRange>::iterator boxIt = curChunk.boxes.begin(); 1738 Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = curChunk.boxes.end(); 1739 1740 InlineBox* lastNotifiedBox = 0; 1741 InlineBox* prevBox = 0; 1742 1743 unsigned int chunkOffset = 0; 1744 bool startedFirstChunk = false; 1745 1746 for (; boxIt != boxEnd; ++boxIt) { 1747 SVGInlineBoxCharacterRange& range = *boxIt; 1748 1749 ASSERT(range.box->isInlineTextBox()); 1750 SVGInlineTextBox* rangeTextBox = static_cast<SVGInlineTextBox*>(range.box); 1751 1752 if (textBox && rangeTextBox != textBox) { 1753 chunkOffset += range.endOffset - range.startOffset; 1754 continue; 1755 } 1756 1757 // Eventually notify that we started a new chunk 1758 if (!textBox && !startedFirstChunk) { 1759 startedFirstChunk = true; 1760 1761 lastNotifiedBox = range.box; 1762 walker->start(range.box); 1763 } else { 1764 // Eventually apply new style, as this chunk spans multiple boxes (with possible different styling) 1765 if (prevBox && prevBox != range.box) { 1766 lastNotifiedBox = range.box; 1767 1768 walker->end(prevBox); 1769 walker->start(lastNotifiedBox); 1770 } 1771 } 1772 1773 unsigned int length = range.endOffset - range.startOffset; 1774 1775 Vector<SVGChar>::iterator itCharBegin = curChunk.start + chunkOffset; 1776 Vector<SVGChar>::iterator itCharEnd = curChunk.start + chunkOffset + length; 1777 ASSERT(itCharEnd <= curChunk.end); 1778 1779 // Process this chunk portion 1780 if (textBox) 1781 (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); 1782 else { 1783 if (walker->setupBackground(range.box)) 1784 (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); 1785 1786 if (walker->setupFill(range.box)) 1787 (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); 1788 1789 if (walker->setupFillSelection(range.box)) 1790 (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); 1791 1792 if (walker->setupStroke(range.box)) 1793 (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); 1794 1795 if (walker->setupStrokeSelection(range.box)) 1796 (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); 1797 1798 if (walker->setupForeground(range.box)) 1799 (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); 1800 1801 } 1802 1803 chunkOffset += length; 1804 1805 if (!textBox) 1806 prevBox = range.box; 1807 } 1808 1809 if (!textBox && startedFirstChunk) 1810 walker->end(lastNotifiedBox); 1811 } 1812 } 1813 1814 } // namespace WebCore 1815 1816 #endif // ENABLE(SVG) 1817