2011-01-19 Shane Stephens <shanestephens@google.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 PLATFORM(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 PLATFORM(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         collectGradientAttributes(gradientElement);
159         m_shouldCollectGradientAttributes = false;
160     }
161
162     // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified,
163     // then the given effect (e.g. a gradient or a filter) will be ignored.
164     FloatRect objectBoundingBox = object->objectBoundingBox();
165     if (boundingBoxMode() && objectBoundingBox.isEmpty())
166         return false;
167
168     if (!m_gradient.contains(object))
169         m_gradient.set(object, new GradientData);
170
171     GradientData* gradientData = m_gradient.get(object);
172     bool isPaintingText = resourceMode & ApplyToTextMode;
173
174     // Create gradient object
175     if (!gradientData->gradient) {
176         buildGradient(gradientData, gradientElement);
177
178         // CG platforms will handle the gradient space transform for text after applying the
179         // resource, so don't apply it here. For non-CG platforms, we want the text bounding
180         // box applied to the gradient space transform now, so the gradient shader can use it.
181 #if PLATFORM(CG)
182         if (boundingBoxMode() && !objectBoundingBox.isEmpty() && !isPaintingText) {
183 #else
184         if (boundingBoxMode() && !objectBoundingBox.isEmpty()) {
185 #endif
186             gradientData->userspaceTransform.translate(objectBoundingBox.x(), objectBoundingBox.y());
187             gradientData->userspaceTransform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
188         }
189
190         AffineTransform gradientTransform;
191         calculateGradientTransform(gradientTransform);
192
193         gradientData->userspaceTransform *= gradientTransform;
194         gradientData->gradient->setGradientSpaceTransform(gradientData->userspaceTransform);
195     }
196
197     if (!gradientData->gradient)
198         return false;
199
200     // Draw gradient
201     context->save();
202
203     if (isPaintingText) {
204 #if PLATFORM(CG)
205         if (!createMaskAndSwapContextForTextGradient(context, m_savedContext, m_imageBuffer, object)) {
206             context->restore();
207             return false;
208         }
209 #endif
210
211         context->setTextDrawingMode(resourceMode & ApplyToFillMode ? TextModeFill : TextModeStroke);
212     }
213
214     const SVGRenderStyle* svgStyle = style->svgStyle();
215     ASSERT(svgStyle);
216
217     if (resourceMode & ApplyToFillMode) {
218         context->setAlpha(svgStyle->fillOpacity());
219         context->setFillGradient(gradientData->gradient);
220         context->setFillRule(svgStyle->fillRule());
221     } else if (resourceMode & ApplyToStrokeMode) {
222         if (svgStyle->vectorEffect() == VE_NON_SCALING_STROKE)
223             gradientData->gradient->setGradientSpaceTransform(transformOnNonScalingStroke(object, gradientData->userspaceTransform));
224         context->setAlpha(svgStyle->strokeOpacity());
225         context->setStrokeGradient(gradientData->gradient);
226         SVGRenderSupport::applyStrokeStyleToContext(context, style, object);
227     }
228
229     return true;
230 }
231
232 void RenderSVGResourceGradient::postApplyResource(RenderObject* object, GraphicsContext*& context, unsigned short resourceMode, const Path* path)
233 {
234     ASSERT(context);
235     ASSERT(resourceMode != ApplyToDefaultMode);
236
237     if (resourceMode & ApplyToTextMode) {
238 #if PLATFORM(CG)
239         // CG requires special handling for gradient on text
240         if (m_savedContext && m_gradient.contains(object)) {
241             GradientData* gradientData = m_gradient.get(object);
242
243             // Restore on-screen drawing context
244             context = m_savedContext;
245             m_savedContext = 0;
246
247             AffineTransform gradientTransform;
248             calculateGradientTransform(gradientTransform);
249
250             FloatRect targetRect;
251             gradientData->gradient->setGradientSpaceTransform(clipToTextMask(context, m_imageBuffer, targetRect, object, boundingBoxMode(), gradientTransform));
252             context->setFillGradient(gradientData->gradient);
253
254             context->fillRect(targetRect);
255             m_imageBuffer.clear();
256         }
257 #else
258         UNUSED_PARAM(object);
259 #endif
260     } else if (path) {
261         if (resourceMode & ApplyToFillMode)
262             context->fillPath(*path);
263         else if (resourceMode & ApplyToStrokeMode)
264             context->strokePath(*path);
265     }
266
267     context->restore();
268 }
269
270 void RenderSVGResourceGradient::addStops(GradientData* gradientData, const Vector<Gradient::ColorStop>& stops) const
271 {
272     ASSERT(gradientData->gradient);
273
274     const Vector<Gradient::ColorStop>::const_iterator end = stops.end();
275     for (Vector<Gradient::ColorStop>::const_iterator it = stops.begin(); it != end; ++it)
276         gradientData->gradient->addColorStop(*it);
277 }
278
279 }
280
281 #endif