• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "core/rendering/svg/RenderSVGResourceClipper.h"
26 
27 #include "core/SVGNames.h"
28 #include "core/dom/ElementTraversal.h"
29 #include "core/frame/FrameView.h"
30 #include "core/frame/LocalFrame.h"
31 #include "core/rendering/HitTestResult.h"
32 #include "core/rendering/svg/SVGRenderSupport.h"
33 #include "core/rendering/svg/SVGRenderingContext.h"
34 #include "core/rendering/svg/SVGResources.h"
35 #include "core/rendering/svg/SVGResourcesCache.h"
36 #include "core/svg/SVGUseElement.h"
37 #include "platform/RuntimeEnabledFeatures.h"
38 #include "platform/graphics/DisplayList.h"
39 #include "platform/graphics/GraphicsContextStateSaver.h"
40 #include "wtf/TemporaryChange.h"
41 
42 namespace blink {
43 
44 const RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType;
45 
RenderSVGResourceClipper(SVGClipPathElement * node)46 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node)
47     : RenderSVGResourceContainer(node)
48     , m_inClipExpansion(false)
49 {
50 }
51 
~RenderSVGResourceClipper()52 RenderSVGResourceClipper::~RenderSVGResourceClipper()
53 {
54 }
55 
removeAllClientsFromCache(bool markForInvalidation)56 void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation)
57 {
58     m_clipContentDisplayList.clear();
59     m_clipBoundaries = FloatRect();
60     markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
61 }
62 
removeClientFromCache(RenderObject * client,bool markForInvalidation)63 void RenderSVGResourceClipper::removeClientFromCache(RenderObject* client, bool markForInvalidation)
64 {
65     ASSERT(client);
66     markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
67 }
68 
applyResource(RenderObject *,RenderStyle *,GraphicsContext * &,unsigned short)69 bool RenderSVGResourceClipper::applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short)
70 {
71     // Clippers are always applied using stateful methods.
72     ASSERT_NOT_REACHED();
73     return false;
74 }
75 
applyStatefulResource(RenderObject * object,GraphicsContext * & context,ClipperState & clipperState)76 bool RenderSVGResourceClipper::applyStatefulResource(RenderObject* object, GraphicsContext*& context, ClipperState& clipperState)
77 {
78     ASSERT(object);
79     ASSERT(context);
80 
81     clearInvalidationMask();
82 
83     return applyClippingToContext(object, object->objectBoundingBox(), object->paintInvalidationRectInLocalCoordinates(), context, clipperState);
84 }
85 
tryPathOnlyClipping(GraphicsContext * context,const AffineTransform & animatedLocalTransform,const FloatRect & objectBoundingBox)86 bool RenderSVGResourceClipper::tryPathOnlyClipping(GraphicsContext* context,
87     const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox) {
88     // If the current clip-path gets clipped itself, we have to fallback to masking.
89     if (!style()->svgStyle().clipperResource().isEmpty())
90         return false;
91     WindRule clipRule = RULE_NONZERO;
92     Path clipPath = Path();
93 
94     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
95         RenderObject* renderer = childElement->renderer();
96         if (!renderer)
97             continue;
98         // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts.
99         if (renderer->isSVGText())
100             return false;
101         if (!childElement->isSVGGraphicsElement())
102             continue;
103         SVGGraphicsElement* styled = toSVGGraphicsElement(childElement);
104         RenderStyle* style = renderer->style();
105         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
106              continue;
107         const SVGRenderStyle& svgStyle = style->svgStyle();
108         // Current shape in clip-path gets clipped too. Fallback to masking.
109         if (!svgStyle.clipperResource().isEmpty())
110             return false;
111 
112         if (clipPath.isEmpty()) {
113             // First clip shape.
114             styled->toClipPath(clipPath);
115             clipRule = svgStyle.clipRule();
116             clipPath.setWindRule(clipRule);
117             continue;
118         }
119 
120         if (RuntimeEnabledFeatures::pathOpsSVGClippingEnabled()) {
121             // Attempt to generate a combined clip path, fall back to masking if not possible.
122             Path subPath;
123             styled->toClipPath(subPath);
124             subPath.setWindRule(svgStyle.clipRule());
125             if (!clipPath.unionPath(subPath))
126                 return false;
127         } else {
128             return false;
129         }
130     }
131     // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary.
132     if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
133         AffineTransform transform;
134         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
135         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
136         clipPath.transform(transform);
137     }
138 
139     // Transform path by animatedLocalTransform.
140     clipPath.transform(animatedLocalTransform);
141 
142     // The SVG specification wants us to clip everything, if clip-path doesn't have a child.
143     if (clipPath.isEmpty())
144         clipPath.addRect(FloatRect());
145     context->clipPath(clipPath, clipRule);
146     return true;
147 }
148 
applyClippingToContext(RenderObject * target,const FloatRect & targetBoundingBox,const FloatRect & paintInvalidationRect,GraphicsContext * context,ClipperState & clipperState)149 bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* target, const FloatRect& targetBoundingBox,
150     const FloatRect& paintInvalidationRect, GraphicsContext* context, ClipperState& clipperState)
151 {
152     ASSERT(target);
153     ASSERT(context);
154     ASSERT(clipperState == ClipperNotApplied);
155     ASSERT_WITH_SECURITY_IMPLICATION(!needsLayout());
156 
157     if (paintInvalidationRect.isEmpty() || m_inClipExpansion)
158         return false;
159     TemporaryChange<bool> inClipExpansionChange(m_inClipExpansion, true);
160 
161     AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->animatedLocalTransform();
162     // When drawing a clip for non-SVG elements, the CTM does not include the zoom factor.
163     // In this case, we need to apply the zoom scale explicitly - but only for clips with
164     // userSpaceOnUse units (the zoom is accounted for objectBoundingBox-resolved lengths).
165     if (!target->isSVG() && clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
166         ASSERT(style());
167         animatedLocalTransform.scale(style()->effectiveZoom());
168     }
169 
170     // First, try to apply the clip as a clipPath.
171     if (tryPathOnlyClipping(context, animatedLocalTransform, targetBoundingBox)) {
172         clipperState = ClipperAppliedPath;
173         return true;
174     }
175 
176     // Fall back to masking.
177     clipperState = ClipperAppliedMask;
178 
179     // Mask layer start
180     context->beginTransparencyLayer(1, &paintInvalidationRect);
181     {
182         GraphicsContextStateSaver maskContentSaver(*context);
183         context->concatCTM(animatedLocalTransform);
184 
185         // clipPath can also be clipped by another clipPath.
186         SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this);
187         RenderSVGResourceClipper* clipPathClipper = resources ? resources->clipper() : 0;
188         ClipperState clipPathClipperState = ClipperNotApplied;
189         if (clipPathClipper && !clipPathClipper->applyClippingToContext(this, targetBoundingBox, paintInvalidationRect, context, clipPathClipperState)) {
190             // FIXME: Awkward state micro-management. Ideally, GraphicsContextStateSaver should
191             //   a) pop saveLayers also
192             //   b) pop multiple states if needed (similarly to SkCanvas::restoreToCount())
193             // Then we should be able to replace this mess with a single, top-level GCSS.
194             maskContentSaver.restore();
195             context->restoreLayer();
196             return false;
197         }
198 
199         drawClipMaskContent(context, targetBoundingBox);
200 
201         if (clipPathClipper)
202             clipPathClipper->postApplyStatefulResource(this, context, clipPathClipperState);
203     }
204 
205     // Masked content layer start.
206     context->beginLayer(1, CompositeSourceIn, &paintInvalidationRect);
207 
208     return true;
209 }
210 
postApplyResource(RenderObject *,GraphicsContext * &)211 void RenderSVGResourceClipper::postApplyResource(RenderObject*, GraphicsContext*&)
212 {
213     // Clippers are always applied using stateful methods.
214     ASSERT_NOT_REACHED();
215 }
216 
postApplyStatefulResource(RenderObject *,GraphicsContext * & context,ClipperState & clipperState)217 void RenderSVGResourceClipper::postApplyStatefulResource(RenderObject*, GraphicsContext*& context, ClipperState& clipperState)
218 {
219     switch (clipperState) {
220     case ClipperAppliedPath:
221         // Path-only clipping, no layers to restore.
222         break;
223     case ClipperAppliedMask:
224         // Transfer content layer -> mask layer (SrcIn)
225         context->endLayer();
226         // Transfer mask layer -> bg layer (SrcOver)
227         context->endLayer();
228         break;
229     default:
230         ASSERT_NOT_REACHED();
231     }
232 }
233 
drawClipMaskContent(GraphicsContext * context,const FloatRect & targetBoundingBox)234 void RenderSVGResourceClipper::drawClipMaskContent(GraphicsContext* context, const FloatRect& targetBoundingBox)
235 {
236     ASSERT(context);
237 
238     AffineTransform contentTransformation;
239     if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
240         contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y());
241         contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height());
242         context->concatCTM(contentTransformation);
243     }
244 
245     if (!m_clipContentDisplayList)
246         createDisplayList(context, contentTransformation);
247 
248     ASSERT(m_clipContentDisplayList);
249     context->drawDisplayList(m_clipContentDisplayList.get());
250 }
251 
createDisplayList(GraphicsContext * context,const AffineTransform & contentTransformation)252 void RenderSVGResourceClipper::createDisplayList(GraphicsContext* context,
253     const AffineTransform& contentTransformation)
254 {
255     ASSERT(context);
256     ASSERT(frame());
257 
258     // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection
259     // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and
260     // userSpaceOnUse units (http://crbug.com/294900).
261     FloatRect bounds = strokeBoundingBox();
262     context->beginRecording(bounds);
263 
264     // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints:
265     // - fill-opacity/stroke-opacity/opacity set to 1
266     // - masker/filter not applied when rendering the children
267     // - fill is set to the initial fill paint server (solid, black)
268     // - stroke is set to the initial stroke paint server (none)
269     PaintBehavior oldBehavior = frame()->view()->paintBehavior();
270     frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask);
271 
272     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
273         RenderObject* renderer = childElement->renderer();
274         if (!renderer)
275             continue;
276 
277         RenderStyle* style = renderer->style();
278         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
279             continue;
280 
281         WindRule newClipRule = style->svgStyle().clipRule();
282         bool isUseElement = isSVGUseElement(*childElement);
283         if (isUseElement) {
284             SVGUseElement& useElement = toSVGUseElement(*childElement);
285             renderer = useElement.rendererClipChild();
286             if (!renderer)
287                 continue;
288             if (!useElement.hasAttribute(SVGNames::clip_ruleAttr))
289                 newClipRule = renderer->style()->svgStyle().clipRule();
290         }
291 
292         // Only shapes, paths and texts are allowed for clipping.
293         if (!renderer->isSVGShape() && !renderer->isSVGText())
294             continue;
295 
296         context->setFillRule(newClipRule);
297 
298         if (isUseElement)
299             renderer = childElement->renderer();
300 
301         SVGRenderingContext::renderSubtree(context, renderer, contentTransformation);
302     }
303 
304     frame()->view()->setPaintBehavior(oldBehavior);
305 
306     m_clipContentDisplayList = context->endRecording();
307 }
308 
calculateClipContentPaintInvalidationRect()309 void RenderSVGResourceClipper::calculateClipContentPaintInvalidationRect()
310 {
311     // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip.
312     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
313         RenderObject* renderer = childElement->renderer();
314         if (!renderer)
315             continue;
316         if (!renderer->isSVGShape() && !renderer->isSVGText() && !isSVGUseElement(*childElement))
317             continue;
318         RenderStyle* style = renderer->style();
319         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
320              continue;
321         m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->paintInvalidationRectInLocalCoordinates()));
322     }
323     m_clipBoundaries = toSVGClipPathElement(element())->animatedLocalTransform().mapRect(m_clipBoundaries);
324 }
325 
hitTestClipContent(const FloatRect & objectBoundingBox,const FloatPoint & nodeAtPoint)326 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint)
327 {
328     FloatPoint point = nodeAtPoint;
329     if (!SVGRenderSupport::pointInClippingArea(this, point))
330         return false;
331 
332     if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
333         AffineTransform transform;
334         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
335         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
336         point = transform.inverse().mapPoint(point);
337     }
338 
339     AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->animatedLocalTransform();
340     if (!animatedLocalTransform.isInvertible())
341         return false;
342 
343     point = animatedLocalTransform.inverse().mapPoint(point);
344 
345     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
346         RenderObject* renderer = childElement->renderer();
347         if (!renderer)
348             continue;
349         if (!renderer->isSVGShape() && !renderer->isSVGText() && !isSVGUseElement(*childElement))
350             continue;
351         IntPoint hitPoint;
352         HitTestResult result(hitPoint);
353         if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent), result, point, HitTestForeground))
354             return true;
355     }
356 
357     return false;
358 }
359 
resourceBoundingBox(const RenderObject * object)360 FloatRect RenderSVGResourceClipper::resourceBoundingBox(const RenderObject* object)
361 {
362     // Resource was not layouted yet. Give back the boundingBox of the object.
363     if (selfNeedsLayout())
364         return object->objectBoundingBox();
365 
366     if (m_clipBoundaries.isEmpty())
367         calculateClipContentPaintInvalidationRect();
368 
369     if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
370         FloatRect objectBoundingBox = object->objectBoundingBox();
371         AffineTransform transform;
372         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
373         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
374         return transform.mapRect(m_clipBoundaries);
375     }
376 
377     return m_clipBoundaries;
378 }
379 
380 }
381