1/*
2    Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org>
3                  2004, 2005, 2008 Rob Buis <buis@kde.org>
4                  2005, 2007 Eric Seidel <eric@webkit.org>
5                  2009 Google, Inc.
6                  2009 Dirk Schulze <krit@webkit.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    aint 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#include "config.h"
25
26#if ENABLE(SVG)
27#include "RenderPath.h"
28
29#include "FloatPoint.h"
30#include "FloatQuad.h"
31#include "GraphicsContext.h"
32#include "PointerEventsHitRules.h"
33#include "RenderSVGContainer.h"
34#include "StrokeStyleApplier.h"
35#include "SVGPaintServer.h"
36#include "SVGRenderSupport.h"
37#include "SVGResourceFilter.h"
38#include "SVGResourceMarker.h"
39#include "SVGResourceMasker.h"
40#include "SVGStyledTransformableElement.h"
41#include "SVGTransformList.h"
42#include "SVGURIReference.h"
43#include <wtf/MathExtras.h>
44
45namespace WebCore {
46
47class BoundingRectStrokeStyleApplier : public StrokeStyleApplier {
48public:
49    BoundingRectStrokeStyleApplier(const RenderObject* object, RenderStyle* style)
50        : m_object(object)
51        , m_style(style)
52    {
53        ASSERT(style);
54        ASSERT(object);
55    }
56
57    void strokeStyle(GraphicsContext* gc)
58    {
59        applyStrokeStyleToContext(gc, m_style, m_object);
60    }
61
62private:
63    const RenderObject* m_object;
64    RenderStyle* m_style;
65};
66
67RenderPath::RenderPath(SVGStyledTransformableElement* node)
68    : RenderSVGModelObject(node)
69{
70}
71
72const AffineTransform& RenderPath::localToParentTransform() const
73{
74    return m_localTransform;
75}
76
77AffineTransform RenderPath::localTransform() const
78{
79    return m_localTransform;
80}
81
82bool RenderPath::fillContains(const FloatPoint& point, bool requiresFill) const
83{
84    if (m_path.isEmpty())
85        return false;
86
87    if (requiresFill && !SVGPaintServer::fillPaintServer(style(), this))
88        return false;
89
90    return m_path.contains(point, style()->svgStyle()->fillRule());
91}
92
93bool RenderPath::strokeContains(const FloatPoint& point, bool requiresStroke) const
94{
95    if (m_path.isEmpty())
96        return false;
97
98    if (requiresStroke && !SVGPaintServer::strokePaintServer(style(), this))
99        return false;
100
101    BoundingRectStrokeStyleApplier strokeStyle(this, style());
102    return m_path.strokeContains(&strokeStyle, point);
103}
104
105FloatRect RenderPath::objectBoundingBox() const
106{
107    if (m_path.isEmpty())
108        return FloatRect();
109
110    if (m_cachedLocalFillBBox.isEmpty())
111        m_cachedLocalFillBBox = m_path.boundingRect();
112
113    return m_cachedLocalFillBBox;
114}
115
116FloatRect RenderPath::strokeBoundingBox() const
117{
118    if (m_path.isEmpty())
119        return FloatRect();
120
121    if (!m_cachedLocalStrokeBBox.isEmpty())
122        return m_cachedLocalStrokeBBox;
123
124    m_cachedLocalStrokeBBox = objectBoundingBox();
125    if (style()->svgStyle()->hasStroke()) {
126        BoundingRectStrokeStyleApplier strokeStyle(this, style());
127        m_cachedLocalStrokeBBox.unite(m_path.strokeBoundingRect(&strokeStyle));
128    }
129
130    return m_cachedLocalStrokeBBox;
131}
132
133FloatRect RenderPath::markerBoundingBox() const
134{
135    if (m_path.isEmpty())
136        return FloatRect();
137
138    if (m_cachedLocalMarkerBBox.isEmpty())
139        calculateMarkerBoundsIfNeeded();
140
141    return m_cachedLocalMarkerBBox;
142}
143
144FloatRect RenderPath::repaintRectInLocalCoordinates() const
145{
146    if (m_path.isEmpty())
147        return FloatRect();
148
149    // If we already have a cached repaint rect, return that
150    if (!m_cachedLocalRepaintRect.isEmpty())
151        return m_cachedLocalRepaintRect;
152
153    // FIXME: We need to be careful here. We assume that there is no filter,
154    // clipper, marker or masker if the rects are empty.
155    FloatRect rect = filterBoundingBoxForRenderer(this);
156    if (!rect.isEmpty())
157        m_cachedLocalRepaintRect = rect;
158    else {
159        m_cachedLocalRepaintRect = strokeBoundingBox();
160        m_cachedLocalRepaintRect.unite(markerBoundingBox());
161    }
162
163    rect = clipperBoundingBoxForRenderer(this);
164    if (!rect.isEmpty())
165        m_cachedLocalRepaintRect.intersect(rect);
166
167    rect = maskerBoundingBoxForRenderer(this);
168    if (!rect.isEmpty())
169        m_cachedLocalRepaintRect.intersect(rect);
170
171    style()->svgStyle()->inflateForShadow(m_cachedLocalRepaintRect);
172
173    return m_cachedLocalRepaintRect;
174}
175
176void RenderPath::setPath(const Path& newPath)
177{
178    m_path = newPath;
179    m_cachedLocalRepaintRect = FloatRect();
180    m_cachedLocalStrokeBBox = FloatRect();
181    m_cachedLocalFillBBox = FloatRect();
182    m_cachedLocalMarkerBBox = FloatRect();
183}
184
185void RenderPath::layout()
186{
187    LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && selfNeedsLayout());
188
189    SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node());
190    m_localTransform = element->animatedLocalTransform();
191    setPath(element->toPathData());
192
193    repainter.repaintAfterLayout();
194    setNeedsLayout(false);
195}
196
197static inline void fillAndStrokePath(const Path& path, GraphicsContext* context, RenderStyle* style, RenderPath* object)
198{
199    context->beginPath();
200
201    SVGPaintServer* fillPaintServer = SVGPaintServer::fillPaintServer(style, object);
202    if (fillPaintServer) {
203        context->addPath(path);
204        fillPaintServer->draw(context, object, ApplyToFillTargetType);
205    }
206
207    SVGPaintServer* strokePaintServer = SVGPaintServer::strokePaintServer(style, object);
208    if (strokePaintServer) {
209        context->addPath(path); // path is cleared when filled.
210        strokePaintServer->draw(context, object, ApplyToStrokeTargetType);
211    }
212}
213
214void RenderPath::paint(PaintInfo& paintInfo, int, int)
215{
216    if (paintInfo.context->paintingDisabled() || style()->visibility() == HIDDEN || m_path.isEmpty())
217        return;
218
219    FloatRect boundingBox = repaintRectInLocalCoordinates();
220    FloatRect nonLocalBoundingBox = m_localTransform.mapRect(boundingBox);
221    // FIXME: The empty rect check is to deal with incorrect initial clip in renderSubtreeToImage
222    // unfortunately fixing that problem is fairly complex unless we were willing to just futz the
223    // rect to something "close enough"
224    if (!nonLocalBoundingBox.intersects(paintInfo.rect) && !paintInfo.rect.isEmpty())
225        return;
226
227    PaintInfo childPaintInfo(paintInfo);
228    childPaintInfo.context->save();
229    applyTransformToPaintInfo(childPaintInfo, m_localTransform);
230    SVGResourceFilter* filter = 0;
231
232    if (childPaintInfo.phase == PaintPhaseForeground) {
233        PaintInfo savedInfo(childPaintInfo);
234
235        if (prepareToRenderSVGContent(this, childPaintInfo, boundingBox, filter)) {
236            if (style()->svgStyle()->shapeRendering() == SR_CRISPEDGES)
237                childPaintInfo.context->setShouldAntialias(false);
238            fillAndStrokePath(m_path, childPaintInfo.context, style(), this);
239
240            if (static_cast<SVGStyledElement*>(node())->supportsMarkers())
241                m_markerLayoutInfo.drawMarkers(childPaintInfo);
242        }
243        finishRenderSVGContent(this, childPaintInfo, filter, savedInfo.context);
244    }
245
246    if ((childPaintInfo.phase == PaintPhaseOutline || childPaintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth())
247        paintOutline(childPaintInfo.context, static_cast<int>(boundingBox.x()), static_cast<int>(boundingBox.y()),
248            static_cast<int>(boundingBox.width()), static_cast<int>(boundingBox.height()), style());
249
250    childPaintInfo.context->restore();
251}
252
253// This method is called from inside paintOutline() since we call paintOutline()
254// while transformed to our coord system, return local coords
255void RenderPath::addFocusRingRects(Vector<IntRect>& rects, int, int)
256{
257    IntRect rect = enclosingIntRect(repaintRectInLocalCoordinates());
258    if (!rect.isEmpty())
259        rects.append(rect);
260}
261
262bool RenderPath::nodeAtFloatPoint(const HitTestRequest&, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction)
263{
264    // We only draw in the forground phase, so we only hit-test then.
265    if (hitTestAction != HitTestForeground)
266        return false;
267
268    FloatPoint localPoint = m_localTransform.inverse().mapPoint(pointInParent);
269
270    PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_PATH_HITTESTING, style()->pointerEvents());
271
272    bool isVisible = (style()->visibility() == VISIBLE);
273    if (isVisible || !hitRules.requireVisible) {
274        if ((hitRules.canHitStroke && (style()->svgStyle()->hasStroke() || !hitRules.requireStroke) && strokeContains(localPoint, hitRules.requireStroke))
275            || (hitRules.canHitFill && (style()->svgStyle()->hasFill() || !hitRules.requireFill) && fillContains(localPoint, hitRules.requireFill))) {
276            updateHitTestResult(result, roundedIntPoint(localPoint));
277            return true;
278        }
279    }
280
281    return false;
282}
283
284void RenderPath::calculateMarkerBoundsIfNeeded() const
285{
286    Document* doc = document();
287
288    SVGElement* svgElement = static_cast<SVGElement*>(node());
289    ASSERT(svgElement && svgElement->document());
290    if (!svgElement->isStyled())
291        return;
292
293    SVGStyledElement* styledElement = static_cast<SVGStyledElement*>(svgElement);
294    if (!styledElement->supportsMarkers())
295        return;
296
297    const SVGRenderStyle* svgStyle = style()->svgStyle();
298    AtomicString startMarkerId(svgStyle->startMarker());
299    AtomicString midMarkerId(svgStyle->midMarker());
300    AtomicString endMarkerId(svgStyle->endMarker());
301
302    SVGResourceMarker* startMarker = getMarkerById(doc, startMarkerId, this);
303    SVGResourceMarker* midMarker = getMarkerById(doc, midMarkerId, this);
304    SVGResourceMarker* endMarker = getMarkerById(doc, endMarkerId, this);
305
306    if (!startMarker && !startMarkerId.isEmpty())
307        svgElement->document()->accessSVGExtensions()->addPendingResource(startMarkerId, styledElement);
308    else if (startMarker)
309        startMarker->addClient(styledElement);
310
311    if (!midMarker && !midMarkerId.isEmpty())
312        svgElement->document()->accessSVGExtensions()->addPendingResource(midMarkerId, styledElement);
313    else if (midMarker)
314        midMarker->addClient(styledElement);
315
316    if (!endMarker && !endMarkerId.isEmpty())
317        svgElement->document()->accessSVGExtensions()->addPendingResource(endMarkerId, styledElement);
318    else if (endMarker)
319        endMarker->addClient(styledElement);
320
321    if (!startMarker && !midMarker && !endMarker)
322        return;
323
324    float strokeWidth = SVGRenderStyle::cssPrimitiveToLength(this, svgStyle->strokeWidth(), 1.0f);
325    m_cachedLocalMarkerBBox = m_markerLayoutInfo.calculateBoundaries(startMarker, midMarker, endMarker, strokeWidth, m_path);
326}
327
328}
329
330#endif // ENABLE(SVG)
331