1/*
2 * This file is part of the WebKit project.
3 *
4 * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
5 *           (C) 2006 Apple Computer Inc.
6 *           (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
7 *           (C) 2008 Rob Buis <buis@kde.org>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB.  If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 *
24 */
25
26#include "config.h"
27
28#if ENABLE(SVG)
29#include "RenderSVGInlineText.h"
30
31#include "FloatConversion.h"
32#include "FloatQuad.h"
33#include "RenderBlock.h"
34#include "RenderSVGRoot.h"
35#include "SVGInlineTextBox.h"
36#include "SVGRootInlineBox.h"
37#include "VisiblePosition.h"
38
39namespace WebCore {
40
41static inline bool isChildOfHiddenContainer(RenderObject* start)
42{
43    while (start) {
44        if (start->isSVGHiddenContainer())
45            return true;
46
47        start = start->parent();
48    }
49
50    return false;
51}
52
53RenderSVGInlineText::RenderSVGInlineText(Node* n, PassRefPtr<StringImpl> str)
54    : RenderText(n, str)
55{
56}
57
58
59void RenderSVGInlineText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
60{
61    // Skip RenderText's possible layout scheduling on style change
62    RenderObject::styleDidChange(diff, oldStyle);
63
64    // FIXME: SVG text is apparently always transformed?
65    if (RefPtr<StringImpl> textToTransform = originalText())
66        setText(textToTransform.release(), true);
67}
68
69void RenderSVGInlineText::absoluteRects(Vector<IntRect>& rects, int, int)
70{
71    rects.append(computeRepaintRectForRange(0, 0, textLength()));
72}
73
74void RenderSVGInlineText::absoluteQuads(Vector<FloatQuad>& quads)
75{
76    quads.append(computeRepaintQuadForRange(0, 0, textLength()));
77}
78
79IntRect RenderSVGInlineText::selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool /*clipToVisibleContent*/)
80{
81    ASSERT(!needsLayout());
82
83    if (selectionState() == SelectionNone)
84        return IntRect();
85
86    // Early exit if we're ie. a <text> within a <defs> section.
87    if (isChildOfHiddenContainer(this))
88        return IntRect();
89
90    // Now calculate startPos and endPos for painting selection.
91    // We include a selection while endPos > 0
92    int startPos, endPos;
93    if (selectionState() == SelectionInside) {
94        // We are fully selected.
95        startPos = 0;
96        endPos = textLength();
97    } else {
98        selectionStartEnd(startPos, endPos);
99        if (selectionState() == SelectionStart)
100            endPos = textLength();
101        else if (selectionState() == SelectionEnd)
102            startPos = 0;
103    }
104
105    if (startPos == endPos)
106        return IntRect();
107
108    return computeRepaintRectForRange(repaintContainer, startPos, endPos);
109}
110
111IntRect RenderSVGInlineText::computeRepaintRectForRange(RenderBoxModelObject* repaintContainer, int startPos, int endPos)
112{
113    FloatQuad repaintQuad = computeRepaintQuadForRange(repaintContainer, startPos, endPos);
114    return enclosingIntRect(repaintQuad.boundingBox());
115}
116
117FloatQuad RenderSVGInlineText::computeRepaintQuadForRange(RenderBoxModelObject* repaintContainer, int startPos, int endPos)
118{
119    RenderBlock* cb = containingBlock();
120    if (!cb || !cb->container())
121        return FloatQuad();
122
123    RenderSVGRoot* root = findSVGRootObject(parent());
124    if (!root)
125        return FloatQuad();
126
127    IntRect rect;
128    for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
129        rect.unite(box->selectionRect(0, 0, startPos, endPos));
130
131    return localToContainerQuad(FloatQuad(rect), repaintContainer);
132}
133
134InlineTextBox* RenderSVGInlineText::createTextBox()
135{
136    InlineTextBox* box = new (renderArena()) SVGInlineTextBox(this);
137    box->setHasVirtualHeight();
138    return box;
139}
140
141IntRect RenderSVGInlineText::localCaretRect(InlineBox*, int, int*)
142{
143    // SVG doesn't have any editable content where a caret rect would be needed.
144    // FIXME: That's not sufficient. The localCaretRect function is also used for selection.
145    return IntRect();
146}
147
148VisiblePosition RenderSVGInlineText::positionForPoint(const IntPoint& point)
149{
150    SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(firstTextBox());
151
152    if (!textBox || textLength() == 0)
153        return createVisiblePosition(0, DOWNSTREAM);
154
155    SVGRootInlineBox* rootBox = textBox->svgRootInlineBox();
156    RenderBlock* object = rootBox ? rootBox->block() : 0;
157
158    if (!object)
159        return createVisiblePosition(0, DOWNSTREAM);
160
161    int closestOffsetInBox = 0;
162
163    // FIXME: This approach is wrong.  The correct code would first find the
164    // closest SVGInlineTextBox to the point, and *then* ask only that inline box
165    // what the closest text offset to that point is.  This code instead walks
166    // through all boxes in order, so when you click "near" a box, you'll actually
167    // end up returning the nearest offset in the last box, even if the
168    // nearest offset to your click is contained in another box.
169    for (SVGInlineTextBox* box = textBox; box; box = static_cast<SVGInlineTextBox*>(box->nextTextBox())) {
170        if (box->svgCharacterHitsPosition(point.x() + object->x(), point.y() + object->y(), closestOffsetInBox)) {
171            // If we're not at the end/start of the box, stop looking for other selected boxes.
172            if (box->direction() == LTR) {
173                if (closestOffsetInBox <= (int) box->end() + 1)
174                    break;
175            } else {
176                if (closestOffsetInBox > (int) box->start())
177                    break;
178            }
179        }
180    }
181
182    return createVisiblePosition(closestOffsetInBox, DOWNSTREAM);
183}
184
185void RenderSVGInlineText::destroy()
186{
187    if (!documentBeingDestroyed()) {
188        setNeedsLayoutAndPrefWidthsRecalc();
189        repaint();
190    }
191    RenderText::destroy();
192}
193
194}
195
196#endif // ENABLE(SVG)
197