1/*
2 * Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2008 Rob Buis <buis@kde.org>
4 * Copyright (C) 2005, 2007 Eric Seidel <eric@webkit.org>
5 * Copyright (C) 2009 Google, Inc.
6 * Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
7 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
8 * Copyright (C) 2009 Jeff Schiller <codedread@gmail.com>
9 * Copyright (C) 2011 Renata Hodovan <reni@webkit.org>
10 * Copyright (C) 2011 University of Szeged
11 *
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Library General Public
14 * License as published by the Free Software Foundation; either
15 * version 2 of the License, or (at your option) any later version.
16 *
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Library General Public License for more details.
21 *
22 * You should have received a copy of the GNU Library General Public License
23 * along with this library; see the file COPYING.LIB. If not, write to
24 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 * Boston, MA 02110-1301, USA.
26 */
27
28#include "config.h"
29
30#if ENABLE(SVG)
31#include "RenderSVGShape.h"
32
33#include "FloatPoint.h"
34#include "FloatQuad.h"
35#include "GraphicsContext.h"
36#include "HitTestRequest.h"
37#include "PointerEventsHitRules.h"
38#include "RenderSVGContainer.h"
39#include "RenderSVGResourceMarker.h"
40#include "RenderSVGResourceSolidColor.h"
41#include "SVGPathElement.h"
42#include "SVGRenderSupport.h"
43#include "SVGResources.h"
44#include "SVGStyledTransformableElement.h"
45#include "SVGTransformList.h"
46#include "SVGURIReference.h"
47#include "StrokeStyleApplier.h"
48#include <wtf/MathExtras.h>
49
50namespace WebCore {
51
52RenderSVGShape::RenderSVGShape(SVGStyledTransformableElement* node)
53 : RenderSVGModelObject(node)
54 , m_needsBoundariesUpdate(false) // default is false, the cached rects are empty from the beginning
55 , m_needsShapeUpdate(true) // default is true, so we grab a Path object once from SVGStyledTransformableElement
56 , m_needsTransformUpdate(true) // default is true, so we grab a AffineTransform object once from SVGStyledTransformableElement
57{
58}
59
60RenderSVGShape::~RenderSVGShape()
61{
62}
63
64void RenderSVGShape::createShape()
65{
66 m_path = adoptPtr(new Path);
67 ASSERT(isEmpty());
68
69 SVGPathElement* element = static_cast<SVGPathElement*>(node());
70 element->toPathData(path());
71}
72
73bool RenderSVGShape::isEmpty() const
74{
75 return path().isEmpty();
76}
77
78void RenderSVGShape::fillShape(GraphicsContext* context)
79{
80 context->fillPath(path());
81}
82
83FloatRect RenderSVGShape::objectBoundingBox() const
84{
85 return m_path->boundingRect();
86}
87
88FloatRect RenderSVGShape::strokeBoundingBox()
89{
90 BoundingRectStrokeStyleApplier strokeStyle(this, style());
91 return path().strokeBoundingRect(&strokeStyle);
92}
93
94void RenderSVGShape::strokeShape(GraphicsContext* context)
95{
96 context->strokePath(path());
97}
98
99bool RenderSVGShape::strokeContainsSlowCase(const FloatPoint& point)
100{
101 BoundingRectStrokeStyleApplier applier(this, style());
102 return path().strokeContains(&applier, point);
103}
104
105bool RenderSVGShape::fillContainsSlowCase(const FloatPoint& point, const WindRule& fillRule) const
106{
107 return path().contains(point, fillRule);
108}
109
110bool RenderSVGShape::fillContains(const FloatPoint& point, bool requiresFill, WindRule fillRule)
111{
112 if (!m_fillBoundingBox.contains(point))
113 return false;
114
115 Color fallbackColor;
116 if (requiresFill && !RenderSVGResource::fillPaintingResource(this, style(), fallbackColor))
117 return false;
118
119 return fillContainsSlowCase(point, fillRule);
120}
121
122bool RenderSVGShape::strokeContains(const FloatPoint& point, bool requiresStroke)
123{
124 if (!m_strokeAndMarkerBoundingBox.contains(point))
125 return false;
126
127 Color fallbackColor;
128 if (requiresStroke && !RenderSVGResource::strokePaintingResource(this, style(), fallbackColor))
129 return false;
130
131 if (shouldStrokeZeroLengthSubpath())
132 return zeroLengthSubpathRect().contains(point);
133
134 return strokeContainsSlowCase(point);
135}
136
137void RenderSVGShape::layout()
138{
139 LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && selfNeedsLayout());
140 SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node());
141
142 bool updateCachedBoundariesInParents = false;
143
144 bool needsShapeUpdate = m_needsShapeUpdate;
145 if (needsShapeUpdate) {;
146 createShape();
147 m_needsShapeUpdate = false;
148 updateCachedBoundariesInParents = true;
149 }
150
151 if (m_needsTransformUpdate) {
152 m_localTransform = element->animatedLocalTransform();
153 m_needsTransformUpdate = false;
154 updateCachedBoundariesInParents = true;
155 }
156
157 if (m_needsBoundariesUpdate)
158 updateCachedBoundariesInParents = true;
159
160 // Invalidate all resources of this client if our layout changed.
161 if (m_everHadLayout && selfNeedsLayout()) {
162 SVGResourcesCache::clientLayoutChanged(this);
163 m_markerLayoutInfo.clear();
164 }
165
166 // At this point LayoutRepainter already grabbed the old bounds,
167 // recalculate them now so repaintAfterLayout() uses the new bounds.
168 if (needsShapeUpdate || m_needsBoundariesUpdate) {
169 updateCachedBoundaries();
170 m_needsBoundariesUpdate = false;
171 }
172
173 // If our bounds changed, notify the parents.
174 if (updateCachedBoundariesInParents)
175 RenderSVGModelObject::setNeedsBoundariesUpdate();
176
177 repainter.repaintAfterLayout();
178 setNeedsLayout(false);
179}
180
181bool RenderSVGShape::shouldStrokeZeroLengthSubpath() const
182{
183 // Spec(11.4): Any zero length subpath shall not be stroked if the ‘stroke-linecap’ property has a value of butt
184 // but shall be stroked if the ‘stroke-linecap’ property has a value of round or square
185 return style()->svgStyle()->hasStroke() && style()->svgStyle()->capStyle() != ButtCap && !m_fillBoundingBox.width() && !m_fillBoundingBox.height();
186}
187
188FloatRect RenderSVGShape::zeroLengthSubpathRect() const
189{
190 float strokeWidth = this->strokeWidth();
191 return FloatRect(m_fillBoundingBox.x() - strokeWidth / 2, m_fillBoundingBox.y() - strokeWidth / 2, strokeWidth, strokeWidth);
192}
193
194void RenderSVGShape::setupSquareCapPath(Path*& usePath, int& applyMode)
195{
196 // Spec(11.4): Any zero length subpath shall not be stroked if the ‘stroke-linecap’ property has a value of butt
197 // but shall be stroked if the ‘stroke-linecap’ property has a value of round or square
198 DEFINE_STATIC_LOCAL(Path, tempPath, ());
199
200 applyMode = ApplyToFillMode;
201 usePath = &tempPath;
202 usePath->clear();
203 if (style()->svgStyle()->capStyle() == SquareCap)
204 usePath->addRect(zeroLengthSubpathRect());
205 else
206 usePath->addEllipse(zeroLengthSubpathRect());
207}
208
209bool RenderSVGShape::setupNonScalingStrokePath(Path*& usePath, GraphicsContextStateSaver& stateSaver)
210{
211 DEFINE_STATIC_LOCAL(Path, tempPath, ());
212
213 SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node());
214 AffineTransform nonScalingStrokeTransform = element->getScreenCTM(SVGLocatable::DisallowStyleUpdate);
215 if (!nonScalingStrokeTransform.isInvertible())
216 return false;
217
218 tempPath = *m_path;
219 usePath = &tempPath;
220 tempPath.transform(nonScalingStrokeTransform);
221
222 stateSaver.save();
223 stateSaver.context()->concatCTM(nonScalingStrokeTransform.inverse());
224 return true;
225}
226
227void RenderSVGShape::fillAndStrokePath(GraphicsContext* context)
228{
229 RenderStyle* style = this->style();
230
231 Color fallbackColor;
232 if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(this, style, fallbackColor)) {
233 if (fillPaintingResource->applyResource(this, style, context, ApplyToFillMode)) {
234 fillShape(context);
235 } else if (fallbackColor.isValid()) {
236 RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource();
237 fallbackResource->setColor(fallbackColor);
238 if (fallbackResource->applyResource(this, style, context, ApplyToFillMode))
239 fillShape(context);
240 }
241 }
242
243 fallbackColor = Color();
244 RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(this, style, fallbackColor);
245 if (!strokePaintingResource)
246 return;
247
248 Path* usePath;
249 int applyMode = ApplyToStrokeMode;
250
251 bool nonScalingStroke = style->svgStyle()->vectorEffect() == VE_NON_SCALING_STROKE;
252
253 GraphicsContextStateSaver stateSaver(*context, false);
254
255 // Spec(11.4): Any zero length subpath shall not be stroked if the ‘stroke-linecap’ property has a value of butt
256 // but shall be stroked if the ‘stroke-linecap’ property has a value of round or square
257 // FIXME: this does not work for zero-length subpaths, only when total path is zero-length
258 if (shouldStrokeZeroLengthSubpath()) {
259 RenderSVGShape::createShape();
260 usePath = m_path.get();
261 setupSquareCapPath(usePath, applyMode);
262 } else if (nonScalingStroke) {
263 RenderSVGShape::createShape();
264 usePath = m_path.get();
265 if (!setupNonScalingStrokePath(usePath, stateSaver))
266 return;
267 }
268 if (strokePaintingResource->applyResource(this, style, context, applyMode)) {
269 strokeShape(context);
270 return;
271 }
272 if (!fallbackColor.isValid())
273 return;
274 RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource();
275 fallbackResource->setColor(fallbackColor);
276 if (fallbackResource->applyResource(this, style, context, applyMode))
277 strokeShape(context);
278}
279
280void RenderSVGShape::paint(PaintInfo& paintInfo, const IntPoint&)
281{
282 if (paintInfo.context->paintingDisabled() || style()->visibility() == HIDDEN || isEmpty())
283 return;
284 FloatRect boundingBox = repaintRectInLocalCoordinates();
285 if (!SVGRenderSupport::paintInfoIntersectsRepaintRect(boundingBox, m_localTransform, paintInfo))
286 return;
287 PaintInfo childPaintInfo(paintInfo);
288 bool drawsOutline = style()->outlineWidth() && (childPaintInfo.phase == PaintPhaseOutline || childPaintInfo.phase == PaintPhaseSelfOutline);
289 if (drawsOutline || childPaintInfo.phase == PaintPhaseForeground) {
290 GraphicsContextStateSaver stateSaver(*childPaintInfo.context);
291 childPaintInfo.applyTransform(m_localTransform);
292
293 if (childPaintInfo.phase == PaintPhaseForeground) {
294 PaintInfo savedInfo(childPaintInfo);
295
296 if (SVGRenderSupport::prepareToRenderSVGContent(this, childPaintInfo)) {
297 const SVGRenderStyle* svgStyle = style()->svgStyle();
298 if (svgStyle->shapeRendering() == SR_CRISPEDGES)
299 childPaintInfo.context->setShouldAntialias(false);
300
301 fillAndStrokePath(childPaintInfo.context);
302
303 if (svgStyle->hasMarkers())
304 m_markerLayoutInfo.drawMarkers(childPaintInfo);
305 }
306
307 SVGRenderSupport::finishRenderSVGContent(this, childPaintInfo, savedInfo.context);
308 }
309
310 if (drawsOutline)
311 paintOutline(childPaintInfo.context, IntRect(boundingBox));
312 }
313}
314
315// This method is called from inside paintOutline() since we call paintOutline()
316// while transformed to our coord system, return local coords
317void RenderSVGShape::addFocusRingRects(Vector<LayoutRect>& rects, const LayoutPoint&)
318{
319 LayoutRect rect = enclosingLayoutRect(repaintRectInLocalCoordinates());
320 if (!rect.isEmpty())
321 rects.append(rect);
322}
323
324bool RenderSVGShape::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction)
325{
326 // We only draw in the forground phase, so we only hit-test then.
327 if (hitTestAction != HitTestForeground)
328 return false;
329
330 FloatPoint localPoint = m_localTransform.inverse().mapPoint(pointInParent);
331
332 if (!SVGRenderSupport::pointInClippingArea(this, localPoint))
333 return false;
334
335 PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_PATH_HITTESTING, request, style()->pointerEvents());
336 bool isVisible = (style()->visibility() == VISIBLE);
337 if (isVisible || !hitRules.requireVisible) {
338 const SVGRenderStyle* svgStyle = style()->svgStyle();
339 WindRule fillRule = svgStyle->fillRule();
340 if (request.svgClipContent())
341 fillRule = svgStyle->clipRule();
342 if ((hitRules.canHitStroke && (svgStyle->hasStroke() || !hitRules.requireStroke) && strokeContains(localPoint, hitRules.requireStroke))
343 || (hitRules.canHitFill && (svgStyle->hasFill() || !hitRules.requireFill) && fillContains(localPoint, hitRules.requireFill, fillRule))) {
344 updateHitTestResult(result, roundedLayoutPoint(localPoint));
345 return true;
346 }
347 }
348 return false;
349}
350
351FloatRect RenderSVGShape::calculateMarkerBoundsIfNeeded()
352{
353 SVGElement* svgElement = static_cast<SVGElement*>(node());
354 ASSERT(svgElement && svgElement->document());
355 if (!svgElement->isStyled())
356 return FloatRect();
357
358 SVGStyledElement* styledElement = static_cast<SVGStyledElement*>(svgElement);
359 if (!styledElement->supportsMarkers())
360 return FloatRect();
361
362 ASSERT(style()->svgStyle()->hasMarkers());
363
364 SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this);
365 if (!resources)
366 return FloatRect();
367
368 RenderSVGResourceMarker* markerStart = resources->markerStart();
369 RenderSVGResourceMarker* markerMid = resources->markerMid();
370 RenderSVGResourceMarker* markerEnd = resources->markerEnd();
371 if (!markerStart && !markerMid && !markerEnd)
372 return FloatRect();
373
374 return m_markerLayoutInfo.calculateBoundaries(markerStart, markerMid, markerEnd, strokeWidth(), path());
375}
376
377void RenderSVGShape::updateCachedBoundaries()
378{
379 if (isEmpty()) {
380 m_fillBoundingBox = FloatRect();
381 m_strokeAndMarkerBoundingBox = FloatRect();
382 m_repaintBoundingBox = FloatRect();
383 return;
384 }
385
386 // Cache _unclipped_ fill bounding box, used for calculations in resources
387 m_fillBoundingBox = objectBoundingBox();
388
389 // Spec(11.4): Any zero length subpath shall not be stroked if the ‘stroke-linecap’ property has a value of butt
390 // but shall be stroked if the ‘stroke-linecap’ property has a value of round or square
391 if (shouldStrokeZeroLengthSubpath()) {
392 m_strokeAndMarkerBoundingBox = zeroLengthSubpathRect();
393 // Cache smallest possible repaint rectangle
394 m_repaintBoundingBox = m_strokeAndMarkerBoundingBox;
395 SVGRenderSupport::intersectRepaintRectWithResources(this, m_repaintBoundingBox);
396 return;
397 }
398
399 // Cache _unclipped_ stroke bounding box, used for calculations in resources (includes marker boundaries)
400 m_strokeAndMarkerBoundingBox = m_fillBoundingBox;
401
402 const SVGRenderStyle* svgStyle = style()->svgStyle();
403 if (svgStyle->hasStroke())
404 m_strokeAndMarkerBoundingBox.unite(strokeBoundingBox());
405
406 if (isSVGPath()) {
407 if (svgStyle->hasMarkers()) {
408 FloatRect markerBounds = calculateMarkerBoundsIfNeeded();
409 if (!markerBounds.isEmpty())
410 m_strokeAndMarkerBoundingBox.unite(markerBounds);
411 }
412 }
413 // Cache smallest possible repaint rectangle
414 m_repaintBoundingBox = m_strokeAndMarkerBoundingBox;
415 SVGRenderSupport::intersectRepaintRectWithResources(this, m_repaintBoundingBox);
416}
417
418float RenderSVGShape::strokeWidth() const
419{
420 SVGElement* svgElement = static_cast<SVGElement*>(node());
421 return style()->svgStyle()->strokeWidth().value(svgElement);
422}
423
424}