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