2011-06-23 Jeffrey Pfau <jpfau@apple.com>
[WebKit-https.git] / Source / WebCore / rendering / svg / RenderSVGResourceGradient.cpp
1 /*
2  * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3  * Copyright (C) 2008 Eric Seidel <eric@webkit.org>
4  * Copyright (C) 2008 Dirk Schulze <krit@webkit.org>
5  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
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 "RenderSVGResourceGradient.h"
27
28 #include "GradientAttributes.h"
29 #include "GraphicsContext.h"
30 #include "RenderSVGText.h"
31 #include "SVGImageBufferTools.h"
32 #include "SVGRenderSupport.h"
33 #include <wtf/UnusedParam.h>
34
35 namespace WebCore {
36
37 RenderSVGResourceGradient::RenderSVGResourceGradient(SVGGradientElement* node)
38     : RenderSVGResourceContainer(node)
39     , m_shouldCollectGradientAttributes(true)
40 #if USE(CG)
41     , m_savedContext(0)
42 #endif
43 {
44 }
45
46 RenderSVGResourceGradient::~RenderSVGResourceGradient()
47 {
48     if (m_gradient.isEmpty())
49         return;
50
51     deleteAllValues(m_gradient);
52     m_gradient.clear();
53 }
54
55 void RenderSVGResourceGradient::removeAllClientsFromCache(bool markForInvalidation)
56 {
57     if (!m_gradient.isEmpty()) {
58         deleteAllValues(m_gradient);
59         m_gradient.clear();
60     }
61
62     m_shouldCollectGradientAttributes = true;
63     markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
64 }
65
66 void RenderSVGResourceGradient::removeClientFromCache(RenderObject* client, bool markForInvalidation)
67 {
68     ASSERT(client);
69
70     if (m_gradient.contains(client))
71         delete m_gradient.take(client);
72
73     markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
74 }
75
76 #if USE(CG)
77 static inline bool createMaskAndSwapContextForTextGradient(GraphicsContext*& context,
78                                                            GraphicsContext*& savedContext,
79                                                            OwnPtr<ImageBuffer>& imageBuffer,
80                                                            RenderObject* object)
81 {
82     RenderObject* textRootBlock = RenderSVGText::locateRenderSVGTextAncestor(object);
83     ASSERT(textRootBlock);
84
85     AffineTransform absoluteTransform;
86     SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(textRootBlock, absoluteTransform);
87
88     FloatRect absoluteTargetRect = absoluteTransform.mapRect(textRootBlock->repaintRectInLocalCoordinates());
89     FloatRect clampedAbsoluteTargetRect = SVGImageBufferTools::clampedAbsoluteTargetRectForRenderer(textRootBlock, absoluteTargetRect);
90     if (clampedAbsoluteTargetRect.isEmpty())
91         return false;
92
93     OwnPtr<ImageBuffer> maskImage;
94     if (!SVGImageBufferTools::createImageBuffer(absoluteTargetRect, clampedAbsoluteTargetRect, maskImage, ColorSpaceDeviceRGB))
95         return false;
96
97     GraphicsContext* maskImageContext = maskImage->context();
98     ASSERT(maskImageContext);
99
100     maskImageContext->translate(-clampedAbsoluteTargetRect.x(), -clampedAbsoluteTargetRect.y());
101     maskImageContext->concatCTM(absoluteTransform);
102
103     ASSERT(maskImage);
104     savedContext = context;
105     context = maskImageContext;
106     imageBuffer = maskImage.release();
107     return true;
108 }
109
110 static inline AffineTransform clipToTextMask(GraphicsContext* context,
111                                              OwnPtr<ImageBuffer>& imageBuffer,
112                                              FloatRect& targetRect,
113                                              RenderObject* object,
114                                              bool boundingBoxMode,
115                                              const AffineTransform& gradientTransform)
116 {
117     RenderObject* textRootBlock = RenderSVGText::locateRenderSVGTextAncestor(object);
118     ASSERT(textRootBlock);
119
120     targetRect = textRootBlock->repaintRectInLocalCoordinates();
121
122     AffineTransform absoluteTransform;
123     SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(textRootBlock, absoluteTransform);
124
125     FloatRect absoluteTargetRect = absoluteTransform.mapRect(targetRect);
126     FloatRect clampedAbsoluteTargetRect = SVGImageBufferTools::clampedAbsoluteTargetRectForRenderer(textRootBlock, absoluteTargetRect);
127
128     SVGImageBufferTools::clipToImageBuffer(context, absoluteTransform, clampedAbsoluteTargetRect, imageBuffer);
129
130     AffineTransform matrix;
131     if (boundingBoxMode) {
132         FloatRect maskBoundingBox = textRootBlock->objectBoundingBox();
133         matrix.translate(maskBoundingBox.x(), maskBoundingBox.y());
134         matrix.scaleNonUniform(maskBoundingBox.width(), maskBoundingBox.height());
135     }
136     matrix *= gradientTransform;
137     return matrix;
138 }
139 #endif
140
141 bool RenderSVGResourceGradient::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode)
142 {
143     ASSERT(object);
144     ASSERT(style);
145     ASSERT(context);
146     ASSERT(resourceMode != ApplyToDefaultMode);
147
148     // Be sure to synchronize all SVG properties on the gradientElement _before_ processing any further.
149     // Otherwhise the call to collectGradientAttributes() in createTileImage(), may cause the SVG DOM property
150     // synchronization to kick in, which causes removeAllClientsFromCache() to be called, which in turn deletes our
151     // GradientData object! Leaving out the line below will cause svg/dynamic-updates/SVG*GradientElement-svgdom* to crash.
152     SVGGradientElement* gradientElement = static_cast<SVGGradientElement*>(node());
153     if (!gradientElement)
154         return false;
155
156     if (m_shouldCollectGradientAttributes) {
157         gradientElement->updateAnimatedSVGAttribute(anyQName());
158         if (!collectGradientAttributes(gradientElement))
159             return false;
160
161         m_shouldCollectGradientAttributes = false;
162     }
163
164     // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified,
165     // then the given effect (e.g. a gradient or a filter) will be ignored.
166     FloatRect objectBoundingBox = object->objectBoundingBox();
167     if (boundingBoxMode() && objectBoundingBox.isEmpty())
168         return false;
169
170     if (!m_gradient.contains(object))
171         m_gradient.set(object, new GradientData);
172
173     GradientData* gradientData = m_gradient.get(object);
174     bool isPaintingText = resourceMode & ApplyToTextMode;
175
176     // Create gradient object
177     if (!gradientData->gradient) {
178         buildGradient(gradientData, gradientElement);
179
180         // CG platforms will handle the gradient space transform for text after applying the
181         // resource, so don't apply it here. For non-CG platforms, we want the text bounding
182         // box applied to the gradient space transform now, so the gradient shader can use it.
183 #if USE(CG)
184         if (boundingBoxMode() && !objectBoundingBox.isEmpty() && !isPaintingText) {
185 #else
186         if (boundingBoxMode() && !objectBoundingBox.isEmpty()) {
187 #endif
188             gradientData->userspaceTransform.translate(objectBoundingBox.x(), objectBoundingBox.y());
189             gradientData->userspaceTransform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
190         }
191
192         AffineTransform gradientTransform;
193         calculateGradientTransform(gradientTransform);
194
195         gradientData->userspaceTransform *= gradientTransform;
196         gradientData->gradient->setGradientSpaceTransform(gradientData->userspaceTransform);
197     }
198
199     if (!gradientData->gradient)
200         return false;
201
202     // Draw gradient
203     context->save();
204
205     if (isPaintingText) {
206 #if USE(CG)
207         if (!createMaskAndSwapContextForTextGradient(context, m_savedContext, m_imageBuffer, object)) {
208             context->restore();
209             return false;
210         }
211 #endif
212
213         context->setTextDrawingMode(resourceMode & ApplyToFillMode ? TextModeFill : TextModeStroke);
214     }
215
216     const SVGRenderStyle* svgStyle = style->svgStyle();
217     ASSERT(svgStyle);
218
219     if (resourceMode & ApplyToFillMode) {
220         context->setAlpha(svgStyle->fillOpacity());
221         context->setFillGradient(gradientData->gradient);
222         context->setFillRule(svgStyle->fillRule());
223     } else if (resourceMode & ApplyToStrokeMode) {
224         if (svgStyle->vectorEffect() == VE_NON_SCALING_STROKE)
225             gradientData->gradient->setGradientSpaceTransform(transformOnNonScalingStroke(object, gradientData->userspaceTransform));
226         context->setAlpha(svgStyle->strokeOpacity());
227         context->setStrokeGradient(gradientData->gradient);
228         SVGRenderSupport::applyStrokeStyleToContext(context, style, object);
229     }
230
231     return true;
232 }
233
234 void RenderSVGResourceGradient::postApplyResource(RenderObject* object, GraphicsContext*& context, unsigned short resourceMode, const Path* path)
235 {
236     ASSERT(context);
237     ASSERT(resourceMode != ApplyToDefaultMode);
238
239     if (resourceMode & ApplyToTextMode) {
240 #if USE(CG)
241         // CG requires special handling for gradient on text
242         if (m_savedContext && m_gradient.contains(object)) {
243             GradientData* gradientData = m_gradient.get(object);
244
245             // Restore on-screen drawing context
246             context = m_savedContext;
247             m_savedContext = 0;
248
249             AffineTransform gradientTransform;
250             calculateGradientTransform(gradientTransform);
251
252             FloatRect targetRect;
253             gradientData->gradient->setGradientSpaceTransform(clipToTextMask(context, m_imageBuffer, targetRect, object, boundingBoxMode(), gradientTransform));
254             context->setFillGradient(gradientData->gradient);
255
256             context->fillRect(targetRect);
257             m_imageBuffer.clear();
258         }
259 #else
260         UNUSED_PARAM(object);
261 #endif
262     } else if (path) {
263         if (resourceMode & ApplyToFillMode)
264             context->fillPath(*path);
265         else if (resourceMode & ApplyToStrokeMode)
266             context->strokePath(*path);
267     }
268
269     context->restore();
270 }
271
272 void RenderSVGResourceGradient::addStops(GradientData* gradientData, const Vector<Gradient::ColorStop>& stops) const
273 {
274     ASSERT(gradientData->gradient);
275
276     const Vector<Gradient::ColorStop>::const_iterator end = stops.end();
277     for (Vector<Gradient::ColorStop>::const_iterator it = stops.begin(); it != end; ++it)
278         gradientData->gradient->addColorStop(*it);
279 }
280
281 GradientSpreadMethod RenderSVGResourceGradient::platformSpreadMethodFromSVGType(SVGGradientElement::SVGSpreadMethodType method) const
282 {
283     switch (method) {
284     case SVGGradientElement::SVG_SPREADMETHOD_UNKNOWN:
285     case SVGGradientElement::SVG_SPREADMETHOD_PAD:
286         return SpreadMethodPad;
287     case SVGGradientElement::SVG_SPREADMETHOD_REFLECT:
288         return SpreadMethodReflect;
289     case SVGGradientElement::SVG_SPREADMETHOD_REPEAT:
290         return SpreadMethodRepeat;
291     }
292
293     ASSERT_NOT_REACHED();
294     return SpreadMethodPad;
295 }
296
297 }
298
299 #endif