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