1/** 2 * Copyright (C) 2007 Rob Buis <buis@kde.org> 3 * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> 4 * Copyright (C) Research In Motion Limited 2010. All rights reserved. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Library General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Library General Public License for more details. 15 * 16 * You should have received a copy of the GNU Library General Public License 17 * along with this library; see the file COPYING.LIB. If not, write to 18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA. 20 */ 21 22#include "config.h" 23#include "SVGInlineTextBox.h" 24 25#include "FontCache.h" 26#include "Frame.h" 27#include "FrameView.h" 28#include "GraphicsContext.h" 29#include "HitTestResult.h" 30#include "InlineFlowBox.h" 31#include "PointerEventsHitRules.h" 32#include "RenderBlock.h" 33#include "RenderInline.h" 34#include "RenderSVGResourceSolidColor.h" 35#include "RenderView.h" 36#include "SVGRenderingContext.h" 37#include "SVGResourcesCache.h" 38#include "SVGRootInlineBox.h" 39#include "SVGTextRunRenderingContext.h" 40 41namespace WebCore { 42 43struct ExpectedSVGInlineTextBoxSize : public InlineTextBox { 44 float float1; 45 uint32_t bitfields : 5; 46 void* pointer; 47 Vector<SVGTextFragment> vector; 48}; 49 50COMPILE_ASSERT(sizeof(SVGInlineTextBox) == sizeof(ExpectedSVGInlineTextBoxSize), SVGInlineTextBox_is_not_of_expected_size); 51 52SVGInlineTextBox::SVGInlineTextBox(RenderSVGInlineText& renderer) 53 : InlineTextBox(renderer) 54 , m_logicalHeight(0) 55 , m_paintingResourceMode(ApplyToDefaultMode) 56 , m_startsNewTextChunk(false) 57 , m_paintingResource(0) 58{ 59} 60 61void SVGInlineTextBox::dirtyOwnLineBoxes() 62{ 63 InlineTextBox::dirtyLineBoxes(); 64 65 // Clear the now stale text fragments 66 clearTextFragments(); 67} 68 69void SVGInlineTextBox::dirtyLineBoxes() 70{ 71 dirtyOwnLineBoxes(); 72 73 // And clear any following text fragments as the text on which they 74 // depend may now no longer exist, or glyph positions may be wrong 75 for (InlineTextBox* nextBox = nextTextBox(); nextBox; nextBox = nextBox->nextTextBox()) 76 nextBox->dirtyOwnLineBoxes(); 77} 78 79int SVGInlineTextBox::offsetForPosition(float, bool) const 80{ 81 // SVG doesn't use the standard offset <-> position selection system, as it's not suitable for SVGs complex needs. 82 // vertical text selection, inline boxes spanning multiple lines (contrary to HTML, etc.) 83 ASSERT_NOT_REACHED(); 84 return 0; 85} 86 87int SVGInlineTextBox::offsetForPositionInFragment(const SVGTextFragment& fragment, float position, bool includePartialGlyphs) const 88{ 89 float scalingFactor = renderer().scalingFactor(); 90 ASSERT(scalingFactor); 91 92 TextRun textRun = constructTextRun(&renderer().style(), fragment); 93 94 // Eventually handle lengthAdjust="spacingAndGlyphs". 95 // FIXME: Handle vertical text. 96 AffineTransform fragmentTransform; 97 fragment.buildFragmentTransform(fragmentTransform); 98 if (!fragmentTransform.isIdentity()) 99 textRun.setHorizontalGlyphStretch(narrowPrecisionToFloat(fragmentTransform.xScale())); 100 101 return fragment.characterOffset - start() + renderer().scaledFont().offsetForPosition(textRun, position * scalingFactor, includePartialGlyphs); 102} 103 104float SVGInlineTextBox::positionForOffset(int) const 105{ 106 // SVG doesn't use the offset <-> position selection system. 107 ASSERT_NOT_REACHED(); 108 return 0; 109} 110 111FloatRect SVGInlineTextBox::selectionRectForTextFragment(const SVGTextFragment& fragment, int startPosition, int endPosition, RenderStyle* style) const 112{ 113 ASSERT_WITH_SECURITY_IMPLICATION(startPosition < endPosition); 114 ASSERT(style); 115 116 FontCachePurgePreventer fontCachePurgePreventer; 117 118 float scalingFactor = renderer().scalingFactor(); 119 ASSERT(scalingFactor); 120 121 const Font& scaledFont = renderer().scaledFont(); 122 const FontMetrics& scaledFontMetrics = scaledFont.fontMetrics(); 123 FloatPoint textOrigin(fragment.x, fragment.y); 124 if (scalingFactor != 1) 125 textOrigin.scale(scalingFactor, scalingFactor); 126 127 textOrigin.move(0, -scaledFontMetrics.floatAscent()); 128 129 LayoutRect selectionRect = LayoutRect(textOrigin, LayoutSize(0, fragment.height * scalingFactor)); 130 TextRun run = constructTextRun(style, fragment); 131 scaledFont.adjustSelectionRectForText(run, selectionRect, startPosition, endPosition); 132 FloatRect snappedSelectionRect = directionalPixelSnappedForPainting(selectionRect, renderer().document().deviceScaleFactor(), run.ltr()); 133 if (scalingFactor == 1) 134 return snappedSelectionRect; 135 136 snappedSelectionRect.scale(1 / scalingFactor); 137 return snappedSelectionRect; 138} 139 140LayoutRect SVGInlineTextBox::localSelectionRect(int startPosition, int endPosition) const 141{ 142 int boxStart = start(); 143 startPosition = std::max(startPosition - boxStart, 0); 144 endPosition = std::min(endPosition - boxStart, static_cast<int>(len())); 145 if (startPosition >= endPosition) 146 return LayoutRect(); 147 148 RenderStyle& style = renderer().style(); 149 150 AffineTransform fragmentTransform; 151 FloatRect selectionRect; 152 int fragmentStartPosition = 0; 153 int fragmentEndPosition = 0; 154 155 unsigned textFragmentsSize = m_textFragments.size(); 156 for (unsigned i = 0; i < textFragmentsSize; ++i) { 157 const SVGTextFragment& fragment = m_textFragments.at(i); 158 159 fragmentStartPosition = startPosition; 160 fragmentEndPosition = endPosition; 161 if (!mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) 162 continue; 163 164 FloatRect fragmentRect = selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, &style); 165 fragment.buildFragmentTransform(fragmentTransform); 166 if (!fragmentTransform.isIdentity()) 167 fragmentRect = fragmentTransform.mapRect(fragmentRect); 168 169 selectionRect.unite(fragmentRect); 170 } 171 172 return enclosingIntRect(selectionRect); 173} 174 175static inline bool textShouldBePainted(const RenderSVGInlineText& textRenderer) 176{ 177 // Font::pixelSize(), returns FontDescription::computedPixelSize(), which returns "int(x + 0.5)". 178 // If the absolute font size on screen is below x=0.5, don't render anything. 179 return textRenderer.scaledFont().pixelSize(); 180} 181 182void SVGInlineTextBox::paintSelectionBackground(PaintInfo& paintInfo) 183{ 184 ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); 185 ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); 186 ASSERT(truncation() == cNoTruncation); 187 188 if (renderer().style().visibility() != VISIBLE) 189 return; 190 191 RenderObject& parentRenderer = parent()->renderer(); 192 ASSERT(!parentRenderer.document().printing()); 193 194 // Determine whether or not we're selected. 195 bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; 196 bool hasSelection = selectionState() != RenderObject::SelectionNone; 197 if (!hasSelection || paintSelectedTextOnly) 198 return; 199 200 Color backgroundColor = renderer().selectionBackgroundColor(); 201 if (!backgroundColor.isValid() || !backgroundColor.alpha()) 202 return; 203 204 if (!textShouldBePainted(renderer())) 205 return; 206 207 RenderStyle& style = parentRenderer.style(); 208 209 RenderStyle* selectionStyle = &style; 210 if (hasSelection) { 211 selectionStyle = parentRenderer.getCachedPseudoStyle(SELECTION); 212 if (!selectionStyle) 213 selectionStyle = &style; 214 } 215 216 int startPosition, endPosition; 217 selectionStartEnd(startPosition, endPosition); 218 219 int fragmentStartPosition = 0; 220 int fragmentEndPosition = 0; 221 AffineTransform fragmentTransform; 222 unsigned textFragmentsSize = m_textFragments.size(); 223 for (unsigned i = 0; i < textFragmentsSize; ++i) { 224 SVGTextFragment& fragment = m_textFragments.at(i); 225 ASSERT(!m_paintingResource); 226 227 fragmentStartPosition = startPosition; 228 fragmentEndPosition = endPosition; 229 if (!mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) 230 continue; 231 232 GraphicsContextStateSaver stateSaver(*paintInfo.context); 233 fragment.buildFragmentTransform(fragmentTransform); 234 if (!fragmentTransform.isIdentity()) 235 paintInfo.context->concatCTM(fragmentTransform); 236 237 paintInfo.context->setFillColor(backgroundColor, style.colorSpace()); 238 paintInfo.context->fillRect(selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, &style), backgroundColor, style.colorSpace()); 239 240 m_paintingResourceMode = ApplyToDefaultMode; 241 } 242 243 ASSERT(!m_paintingResource); 244} 245 246void SVGInlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit, LayoutUnit) 247{ 248 ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); 249 ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); 250 ASSERT(truncation() == cNoTruncation); 251 252 if (renderer().style().visibility() != VISIBLE) 253 return; 254 255 // Note: We're explicitely not supporting composition & custom underlines and custom highlighters - unlike InlineTextBox. 256 // If we ever need that for SVG, it's very easy to refactor and reuse the code. 257 258 RenderObject& parentRenderer = parent()->renderer(); 259 260 bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; 261 bool hasSelection = !parentRenderer.document().printing() && selectionState() != RenderObject::SelectionNone; 262 if (!hasSelection && paintSelectedTextOnly) 263 return; 264 265 if (!textShouldBePainted(renderer())) 266 return; 267 268 RenderStyle& style = parentRenderer.style(); 269 270 const SVGRenderStyle& svgStyle = style.svgStyle(); 271 272 bool hasFill = svgStyle.hasFill(); 273 bool hasVisibleStroke = svgStyle.hasVisibleStroke(); 274 275 RenderStyle* selectionStyle = &style; 276 if (hasSelection) { 277 selectionStyle = parentRenderer.getCachedPseudoStyle(SELECTION); 278 if (selectionStyle) { 279 const SVGRenderStyle& svgSelectionStyle = selectionStyle->svgStyle(); 280 281 if (!hasFill) 282 hasFill = svgSelectionStyle.hasFill(); 283 if (!hasVisibleStroke) 284 hasVisibleStroke = svgSelectionStyle.hasVisibleStroke(); 285 } else 286 selectionStyle = &style; 287 } 288 289 if (renderer().view().frameView().paintBehavior() & PaintBehaviorRenderingSVGMask) { 290 hasFill = true; 291 hasVisibleStroke = false; 292 } 293 294 AffineTransform fragmentTransform; 295 unsigned textFragmentsSize = m_textFragments.size(); 296 for (unsigned i = 0; i < textFragmentsSize; ++i) { 297 SVGTextFragment& fragment = m_textFragments.at(i); 298 ASSERT(!m_paintingResource); 299 300 GraphicsContextStateSaver stateSaver(*paintInfo.context); 301 fragment.buildFragmentTransform(fragmentTransform); 302 if (!fragmentTransform.isIdentity()) 303 paintInfo.context->concatCTM(fragmentTransform); 304 305 // Spec: All text decorations except line-through should be drawn before the text is filled and stroked; thus, the text is rendered on top of these decorations. 306 int decorations = style.textDecorationsInEffect(); 307 if (decorations & TextDecorationUnderline) 308 paintDecoration(paintInfo.context, TextDecorationUnderline, fragment); 309 if (decorations & TextDecorationOverline) 310 paintDecoration(paintInfo.context, TextDecorationOverline, fragment); 311 312 Vector<PaintType> paintOrder = style.svgStyle().paintTypesForPaintOrder(); 313 for (unsigned i = 0; i < paintOrder.size(); ++i) { 314 switch (paintOrder.at(i)) { 315 case PaintTypeFill: 316 if (!hasFill) 317 continue; 318 m_paintingResourceMode = ApplyToFillMode | ApplyToTextMode; 319 paintText(paintInfo.context, &style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); 320 break; 321 case PaintTypeStroke: 322 if (!hasVisibleStroke) 323 continue; 324 m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode; 325 paintText(paintInfo.context, &style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); 326 break; 327 case PaintTypeMarkers: 328 continue; 329 } 330 } 331 332 // Spec: Line-through should be drawn after the text is filled and stroked; thus, the line-through is rendered on top of the text. 333 if (decorations & TextDecorationLineThrough) 334 paintDecoration(paintInfo.context, TextDecorationLineThrough, fragment); 335 336 m_paintingResourceMode = ApplyToDefaultMode; 337 } 338 339 // Finally, paint the outline if any. 340 if (renderer().style().hasOutline() && parentRenderer.isRenderInline()) 341 toRenderInline(parentRenderer).paintOutline(paintInfo, paintOffset); 342 343 ASSERT(!m_paintingResource); 344} 345 346bool SVGInlineTextBox::acquirePaintingResource(GraphicsContext*& context, float scalingFactor, RenderBoxModelObject& renderer, RenderStyle* style) 347{ 348 ASSERT(scalingFactor); 349 ASSERT(style); 350 ASSERT(m_paintingResourceMode != ApplyToDefaultMode); 351 352 Color fallbackColor; 353 if (m_paintingResourceMode & ApplyToFillMode) 354 m_paintingResource = RenderSVGResource::fillPaintingResource(renderer, *style, fallbackColor); 355 else if (m_paintingResourceMode & ApplyToStrokeMode) 356 m_paintingResource = RenderSVGResource::strokePaintingResource(renderer, *style, fallbackColor); 357 else { 358 // We're either called for stroking or filling. 359 ASSERT_NOT_REACHED(); 360 } 361 362 if (!m_paintingResource) 363 return false; 364 365 if (!m_paintingResource->applyResource(renderer, *style, context, m_paintingResourceMode)) { 366 if (fallbackColor.isValid()) { 367 RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); 368 fallbackResource->setColor(fallbackColor); 369 370 m_paintingResource = fallbackResource; 371 m_paintingResource->applyResource(renderer, *style, context, m_paintingResourceMode); 372 } 373 } 374 375 if (scalingFactor != 1 && m_paintingResourceMode & ApplyToStrokeMode) 376 context->setStrokeThickness(context->strokeThickness() * scalingFactor); 377 378 return true; 379} 380 381void SVGInlineTextBox::releasePaintingResource(GraphicsContext*& context, const Path* path) 382{ 383 ASSERT(m_paintingResource); 384 385 m_paintingResource->postApplyResource(parent()->renderer(), context, m_paintingResourceMode, path, /*RenderSVGShape*/ 0); 386 m_paintingResource = nullptr; 387} 388 389bool SVGInlineTextBox::prepareGraphicsContextForTextPainting(GraphicsContext*& context, float scalingFactor, TextRun& textRun, RenderStyle* style) 390{ 391 bool acquiredResource = acquirePaintingResource(context, scalingFactor, parent()->renderer(), style); 392 if (!acquiredResource) 393 return false; 394 395#if ENABLE(SVG_FONTS) 396 // SVG Fonts need access to the painting resource used to draw the current text chunk. 397 TextRun::RenderingContext* renderingContext = textRun.renderingContext(); 398 if (renderingContext) 399 static_cast<SVGTextRunRenderingContext*>(renderingContext)->setActivePaintingResource(m_paintingResource); 400#endif 401 402 return true; 403} 404 405void SVGInlineTextBox::restoreGraphicsContextAfterTextPainting(GraphicsContext*& context, TextRun& textRun) 406{ 407 releasePaintingResource(context, /* path */0); 408 409#if ENABLE(SVG_FONTS) 410 TextRun::RenderingContext* renderingContext = textRun.renderingContext(); 411 if (renderingContext) 412 static_cast<SVGTextRunRenderingContext*>(renderingContext)->setActivePaintingResource(0); 413#else 414 UNUSED_PARAM(textRun); 415#endif 416} 417 418TextRun SVGInlineTextBox::constructTextRun(RenderStyle* style, const SVGTextFragment& fragment) const 419{ 420 ASSERT(style); 421 422 TextRun run(StringView(renderer().text()).substring(fragment.characterOffset, fragment.length) 423 , 0 /* xPos, only relevant with allowTabs=true */ 424 , 0 /* padding, only relevant for justified text, not relevant for SVG */ 425 , TextRun::AllowTrailingExpansion 426 , direction() 427 , dirOverride() || style->rtlOrdering() == VisualOrder /* directionalOverride */); 428 429 if (style->font().primaryFont()->isSVGFont()) 430 run.setRenderingContext(SVGTextRunRenderingContext::create(renderer())); 431 432 run.disableRoundingHacks(); 433 434 // We handle letter & word spacing ourselves. 435 run.disableSpacing(); 436 437 // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring. 438 run.setCharactersLength(renderer().textLength() - fragment.characterOffset); 439 ASSERT(run.charactersLength() >= run.length()); 440 return run; 441} 442 443bool SVGInlineTextBox::mapStartEndPositionsIntoFragmentCoordinates(const SVGTextFragment& fragment, int& startPosition, int& endPosition) const 444{ 445 if (startPosition >= endPosition) 446 return false; 447 448 int offset = static_cast<int>(fragment.characterOffset) - start(); 449 int length = static_cast<int>(fragment.length); 450 451 if (startPosition >= offset + length || endPosition <= offset) 452 return false; 453 454 if (startPosition < offset) 455 startPosition = 0; 456 else 457 startPosition -= offset; 458 459 if (endPosition > offset + length) 460 endPosition = length; 461 else { 462 ASSERT(endPosition >= offset); 463 endPosition -= offset; 464 } 465 466 ASSERT_WITH_SECURITY_IMPLICATION(startPosition < endPosition); 467 return true; 468} 469 470static inline float positionOffsetForDecoration(TextDecoration decoration, const FontMetrics& fontMetrics, float thickness) 471{ 472 // FIXME: For SVG Fonts we need to use the attributes defined in the <font-face> if specified. 473 // Compatible with Batik/Opera. 474 if (decoration == TextDecorationUnderline) 475 return fontMetrics.floatAscent() + thickness * 1.5f; 476 if (decoration == TextDecorationOverline) 477 return thickness; 478 if (decoration == TextDecorationLineThrough) 479 return fontMetrics.floatAscent() * 5 / 8.0f; 480 481 ASSERT_NOT_REACHED(); 482 return 0.0f; 483} 484 485static inline float thicknessForDecoration(TextDecoration, const Font& font) 486{ 487 // FIXME: For SVG Fonts we need to use the attributes defined in the <font-face> if specified. 488 // Compatible with Batik/Opera 489 return font.size() / 20.0f; 490} 491 492static inline RenderBoxModelObject& findRendererDefininingTextDecoration(InlineFlowBox* parentBox) 493{ 494 // Lookup first render object in parent hierarchy which has text-decoration set. 495 RenderBoxModelObject* renderer = nullptr; 496 while (parentBox) { 497 renderer = &parentBox->renderer(); 498 499 if (renderer->style().textDecoration() != TextDecorationNone) 500 break; 501 502 parentBox = parentBox->parent(); 503 } 504 505 ASSERT(renderer); 506 return *renderer; 507} 508 509void SVGInlineTextBox::paintDecoration(GraphicsContext* context, TextDecoration decoration, const SVGTextFragment& fragment) 510{ 511 if (renderer().style().textDecorationsInEffect() == TextDecorationNone) 512 return; 513 514 // Find out which render style defined the text-decoration, as its fill/stroke properties have to be used for drawing instead of ours. 515 auto& decorationRenderer = findRendererDefininingTextDecoration(parent()); 516 const RenderStyle& decorationStyle = decorationRenderer.style(); 517 518 if (decorationStyle.visibility() == HIDDEN) 519 return; 520 521 const SVGRenderStyle& svgDecorationStyle = decorationStyle.svgStyle(); 522 523 bool hasDecorationFill = svgDecorationStyle.hasFill(); 524 bool hasVisibleDecorationStroke = svgDecorationStyle.hasVisibleStroke(); 525 526 if (hasDecorationFill) { 527 m_paintingResourceMode = ApplyToFillMode; 528 paintDecorationWithStyle(context, decoration, fragment, decorationRenderer); 529 } 530 531 if (hasVisibleDecorationStroke) { 532 m_paintingResourceMode = ApplyToStrokeMode; 533 paintDecorationWithStyle(context, decoration, fragment, decorationRenderer); 534 } 535} 536 537void SVGInlineTextBox::paintDecorationWithStyle(GraphicsContext* context, TextDecoration decoration, const SVGTextFragment& fragment, RenderBoxModelObject& decorationRenderer) 538{ 539 ASSERT(!m_paintingResource); 540 ASSERT(m_paintingResourceMode != ApplyToDefaultMode); 541 542 RenderStyle& decorationStyle = decorationRenderer.style(); 543 544 float scalingFactor = 1; 545 Font scaledFont; 546 RenderSVGInlineText::computeNewScaledFontForStyle(decorationRenderer, decorationStyle, scalingFactor, scaledFont); 547 ASSERT(scalingFactor); 548 549 // The initial y value refers to overline position. 550 float thickness = thicknessForDecoration(decoration, scaledFont); 551 552 if (fragment.width <= 0 && thickness <= 0) 553 return; 554 555 FloatPoint decorationOrigin(fragment.x, fragment.y); 556 float width = fragment.width; 557 const FontMetrics& scaledFontMetrics = scaledFont.fontMetrics(); 558 559 GraphicsContextStateSaver stateSaver(*context); 560 if (scalingFactor != 1) { 561 width *= scalingFactor; 562 decorationOrigin.scale(scalingFactor, scalingFactor); 563 context->scale(FloatSize(1 / scalingFactor, 1 / scalingFactor)); 564 } 565 566 decorationOrigin.move(0, -scaledFontMetrics.floatAscent() + positionOffsetForDecoration(decoration, scaledFontMetrics, thickness)); 567 568 Path path; 569 path.addRect(FloatRect(decorationOrigin, FloatSize(width, thickness))); 570 571 if (acquirePaintingResource(context, scalingFactor, decorationRenderer, &decorationStyle)) 572 releasePaintingResource(context, &path); 573} 574 575void SVGInlineTextBox::paintTextWithShadows(GraphicsContext* context, RenderStyle* style, TextRun& textRun, const SVGTextFragment& fragment, int startPosition, int endPosition) 576{ 577 float scalingFactor = renderer().scalingFactor(); 578 ASSERT(scalingFactor); 579 580 const Font& scaledFont = renderer().scaledFont(); 581 const ShadowData* shadow = style->textShadow(); 582 583 FloatPoint textOrigin(fragment.x, fragment.y); 584 FloatSize textSize(fragment.width, fragment.height); 585 586 if (scalingFactor != 1) { 587 textOrigin.scale(scalingFactor, scalingFactor); 588 textSize.scale(scalingFactor); 589 } 590 591 FloatRect shadowRect(FloatPoint(textOrigin.x(), textOrigin.y() - scaledFont.fontMetrics().floatAscent()), textSize); 592 593 do { 594 if (!prepareGraphicsContextForTextPainting(context, scalingFactor, textRun, style)) 595 break; 596 597 FloatSize extraOffset; 598 if (shadow) 599 extraOffset = applyShadowToGraphicsContext(context, shadow, shadowRect, false /* stroked */, true /* opaque */, true /* horizontal */); 600 601 context->save(); 602 context->scale(FloatSize(1 / scalingFactor, 1 / scalingFactor)); 603 604 scaledFont.drawText(context, textRun, textOrigin + extraOffset, startPosition, endPosition); 605 606 context->restore(); 607 608 if (shadow) { 609 if (shadow->next()) 610 context->restore(); 611 else 612 context->clearShadow(); 613 } 614 615 restoreGraphicsContextAfterTextPainting(context, textRun); 616 617 if (!shadow) 618 break; 619 620 shadow = shadow->next(); 621 } while (shadow); 622} 623 624void SVGInlineTextBox::paintText(GraphicsContext* context, RenderStyle* style, RenderStyle* selectionStyle, const SVGTextFragment& fragment, bool hasSelection, bool paintSelectedTextOnly) 625{ 626 ASSERT(style); 627 ASSERT(selectionStyle); 628 629 int startPosition = 0; 630 int endPosition = 0; 631 if (hasSelection) { 632 selectionStartEnd(startPosition, endPosition); 633 hasSelection = mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition); 634 } 635 636 // Fast path if there is no selection, just draw the whole chunk part using the regular style 637 TextRun textRun = constructTextRun(style, fragment); 638 if (!hasSelection || startPosition >= endPosition) { 639 paintTextWithShadows(context, style, textRun, fragment, 0, fragment.length); 640 return; 641 } 642 643 // Eventually draw text using regular style until the start position of the selection 644 if (startPosition > 0 && !paintSelectedTextOnly) 645 paintTextWithShadows(context, style, textRun, fragment, 0, startPosition); 646 647 // Draw text using selection style from the start to the end position of the selection 648 if (style != selectionStyle) 649 SVGResourcesCache::clientStyleChanged(parent()->renderer(), StyleDifferenceRepaint, *selectionStyle); 650 651 TextRun selectionTextRun = constructTextRun(selectionStyle, fragment); 652 paintTextWithShadows(context, selectionStyle, textRun, fragment, startPosition, endPosition); 653 654 if (style != selectionStyle) 655 SVGResourcesCache::clientStyleChanged(parent()->renderer(), StyleDifferenceRepaint, *style); 656 657 // Eventually draw text using regular style from the end position of the selection to the end of the current chunk part 658 if (endPosition < static_cast<int>(fragment.length) && !paintSelectedTextOnly) 659 paintTextWithShadows(context, style, textRun, fragment, endPosition, fragment.length); 660} 661 662FloatRect SVGInlineTextBox::calculateBoundaries() const 663{ 664 FloatRect textRect; 665 666 float scalingFactor = renderer().scalingFactor(); 667 ASSERT(scalingFactor); 668 669 float baseline = renderer().scaledFont().fontMetrics().floatAscent() / scalingFactor; 670 671 AffineTransform fragmentTransform; 672 unsigned textFragmentsSize = m_textFragments.size(); 673 for (unsigned i = 0; i < textFragmentsSize; ++i) { 674 const SVGTextFragment& fragment = m_textFragments.at(i); 675 FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); 676 fragment.buildFragmentTransform(fragmentTransform); 677 if (!fragmentTransform.isIdentity()) 678 fragmentRect = fragmentTransform.mapRect(fragmentRect); 679 680 textRect.unite(fragmentRect); 681 } 682 683 return textRect; 684} 685 686bool SVGInlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit, LayoutUnit) 687{ 688 // FIXME: integrate with InlineTextBox::nodeAtPoint better. 689 ASSERT(!isLineBreak()); 690 691 PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, request, renderer().style().pointerEvents()); 692 bool isVisible = renderer().style().visibility() == VISIBLE; 693 if (isVisible || !hitRules.requireVisible) { 694 if ((hitRules.canHitStroke && (renderer().style().svgStyle().hasStroke() || !hitRules.requireStroke)) 695 || (hitRules.canHitFill && (renderer().style().svgStyle().hasFill() || !hitRules.requireFill))) { 696 FloatPoint boxOrigin(x(), y()); 697 boxOrigin.moveBy(accumulatedOffset); 698 FloatRect rect(boxOrigin, size()); 699 if (locationInContainer.intersects(rect)) { 700 renderer().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); 701 if (!result.addNodeToRectBasedTestResult(&renderer().textNode(), request, locationInContainer, rect)) 702 return true; 703 } 704 } 705 } 706 return false; 707} 708 709} // namespace WebCore 710