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
45 namespace WebCore {
46
47 class BoundingRectStrokeStyleApplier : public StrokeStyleApplier {
48 public:
BoundingRectStrokeStyleApplier(const RenderObject * object,RenderStyle * style)49 BoundingRectStrokeStyleApplier(const RenderObject* object, RenderStyle* style)
50 : m_object(object)
51 , m_style(style)
52 {
53 ASSERT(style);
54 ASSERT(object);
55 }
56
strokeStyle(GraphicsContext * gc)57 void strokeStyle(GraphicsContext* gc)
58 {
59 applyStrokeStyleToContext(gc, m_style, m_object);
60 }
61
62 private:
63 const RenderObject* m_object;
64 RenderStyle* m_style;
65 };
66
RenderPath(SVGStyledTransformableElement * node)67 RenderPath::RenderPath(SVGStyledTransformableElement* node)
68 : RenderSVGModelObject(node)
69 {
70 }
71
localToParentTransform() const72 const AffineTransform& RenderPath::localToParentTransform() const
73 {
74 return m_localTransform;
75 }
76
localTransform() const77 AffineTransform RenderPath::localTransform() const
78 {
79 return m_localTransform;
80 }
81
fillContains(const FloatPoint & point,bool requiresFill) const82 bool 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
strokeContains(const FloatPoint & point,bool requiresStroke) const93 bool 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
objectBoundingBox() const105 FloatRect 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
strokeBoundingBox() const116 FloatRect 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
markerBoundingBox() const133 FloatRect 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
repaintRectInLocalCoordinates() const144 FloatRect 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
setPath(const Path & newPath)176 void 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
layout()185 void 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
fillAndStrokePath(const Path & path,GraphicsContext * context,RenderStyle * style,RenderPath * object)197 static 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
paint(PaintInfo & paintInfo,int,int)214 void 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
addFocusRingRects(Vector<IntRect> & rects,int,int)255 void RenderPath::addFocusRingRects(Vector<IntRect>& rects, int, int)
256 {
257 IntRect rect = enclosingIntRect(repaintRectInLocalCoordinates());
258 if (!rect.isEmpty())
259 rects.append(rect);
260 }
261
nodeAtFloatPoint(const HitTestRequest &,HitTestResult & result,const FloatPoint & pointInParent,HitTestAction hitTestAction)262 bool 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
calculateMarkerBoundsIfNeeded() const284 void 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