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#include "RenderSVGShape.h"
30
31#include "FloatPoint.h"
32#include "FloatQuad.h"
33#include "GraphicsContext.h"
34#include "HitTestRequest.h"
35#include "LayoutRepainter.h"
36#include "PointerEventsHitRules.h"
37#include "RenderSVGResourceMarker.h"
38#include "RenderSVGResourceSolidColor.h"
39#include "SVGPathData.h"
40#include "SVGRenderingContext.h"
41#include "SVGResources.h"
42#include "SVGResourcesCache.h"
43#include "SVGTransformList.h"
44#include "SVGURIReference.h"
45#include "StrokeStyleApplier.h"
46#include <wtf/StackStats.h>
47
48namespace WebCore {
49
50class BoundingRectStrokeStyleApplier final : public StrokeStyleApplier {
51public:
52    BoundingRectStrokeStyleApplier(const RenderSVGShape& renderer)
53        : m_renderer(renderer)
54    {
55    }
56
57    virtual void strokeStyle(GraphicsContext* context) override
58    {
59        SVGRenderSupport::applyStrokeStyleToContext(context, m_renderer.style(), m_renderer);
60    }
61
62private:
63    const RenderSVGShape& m_renderer;
64};
65
66RenderSVGShape::RenderSVGShape(SVGGraphicsElement& element, PassRef<RenderStyle> style)
67    : RenderSVGModelObject(element, WTF::move(style))
68    , m_needsBoundariesUpdate(false) // Default is false, the cached rects are empty from the beginning.
69    , m_needsShapeUpdate(true) // Default is true, so we grab a Path object once from SVGGraphicsElement.
70    , m_needsTransformUpdate(true) // Default is true, so we grab a AffineTransform object once from SVGGraphicsElement.
71{
72}
73
74RenderSVGShape::~RenderSVGShape()
75{
76}
77
78void RenderSVGShape::updateShapeFromElement()
79{
80    m_path = std::make_unique<Path>();
81    ASSERT(RenderSVGShape::isEmpty());
82
83    updatePathFromGraphicsElement(&graphicsElement(), path());
84    processMarkerPositions();
85
86    m_fillBoundingBox = calculateObjectBoundingBox();
87    m_strokeBoundingBox = calculateStrokeBoundingBox();
88}
89
90bool RenderSVGShape::isEmpty() const
91{
92    return path().isEmpty();
93}
94
95void RenderSVGShape::fillShape(GraphicsContext* context) const
96{
97    context->fillPath(path());
98}
99
100void RenderSVGShape::strokeShape(GraphicsContext* context) const
101{
102    ASSERT(m_path);
103    Path* usePath = m_path.get();
104
105    if (hasNonScalingStroke())
106        usePath = nonScalingStrokePath(usePath, nonScalingStrokeTransform());
107
108    context->strokePath(*usePath);
109}
110
111bool RenderSVGShape::shapeDependentStrokeContains(const FloatPoint& point)
112{
113    ASSERT(m_path);
114    BoundingRectStrokeStyleApplier applier(*this);
115
116    if (hasNonScalingStroke()) {
117        AffineTransform nonScalingTransform = nonScalingStrokeTransform();
118        Path* usePath = nonScalingStrokePath(m_path.get(), nonScalingTransform);
119
120        return usePath->strokeContains(&applier, nonScalingTransform.mapPoint(point));
121    }
122
123    return m_path->strokeContains(&applier, point);
124}
125
126bool RenderSVGShape::shapeDependentFillContains(const FloatPoint& point, const WindRule fillRule) const
127{
128    return path().contains(point, fillRule);
129}
130
131bool RenderSVGShape::fillContains(const FloatPoint& point, bool requiresFill, const WindRule fillRule)
132{
133    if (!m_fillBoundingBox.contains(point))
134        return false;
135
136    Color fallbackColor;
137    if (requiresFill && !RenderSVGResource::fillPaintingResource(*this, style(), fallbackColor))
138        return false;
139
140    return shapeDependentFillContains(point, fillRule);
141}
142
143bool RenderSVGShape::strokeContains(const FloatPoint& point, bool requiresStroke)
144{
145    if (!strokeBoundingBox().contains(point))
146        return false;
147
148    Color fallbackColor;
149    if (requiresStroke && !RenderSVGResource::strokePaintingResource(*this, style(), fallbackColor))
150        return false;
151
152    return shapeDependentStrokeContains(point);
153}
154
155void RenderSVGShape::layout()
156{
157    StackStats::LayoutCheckPoint layoutCheckPoint;
158    LayoutRepainter repainter(*this, SVGRenderSupport::checkForSVGRepaintDuringLayout(*this) && selfNeedsLayout());
159
160    bool updateCachedBoundariesInParents = false;
161
162    if (m_needsShapeUpdate || m_needsBoundariesUpdate) {
163        updateShapeFromElement();
164        m_needsShapeUpdate = false;
165        updateRepaintBoundingBox();
166        m_needsBoundariesUpdate = false;
167        updateCachedBoundariesInParents = true;
168    }
169
170    if (m_needsTransformUpdate) {
171        m_localTransform = graphicsElement().animatedLocalTransform();
172        m_needsTransformUpdate = false;
173        updateCachedBoundariesInParents = true;
174    }
175
176    // Invalidate all resources of this client if our layout changed.
177    if (everHadLayout() && selfNeedsLayout())
178        SVGResourcesCache::clientLayoutChanged(*this);
179
180    // If our bounds changed, notify the parents.
181    if (updateCachedBoundariesInParents)
182        RenderSVGModelObject::setNeedsBoundariesUpdate();
183
184    repainter.repaintAfterLayout();
185    clearNeedsLayout();
186}
187
188Path* RenderSVGShape::nonScalingStrokePath(const Path* path, const AffineTransform& strokeTransform) const
189{
190    DEPRECATED_DEFINE_STATIC_LOCAL(Path, tempPath, ());
191
192    tempPath = *path;
193    tempPath.transform(strokeTransform);
194
195    return &tempPath;
196}
197
198bool RenderSVGShape::setupNonScalingStrokeContext(AffineTransform& strokeTransform, GraphicsContextStateSaver& stateSaver)
199{
200    if (!strokeTransform.isInvertible())
201        return false;
202
203    stateSaver.save();
204    stateSaver.context()->concatCTM(strokeTransform.inverse());
205    return true;
206}
207
208AffineTransform RenderSVGShape::nonScalingStrokeTransform() const
209{
210    return graphicsElement().getScreenCTM(SVGLocatable::DisallowStyleUpdate);
211}
212
213bool RenderSVGShape::shouldGenerateMarkerPositions() const
214{
215    if (!style().svgStyle().hasMarkers())
216        return false;
217
218    if (!graphicsElement().supportsMarkers())
219        return false;
220
221    SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(*this);
222    if (!resources)
223        return false;
224
225    return resources->markerStart() || resources->markerMid() || resources->markerEnd();
226}
227
228void RenderSVGShape::fillShape(const RenderStyle& style, GraphicsContext* context)
229{
230    Color fallbackColor;
231    if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(*this, style, fallbackColor)) {
232        if (fillPaintingResource->applyResource(*this, style, context, ApplyToFillMode))
233            fillPaintingResource->postApplyResource(*this, context, ApplyToFillMode, 0, this);
234        else if (fallbackColor.isValid()) {
235            RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource();
236            fallbackResource->setColor(fallbackColor);
237            if (fallbackResource->applyResource(*this, style, context, ApplyToFillMode))
238                fallbackResource->postApplyResource(*this, context, ApplyToFillMode, 0, this);
239        }
240    }
241}
242
243void RenderSVGShape::strokeShape(const RenderStyle& style, GraphicsContext* context)
244{
245    Color fallbackColor;
246    if (RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(*this, style, fallbackColor)) {
247        if (strokePaintingResource->applyResource(*this, style, context, ApplyToStrokeMode))
248            strokePaintingResource->postApplyResource(*this, context, ApplyToStrokeMode, 0, this);
249        else if (fallbackColor.isValid()) {
250            RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource();
251            fallbackResource->setColor(fallbackColor);
252            if (fallbackResource->applyResource(*this, style, context, ApplyToStrokeMode))
253                fallbackResource->postApplyResource(*this, context, ApplyToStrokeMode, 0, this);
254        }
255    }
256}
257
258void RenderSVGShape::strokeShape(GraphicsContext* context)
259{
260    if (!style().svgStyle().hasVisibleStroke())
261        return;
262
263    GraphicsContextStateSaver stateSaver(*context, false);
264    if (hasNonScalingStroke()) {
265        AffineTransform nonScalingTransform = nonScalingStrokeTransform();
266        if (!setupNonScalingStrokeContext(nonScalingTransform, stateSaver))
267            return;
268    }
269    strokeShape(style(), context);
270}
271
272void RenderSVGShape::fillStrokeMarkers(PaintInfo& childPaintInfo)
273{
274    Vector<PaintType> paintOrder = style().svgStyle().paintTypesForPaintOrder();
275    for (unsigned i = 0; i < paintOrder.size(); ++i) {
276        switch (paintOrder.at(i)) {
277        case PaintTypeFill:
278            fillShape(style(), childPaintInfo.context);
279            break;
280        case PaintTypeStroke:
281            strokeShape(childPaintInfo.context);
282            break;
283        case PaintTypeMarkers:
284            if (!m_markerPositions.isEmpty())
285                drawMarkers(childPaintInfo);
286            break;
287        }
288    }
289}
290
291void RenderSVGShape::paint(PaintInfo& paintInfo, const LayoutPoint&)
292{
293    if (paintInfo.context->paintingDisabled() || paintInfo.phase != PaintPhaseForeground
294        || style().visibility() == HIDDEN || isEmpty())
295        return;
296    FloatRect boundingBox = repaintRectInLocalCoordinates();
297    if (!SVGRenderSupport::paintInfoIntersectsRepaintRect(boundingBox, m_localTransform, paintInfo))
298        return;
299
300    PaintInfo childPaintInfo(paintInfo);
301    GraphicsContextStateSaver stateSaver(*childPaintInfo.context);
302    childPaintInfo.applyTransform(m_localTransform);
303
304    if (childPaintInfo.phase == PaintPhaseForeground) {
305        SVGRenderingContext renderingContext(*this, childPaintInfo);
306
307        if (renderingContext.isRenderingPrepared()) {
308            const SVGRenderStyle& svgStyle = style().svgStyle();
309            if (svgStyle.shapeRendering() == SR_CRISPEDGES)
310                childPaintInfo.context->setShouldAntialias(false);
311
312            fillStrokeMarkers(childPaintInfo);
313        }
314    }
315
316    if (style().outlineWidth())
317        paintOutline(childPaintInfo, IntRect(boundingBox));
318}
319
320// This method is called from inside paintOutline() since we call paintOutline()
321// while transformed to our coord system, return local coords
322void RenderSVGShape::addFocusRingRects(Vector<IntRect>& rects, const LayoutPoint&, const RenderLayerModelObject*)
323{
324    IntRect rect = enclosingIntRect(repaintRectInLocalCoordinates());
325    if (!rect.isEmpty())
326        rects.append(rect);
327}
328
329bool RenderSVGShape::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction)
330{
331    // We only draw in the forground phase, so we only hit-test then.
332    if (hitTestAction != HitTestForeground)
333        return false;
334
335    FloatPoint localPoint = m_localTransform.inverse().mapPoint(pointInParent);
336
337    if (!SVGRenderSupport::pointInClippingArea(*this, localPoint))
338        return false;
339
340    PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_PATH_HITTESTING, request, style().pointerEvents());
341    bool isVisible = (style().visibility() == VISIBLE);
342    if (isVisible || !hitRules.requireVisible) {
343        const SVGRenderStyle& svgStyle = style().svgStyle();
344        WindRule fillRule = svgStyle.fillRule();
345        if (request.svgClipContent())
346            fillRule = svgStyle.clipRule();
347        if ((hitRules.canHitStroke && (svgStyle.hasStroke() || !hitRules.requireStroke) && strokeContains(localPoint, hitRules.requireStroke))
348            || (hitRules.canHitFill && (svgStyle.hasFill() || !hitRules.requireFill) && fillContains(localPoint, hitRules.requireFill, fillRule))) {
349            updateHitTestResult(result, roundedLayoutPoint(localPoint));
350            return true;
351        }
352    }
353    return false;
354}
355
356static inline RenderSVGResourceMarker* markerForType(SVGMarkerType type, RenderSVGResourceMarker* markerStart, RenderSVGResourceMarker* markerMid, RenderSVGResourceMarker* markerEnd)
357{
358    switch (type) {
359    case StartMarker:
360        return markerStart;
361    case MidMarker:
362        return markerMid;
363    case EndMarker:
364        return markerEnd;
365    }
366
367    ASSERT_NOT_REACHED();
368    return 0;
369}
370
371FloatRect RenderSVGShape::markerRect(float strokeWidth) const
372{
373    ASSERT(!m_markerPositions.isEmpty());
374
375    SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(*this);
376    ASSERT(resources);
377
378    RenderSVGResourceMarker* markerStart = resources->markerStart();
379    RenderSVGResourceMarker* markerMid = resources->markerMid();
380    RenderSVGResourceMarker* markerEnd = resources->markerEnd();
381    ASSERT(markerStart || markerMid || markerEnd);
382
383    FloatRect boundaries;
384    unsigned size = m_markerPositions.size();
385    for (unsigned i = 0; i < size; ++i) {
386        if (RenderSVGResourceMarker* marker = markerForType(m_markerPositions[i].type, markerStart, markerMid, markerEnd))
387            boundaries.unite(marker->markerBoundaries(marker->markerTransformation(m_markerPositions[i].origin, m_markerPositions[i].angle, strokeWidth)));
388    }
389    return boundaries;
390}
391
392FloatRect RenderSVGShape::calculateObjectBoundingBox() const
393{
394    return path().fastBoundingRect();
395}
396
397FloatRect RenderSVGShape::calculateStrokeBoundingBox() const
398{
399    ASSERT(m_path);
400    FloatRect strokeBoundingBox = m_fillBoundingBox;
401
402    const SVGRenderStyle& svgStyle = style().svgStyle();
403    if (svgStyle.hasStroke()) {
404        BoundingRectStrokeStyleApplier strokeStyle(*this);
405        if (hasNonScalingStroke()) {
406            AffineTransform nonScalingTransform = nonScalingStrokeTransform();
407            if (nonScalingTransform.isInvertible()) {
408                Path* usePath = nonScalingStrokePath(m_path.get(), nonScalingTransform);
409                FloatRect strokeBoundingRect = usePath->strokeBoundingRect(&strokeStyle);
410                strokeBoundingRect = nonScalingTransform.inverse().mapRect(strokeBoundingRect);
411                strokeBoundingBox.unite(strokeBoundingRect);
412            }
413        } else
414            strokeBoundingBox.unite(path().strokeBoundingRect(&strokeStyle));
415    }
416
417    if (!m_markerPositions.isEmpty())
418        strokeBoundingBox.unite(markerRect(strokeWidth()));
419
420    return strokeBoundingBox;
421}
422
423void RenderSVGShape::updateRepaintBoundingBox()
424{
425    m_repaintBoundingBoxExcludingShadow = strokeBoundingBox();
426    SVGRenderSupport::intersectRepaintRectWithResources(*this, m_repaintBoundingBoxExcludingShadow);
427
428    m_repaintBoundingBox = m_repaintBoundingBoxExcludingShadow;
429    SVGRenderSupport::intersectRepaintRectWithShadows(*this, m_repaintBoundingBox);
430}
431
432float RenderSVGShape::strokeWidth() const
433{
434    SVGLengthContext lengthContext(&graphicsElement());
435    return style().svgStyle().strokeWidth().value(lengthContext);
436}
437
438bool RenderSVGShape::hasSmoothStroke() const
439{
440    const SVGRenderStyle& svgStyle = style().svgStyle();
441    return svgStyle.strokeDashArray().isEmpty()
442        && svgStyle.strokeMiterLimit() == svgStyle.initialStrokeMiterLimit()
443        && svgStyle.joinStyle() == svgStyle.initialJoinStyle()
444        && svgStyle.capStyle() == svgStyle.initialCapStyle();
445}
446
447void RenderSVGShape::drawMarkers(PaintInfo& paintInfo)
448{
449    ASSERT(!m_markerPositions.isEmpty());
450
451    SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(*this);
452    if (!resources)
453        return;
454
455    RenderSVGResourceMarker* markerStart = resources->markerStart();
456    RenderSVGResourceMarker* markerMid = resources->markerMid();
457    RenderSVGResourceMarker* markerEnd = resources->markerEnd();
458    if (!markerStart && !markerMid && !markerEnd)
459        return;
460
461    float strokeWidth = this->strokeWidth();
462    unsigned size = m_markerPositions.size();
463    for (unsigned i = 0; i < size; ++i) {
464        if (RenderSVGResourceMarker* marker = markerForType(m_markerPositions[i].type, markerStart, markerMid, markerEnd))
465            marker->draw(paintInfo, marker->markerTransformation(m_markerPositions[i].origin, m_markerPositions[i].angle, strokeWidth));
466    }
467}
468
469void RenderSVGShape::processMarkerPositions()
470{
471    m_markerPositions.clear();
472
473    if (!shouldGenerateMarkerPositions())
474        return;
475
476    ASSERT(m_path);
477
478    SVGMarkerData markerData(m_markerPositions);
479    m_path->apply(&markerData, SVGMarkerData::updateFromPathElement);
480    markerData.pathIsDone();
481}
482
483}
484