1dac141fb1593f4654d40a62a9acc031465a827d
[WebKit-https.git] / Source / WebCore / rendering / svg / RenderSVGResourceClipper.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
3  * Copyright (C) 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org>
4  * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
5  * Copyright (C) 2011 Dirk Schulze <krit@webkit.org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #include "config.h"
24 #include "RenderSVGResourceClipper.h"
25
26 #include "ElementIterator.h"
27 #include "Frame.h"
28 #include "FrameView.h"
29 #include "HitTestRequest.h"
30 #include "HitTestResult.h"
31 #include "IntRect.h"
32 #include "RenderObject.h"
33 #include "RenderStyle.h"
34 #include "RenderView.h"
35 #include "SVGNames.h"
36 #include "SVGRenderingContext.h"
37 #include "SVGResources.h"
38 #include "SVGResourcesCache.h"
39 #include "SVGUseElement.h"
40 #include <wtf/IsoMallocInlines.h>
41
42 namespace WebCore {
43
44 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGResourceClipper);
45
46 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement& element, RenderStyle&& style)
47     : RenderSVGResourceContainer(element, WTFMove(style))
48 {
49 }
50
51 RenderSVGResourceClipper::~RenderSVGResourceClipper() = default;
52
53 void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation)
54 {
55     m_clipBoundaries = FloatRect();
56     m_clipper.clear();
57
58     markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
59 }
60
61 void RenderSVGResourceClipper::removeClientFromCache(RenderElement& client, bool markForInvalidation)
62 {
63     m_clipper.remove(&client);
64
65     markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
66 }
67
68 bool RenderSVGResourceClipper::applyResource(RenderElement& renderer, const RenderStyle&, GraphicsContext*& context, OptionSet<RenderSVGResourceMode> resourceMode)
69 {
70     ASSERT(context);
71     ASSERT_UNUSED(resourceMode, resourceMode == RenderSVGResourceMode::ApplyToDefault);
72
73     return applyClippingToContext(renderer, renderer.objectBoundingBox(), renderer.repaintRectInLocalCoordinates(), *context);
74 }
75
76 bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext& context, const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox)
77 {
78     // If the current clip-path gets clipped itself, we have to fallback to masking.
79     if (!style().svgStyle().clipperResource().isEmpty())
80         return false;
81     WindRule clipRule = WindRule::NonZero;
82     Path clipPath = Path();
83
84     // If clip-path only contains one visible shape or path, we can use path-based clipping. Invisible
85     // shapes don't affect the clipping and can be ignored. If clip-path contains more than one
86     // visible shape, the additive clipping may not work, caused by the clipRule. EvenOdd
87     // as well as NonZero can cause self-clipping of the elements.
88     // See also http://www.w3.org/TR/SVG/painting.html#FillRuleProperty
89     for (Node* childNode = clipPathElement().firstChild(); childNode; childNode = childNode->nextSibling()) {
90         RenderObject* renderer = childNode->renderer();
91         if (!renderer)
92             continue;
93         // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts.
94         if (renderer->isSVGText())
95             return false;
96         if (!childNode->isSVGElement() || !downcast<SVGElement>(*childNode).isSVGGraphicsElement())
97             continue;
98         SVGGraphicsElement& styled = downcast<SVGGraphicsElement>(*childNode);
99         const RenderStyle& style = renderer->style();
100         if (style.display() == DisplayType::None || style.visibility() != Visibility::Visible)
101              continue;
102         const SVGRenderStyle& svgStyle = style.svgStyle();
103         // Current shape in clip-path gets clipped too. Fallback to masking.
104         if (!svgStyle.clipperResource().isEmpty())
105             return false;
106         // Fallback to masking, if there is more than one clipping path.
107         if (clipPath.isEmpty()) {
108             clipPath = styled.toClipPath();
109             clipRule = svgStyle.clipRule();
110         } else
111             return false;
112     }
113     // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary.
114     if (clipPathElement().clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
115         AffineTransform transform;
116         transform.translate(objectBoundingBox.location());
117         transform.scale(objectBoundingBox.size());
118         clipPath.transform(transform);
119     }
120
121     // Transform path by animatedLocalTransform.
122     clipPath.transform(animatedLocalTransform);
123
124     // The SVG specification wants us to clip everything, if clip-path doesn't have a child.
125     if (clipPath.isEmpty())
126         clipPath.addRect(FloatRect());
127     context.clipPath(clipPath, clipRule);
128     return true;
129 }
130
131 bool RenderSVGResourceClipper::applyClippingToContext(RenderElement& renderer, const FloatRect& objectBoundingBox, const FloatRect& repaintRect, GraphicsContext& context)
132 {
133     ClipperMaskImage& clipperMaskImage = addRendererToClipper(renderer);
134     bool shouldCreateClipperMaskImage = !clipperMaskImage;
135
136     AffineTransform animatedLocalTransform = clipPathElement().animatedLocalTransform();
137
138     if (shouldCreateClipperMaskImage && pathOnlyClipping(context, animatedLocalTransform, objectBoundingBox))
139         return true;
140
141     AffineTransform absoluteTransform = SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(renderer);
142
143     if (shouldCreateClipperMaskImage && !repaintRect.isEmpty()) {
144         // FIXME (149469): This image buffer should not be unconditionally unaccelerated. Making it match the context breaks nested clipping, though.
145         clipperMaskImage = SVGRenderingContext::createImageBuffer(repaintRect, absoluteTransform, ColorSpaceSRGB, Unaccelerated);
146         if (!clipperMaskImage)
147             return false;
148
149         GraphicsContext& maskContext = clipperMaskImage->context();
150         maskContext.concatCTM(animatedLocalTransform);
151
152         // clipPath can also be clipped by another clipPath.
153         auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*this);
154         RenderSVGResourceClipper* clipper;
155         bool succeeded;
156         if (resources && (clipper = resources->clipper())) {
157             GraphicsContextStateSaver stateSaver(maskContext);
158
159             if (!clipper->applyClippingToContext(*this, objectBoundingBox, repaintRect, maskContext))
160                 return false;
161
162             succeeded = drawContentIntoMaskImage(clipperMaskImage, objectBoundingBox);
163             // The context restore applies the clipping on non-CG platforms.
164         } else
165             succeeded = drawContentIntoMaskImage(clipperMaskImage, objectBoundingBox);
166
167         if (!succeeded)
168             clipperMaskImage.reset();
169     }
170
171     if (!clipperMaskImage)
172         return false;
173
174     SVGRenderingContext::clipToImageBuffer(context, absoluteTransform, repaintRect, clipperMaskImage, shouldCreateClipperMaskImage);
175     return true;
176 }
177
178 bool RenderSVGResourceClipper::drawContentIntoMaskImage(const ClipperMaskImage& clipperMaskImage, const FloatRect& objectBoundingBox)
179 {
180     ASSERT(clipperMaskImage);
181
182     GraphicsContext& maskContext = clipperMaskImage->context();
183
184     AffineTransform maskContentTransformation;
185     if (clipPathElement().clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
186         maskContentTransformation.translate(objectBoundingBox.location());
187         maskContentTransformation.scale(objectBoundingBox.size());
188         maskContext.concatCTM(maskContentTransformation);
189     }
190
191     // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints:
192     // - fill-opacity/stroke-opacity/opacity set to 1
193     // - masker/filter not applied when rendering the children
194     // - fill is set to the initial fill paint server (solid, black)
195     // - stroke is set to the initial stroke paint server (none)
196     PaintBehavior oldBehavior = view().frameView().paintBehavior();
197     view().frameView().setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask);
198
199     // Draw all clipPath children into a global mask.
200     for (auto& child : childrenOfType<SVGElement>(clipPathElement())) {
201         auto renderer = child.renderer();
202         if (!renderer)
203             continue;
204         if (renderer->needsLayout()) {
205             view().frameView().setPaintBehavior(oldBehavior);
206             return false;
207         }
208         const RenderStyle& style = renderer->style();
209         if (style.display() == DisplayType::None || style.visibility() != Visibility::Visible)
210             continue;
211
212         WindRule newClipRule = style.svgStyle().clipRule();
213         bool isUseElement = child.hasTagName(SVGNames::useTag);
214         if (isUseElement) {
215             SVGUseElement& useElement = downcast<SVGUseElement>(child);
216             renderer = useElement.rendererClipChild();
217             if (!renderer)
218                 continue;
219             if (!useElement.hasAttributeWithoutSynchronization(SVGNames::clip_ruleAttr))
220                 newClipRule = renderer->style().svgStyle().clipRule();
221         }
222
223         // Only shapes, paths and texts are allowed for clipping.
224         if (!renderer->isSVGShape() && !renderer->isSVGText())
225             continue;
226
227         maskContext.setFillRule(newClipRule);
228
229         // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule.
230         // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering.
231         // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overridden above.
232         SVGRenderingContext::renderSubtreeToImageBuffer(clipperMaskImage.get(), isUseElement ? *child.renderer() : *renderer, maskContentTransformation);
233     }
234
235     view().frameView().setPaintBehavior(oldBehavior);
236     return true;
237 }
238
239 void RenderSVGResourceClipper::calculateClipContentRepaintRect()
240 {
241     // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip.
242     for (Node* childNode = clipPathElement().firstChild(); childNode; childNode = childNode->nextSibling()) {
243         RenderObject* renderer = childNode->renderer();
244         if (!childNode->isSVGElement() || !renderer)
245             continue;
246         if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag))
247             continue;
248         const RenderStyle& style = renderer->style();
249         if (style.display() == DisplayType::None || style.visibility() != Visibility::Visible)
250              continue;
251         m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates()));
252     }
253     m_clipBoundaries = clipPathElement().animatedLocalTransform().mapRect(m_clipBoundaries);
254 }
255
256 ClipperMaskImage& RenderSVGResourceClipper::addRendererToClipper(const RenderObject& object)
257 {
258     return m_clipper.add(&object, ClipperMaskImage()).iterator->value;
259 }
260
261 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint)
262 {
263     FloatPoint point = nodeAtPoint;
264     if (!SVGRenderSupport::pointInClippingArea(*this, point))
265         return false;
266
267     if (clipPathElement().clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
268         AffineTransform transform;
269         transform.translate(objectBoundingBox.location());
270         transform.scale(objectBoundingBox.size());
271         point = transform.inverse().value_or(AffineTransform()).mapPoint(point);
272     }
273
274     point = clipPathElement().animatedLocalTransform().inverse().value_or(AffineTransform()).mapPoint(point);
275
276     for (Node* childNode = clipPathElement().firstChild(); childNode; childNode = childNode->nextSibling()) {
277         RenderObject* renderer = childNode->renderer();
278         if (!childNode->isSVGElement() || !renderer)
279             continue;
280         if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag))
281             continue;
282         IntPoint hitPoint;
283         HitTestResult result(hitPoint);
284         if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent | HitTestRequest::DisallowUserAgentShadowContent), result, point, HitTestForeground))
285             return true;
286     }
287
288     return false;
289 }
290
291 FloatRect RenderSVGResourceClipper::resourceBoundingBox(const RenderObject& object)
292 {
293     // Resource was not layouted yet. Give back the boundingBox of the object.
294     if (selfNeedsLayout()) {
295         addRendererToClipper(object);
296         return object.objectBoundingBox();
297     }
298     
299     if (m_clipBoundaries.isEmpty())
300         calculateClipContentRepaintRect();
301
302     if (clipPathElement().clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
303         FloatRect objectBoundingBox = object.objectBoundingBox();
304         AffineTransform transform;
305         transform.translate(objectBoundingBox.location());
306         transform.scale(objectBoundingBox.size());
307         return transform.mapRect(m_clipBoundaries);
308     }
309
310     return m_clipBoundaries;
311 }
312
313 }