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