Remove some extra includes from SVG.
[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
25 #if ENABLE(SVG)
26 #include "RenderSVGResourceClipper.h"
27
28 #include "ElementIterator.h"
29 #include "Frame.h"
30 #include "FrameView.h"
31 #include "HitTestRequest.h"
32 #include "HitTestResult.h"
33 #include "IntRect.h"
34 #include "RenderObject.h"
35 #include "RenderStyle.h"
36 #include "RenderView.h"
37 #include "SVGNames.h"
38 #include "SVGRenderingContext.h"
39 #include "SVGResources.h"
40 #include "SVGResourcesCache.h"
41 #include "SVGUseElement.h"
42
43 namespace WebCore {
44
45 RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType;
46
47 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement& element, PassRef<RenderStyle> style)
48     : RenderSVGResourceContainer(element, std::move(style))
49 {
50 }
51
52 RenderSVGResourceClipper::~RenderSVGResourceClipper()
53 {
54 }
55
56 void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation)
57 {
58     m_clipBoundaries = FloatRect();
59     m_clipper.clear();
60
61     markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
62 }
63
64 void RenderSVGResourceClipper::removeClientFromCache(RenderObject& client, bool markForInvalidation)
65 {
66     m_clipper.remove(&client);
67
68     markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
69 }
70
71 bool RenderSVGResourceClipper::applyResource(RenderElement& renderer, const RenderStyle&, GraphicsContext*& context, unsigned short resourceMode)
72 {
73     ASSERT(context);
74     ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode);
75
76     return applyClippingToContext(renderer, renderer.objectBoundingBox(), renderer.repaintRectInLocalCoordinates(), context);
77 }
78
79 bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext* context, const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox)
80 {
81     // If the current clip-path gets clipped itself, we have to fallback to masking.
82     if (!style().svgStyle().clipperResource().isEmpty())
83         return false;
84     WindRule clipRule = RULE_NONZERO;
85     Path clipPath = Path();
86
87     // If clip-path only contains one visible shape or path, we can use path-based clipping. Invisible
88     // shapes don't affect the clipping and can be ignored. If clip-path contains more than one
89     // visible shape, the additive clipping may not work, caused by the clipRule. EvenOdd
90     // as well as NonZero can cause self-clipping of the elements.
91     // See also http://www.w3.org/TR/SVG/painting.html#FillRuleProperty
92     for (Node* childNode = clipPathElement().firstChild(); childNode; childNode = childNode->nextSibling()) {
93         RenderObject* renderer = childNode->renderer();
94         if (!renderer)
95             continue;
96         // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts.
97         if (renderer->isSVGText())
98             return false;
99         if (!childNode->isSVGElement() || !toSVGElement(childNode)->isSVGGraphicsElement())
100             continue;
101         SVGGraphicsElement* styled = toSVGGraphicsElement(childNode);
102         const RenderStyle& style = renderer->style();
103         if (style.display() == NONE || style.visibility() != VISIBLE)
104              continue;
105         const SVGRenderStyle& svgStyle = style.svgStyle();
106         // Current shape in clip-path gets clipped too. Fallback to masking.
107         if (!svgStyle.clipperResource().isEmpty())
108             return false;
109         // Fallback to masking, if there is more than one clipping path.
110         if (clipPath.isEmpty()) {
111             styled->toClipPath(clipPath);
112             clipRule = svgStyle.clipRule();
113         } else
114             return false;
115     }
116     // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary.
117     if (clipPathElement().clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
118         AffineTransform transform;
119         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
120         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
121         clipPath.transform(transform);
122     }
123
124     // Transform path by animatedLocalTransform.
125     clipPath.transform(animatedLocalTransform);
126
127     // The SVG specification wants us to clip everything, if clip-path doesn't have a child.
128     if (clipPath.isEmpty())
129         clipPath.addRect(FloatRect());
130     context->clipPath(clipPath, clipRule);
131     return true;
132 }
133
134 bool RenderSVGResourceClipper::applyClippingToContext(RenderElement& renderer, const FloatRect& objectBoundingBox,
135                                                       const FloatRect& repaintRect, GraphicsContext* context)
136 {
137     bool missingClipperData = !m_clipper.contains(&renderer);
138     if (missingClipperData)
139         m_clipper.set(&renderer, std::make_unique<ClipperData>());
140
141     bool shouldCreateClipData = false;
142     AffineTransform animatedLocalTransform = clipPathElement().animatedLocalTransform();
143     ClipperData* clipperData = m_clipper.get(&renderer);
144     if (!clipperData->clipMaskImage) {
145         if (pathOnlyClipping(context, animatedLocalTransform, objectBoundingBox))
146             return true;
147         shouldCreateClipData = true;
148     }
149
150     AffineTransform absoluteTransform;
151     SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(&renderer, absoluteTransform);
152
153     if (shouldCreateClipData && !repaintRect.isEmpty()) {
154         if (!SVGRenderingContext::createImageBuffer(repaintRect, absoluteTransform, clipperData->clipMaskImage, ColorSpaceDeviceRGB, Unaccelerated))
155             return false;
156
157         GraphicsContext* maskContext = clipperData->clipMaskImage->context();
158         ASSERT(maskContext);
159
160         maskContext->concatCTM(animatedLocalTransform);
161
162         // clipPath can also be clipped by another clipPath.
163         SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(*this);
164         RenderSVGResourceClipper* clipper;
165         bool succeeded;
166         if (resources && (clipper = resources->clipper())) {
167             GraphicsContextStateSaver stateSaver(*maskContext);
168
169             if (!clipper->applyClippingToContext(*this, objectBoundingBox, repaintRect, maskContext))
170                 return false;
171
172             succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox);
173             // The context restore applies the clipping on non-CG platforms.
174         } else
175             succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox);
176
177         if (!succeeded)
178             clipperData->clipMaskImage.reset();
179     }
180
181     if (!clipperData->clipMaskImage)
182         return false;
183
184     SVGRenderingContext::clipToImageBuffer(context, absoluteTransform, repaintRect, clipperData->clipMaskImage, missingClipperData);
185     return true;
186 }
187
188 bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox)
189 {
190     ASSERT(clipperData);
191     ASSERT(clipperData->clipMaskImage);
192
193     GraphicsContext* maskContext = clipperData->clipMaskImage->context();
194     ASSERT(maskContext);
195
196     AffineTransform maskContentTransformation;
197     if (clipPathElement().clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
198         maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y());
199         maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
200         maskContext->concatCTM(maskContentTransformation);
201     }
202
203     // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints:
204     // - fill-opacity/stroke-opacity/opacity set to 1
205     // - masker/filter not applied when rendering the children
206     // - fill is set to the initial fill paint server (solid, black)
207     // - stroke is set to the initial stroke paint server (none)
208     PaintBehavior oldBehavior = view().frameView().paintBehavior();
209     view().frameView().setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask);
210
211     // Draw all clipPath children into a global mask.
212     for (auto& child : childrenOfType<SVGElement>(clipPathElement())) {
213         auto renderer = child.renderer();
214         if (!renderer)
215             continue;
216         if (renderer->needsLayout()) {
217             view().frameView().setPaintBehavior(oldBehavior);
218             return false;
219         }
220         const RenderStyle& style = renderer->style();
221         if (style.display() == NONE || style.visibility() != VISIBLE)
222             continue;
223
224         WindRule newClipRule = style.svgStyle().clipRule();
225         bool isUseElement = child.hasTagName(SVGNames::useTag);
226         if (isUseElement) {
227             SVGUseElement& useElement = toSVGUseElement(child);
228             renderer = useElement.rendererClipChild();
229             if (!renderer)
230                 continue;
231             if (!useElement.hasAttribute(SVGNames::clip_ruleAttr))
232                 newClipRule = renderer->style().svgStyle().clipRule();
233         }
234
235         // Only shapes, paths and texts are allowed for clipping.
236         if (!renderer->isSVGShape() && !renderer->isSVGText())
237             continue;
238
239         maskContext->setFillRule(newClipRule);
240
241         // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule.
242         // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering.
243         // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above.
244         SVGRenderingContext::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? *child.renderer() : *renderer, maskContentTransformation);
245     }
246
247     view().frameView().setPaintBehavior(oldBehavior);
248     return true;
249 }
250
251 void RenderSVGResourceClipper::calculateClipContentRepaintRect()
252 {
253     // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip.
254     for (Node* childNode = clipPathElement().firstChild(); childNode; childNode = childNode->nextSibling()) {
255         RenderObject* renderer = childNode->renderer();
256         if (!childNode->isSVGElement() || !renderer)
257             continue;
258         if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag))
259             continue;
260         const RenderStyle& style = renderer->style();
261         if (style.display() == NONE || style.visibility() != VISIBLE)
262              continue;
263         m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates()));
264     }
265     m_clipBoundaries = clipPathElement().animatedLocalTransform().mapRect(m_clipBoundaries);
266 }
267
268 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint)
269 {
270     FloatPoint point = nodeAtPoint;
271     if (!SVGRenderSupport::pointInClippingArea(*this, point))
272         return false;
273
274     if (clipPathElement().clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
275         AffineTransform transform;
276         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
277         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
278         point = transform.inverse().mapPoint(point);
279     }
280
281     point = clipPathElement().animatedLocalTransform().inverse().mapPoint(point);
282
283     for (Node* childNode = clipPathElement().firstChild(); childNode; childNode = childNode->nextSibling()) {
284         RenderObject* renderer = childNode->renderer();
285         if (!childNode->isSVGElement() || !renderer)
286             continue;
287         if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag))
288             continue;
289         IntPoint hitPoint;
290         HitTestResult result(hitPoint);
291         if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent | HitTestRequest::DisallowShadowContent), result, point, HitTestForeground))
292             return true;
293     }
294
295     return false;
296 }
297
298 FloatRect RenderSVGResourceClipper::resourceBoundingBox(const RenderObject& object)
299 {
300     // Resource was not layouted yet. Give back the boundingBox of the object.
301     if (selfNeedsLayout())
302         return object.objectBoundingBox();
303     
304     if (m_clipBoundaries.isEmpty())
305         calculateClipContentRepaintRect();
306
307     if (clipPathElement().clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
308         FloatRect objectBoundingBox = object.objectBoundingBox();
309         AffineTransform transform;
310         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
311         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
312         return transform.mapRect(m_clipBoundaries);
313     }
314
315     return m_clipBoundaries;
316 }
317
318 }
319
320 #endif // ENABLE(SVG)