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