1 /*
2 * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3 * 2008 Eric Seidel <eric@webkit.org>
4 * 2008 Dirk Schulze <krit@webkit.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #include "config.h"
29
30 #if ENABLE(SVG)
31 #include "SVGPaintServerGradient.h"
32
33 #include "FloatConversion.h"
34 #include "GraphicsContext.h"
35 #include "ImageBuffer.h"
36 #include "RenderObject.h"
37 #include "RenderView.h"
38 #include "SVGGradientElement.h"
39 #include "SVGPaintServerLinearGradient.h"
40 #include "SVGPaintServerRadialGradient.h"
41 #include "SVGRenderSupport.h"
42 #include "SVGRenderTreeAsText.h"
43
44 using namespace std;
45
46 namespace WebCore {
47
operator <<(TextStream & ts,GradientSpreadMethod m)48 static TextStream& operator<<(TextStream& ts, GradientSpreadMethod m)
49 {
50 switch (m) {
51 case SpreadMethodPad:
52 ts << "PAD"; break;
53 case SpreadMethodRepeat:
54 ts << "REPEAT"; break;
55 case SpreadMethodReflect:
56 ts << "REFLECT"; break;
57 }
58
59 return ts;
60 }
61
operator <<(TextStream & ts,const Vector<SVGGradientStop> & l)62 static TextStream& operator<<(TextStream& ts, const Vector<SVGGradientStop>& l)
63 {
64 ts << "[";
65 for (Vector<SVGGradientStop>::const_iterator it = l.begin(); it != l.end(); ++it) {
66 ts << "(" << it->first << "," << it->second << ")";
67 if (it + 1 != l.end())
68 ts << ", ";
69 }
70 ts << "]";
71 return ts;
72 }
73
SVGPaintServerGradient(const SVGGradientElement * owner)74 SVGPaintServerGradient::SVGPaintServerGradient(const SVGGradientElement* owner)
75 : m_boundingBoxMode(true)
76 , m_ownerElement(owner)
77 #if PLATFORM(CG)
78 , m_savedContext(0)
79 , m_imageBuffer(0)
80 #endif
81 {
82 ASSERT(owner);
83 }
84
~SVGPaintServerGradient()85 SVGPaintServerGradient::~SVGPaintServerGradient()
86 {
87 }
88
gradient() const89 Gradient* SVGPaintServerGradient::gradient() const
90 {
91 return m_gradient.get();
92 }
93
setGradient(PassRefPtr<Gradient> gradient)94 void SVGPaintServerGradient::setGradient(PassRefPtr<Gradient> gradient)
95 {
96 m_gradient = gradient;
97 }
98
boundingBoxMode() const99 bool SVGPaintServerGradient::boundingBoxMode() const
100 {
101 return m_boundingBoxMode;
102 }
103
setBoundingBoxMode(bool mode)104 void SVGPaintServerGradient::setBoundingBoxMode(bool mode)
105 {
106 m_boundingBoxMode = mode;
107 }
108
gradientTransform() const109 TransformationMatrix SVGPaintServerGradient::gradientTransform() const
110 {
111 return m_gradientTransform;
112 }
113
setGradientTransform(const TransformationMatrix & transform)114 void SVGPaintServerGradient::setGradientTransform(const TransformationMatrix& transform)
115 {
116 m_gradientTransform = transform;
117 }
118
119 #if PLATFORM(CG)
findTextRootObject(const RenderObject * start)120 static inline const RenderObject* findTextRootObject(const RenderObject* start)
121 {
122 while (start && !start->isSVGText())
123 start = start->parent();
124 ASSERT(start);
125 ASSERT(start->isSVGText());
126
127 return start;
128 }
129
createMaskAndSwapContextForTextGradient(GraphicsContext * & context,GraphicsContext * & savedContext,OwnPtr<ImageBuffer> & imageBuffer,const RenderObject * object)130 static inline bool createMaskAndSwapContextForTextGradient(
131 GraphicsContext*& context, GraphicsContext*& savedContext,
132 OwnPtr<ImageBuffer>& imageBuffer, const RenderObject* object)
133 {
134 FloatRect maskBBox = const_cast<RenderObject*>(findTextRootObject(object))->objectBoundingBox();
135 IntRect maskRect = enclosingIntRect(object->absoluteTransform().mapRect(maskBBox));
136
137 IntSize maskSize(maskRect.width(), maskRect.height());
138 clampImageBufferSizeToViewport(object->view()->frameView(), maskSize);
139
140 OwnPtr<ImageBuffer> maskImage = ImageBuffer::create(maskSize);
141
142 if (!maskImage)
143 return false;
144
145 GraphicsContext* maskImageContext = maskImage->context();
146
147 maskImageContext->save();
148 maskImageContext->translate(-maskRect.x(), -maskRect.y());
149 maskImageContext->concatCTM(object->absoluteTransform());
150
151 imageBuffer.set(maskImage.release());
152 savedContext = context;
153
154 context = maskImageContext;
155
156 return true;
157 }
158
clipToTextMask(GraphicsContext * context,OwnPtr<ImageBuffer> & imageBuffer,const RenderObject * object,const SVGPaintServerGradient * gradientServer)159 static inline TransformationMatrix clipToTextMask(GraphicsContext* context,
160 OwnPtr<ImageBuffer>& imageBuffer, const RenderObject* object,
161 const SVGPaintServerGradient* gradientServer)
162 {
163 FloatRect maskBBox = const_cast<RenderObject*>(findTextRootObject(object))->objectBoundingBox();
164
165 // Fixup transformations to be able to clip to mask
166 TransformationMatrix transform = object->absoluteTransform();
167 FloatRect textBoundary = transform.mapRect(maskBBox);
168
169 IntSize maskSize(lroundf(textBoundary.width()), lroundf(textBoundary.height()));
170 clampImageBufferSizeToViewport(object->view()->frameView(), maskSize);
171 textBoundary.setSize(textBoundary.size().shrunkTo(maskSize));
172
173 // Clip current context to mask image (gradient)
174 context->concatCTM(transform.inverse());
175 context->clipToImageBuffer(textBoundary, imageBuffer.get());
176 context->concatCTM(transform);
177
178 TransformationMatrix matrix;
179 if (gradientServer->boundingBoxMode()) {
180 matrix.translate(maskBBox.x(), maskBBox.y());
181 matrix.scaleNonUniform(maskBBox.width(), maskBBox.height());
182 }
183 matrix.multiply(gradientServer->gradientTransform());
184 return matrix;
185 }
186 #endif
187
setup(GraphicsContext * & context,const RenderObject * object,SVGPaintTargetType type,bool isPaintingText) const188 bool SVGPaintServerGradient::setup(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type, bool isPaintingText) const
189 {
190 m_ownerElement->buildGradient();
191
192 const SVGRenderStyle* style = object->style()->svgStyle();
193 bool isFilled = (type & ApplyToFillTargetType) && style->hasFill();
194 bool isStroked = (type & ApplyToStrokeTargetType) && style->hasStroke();
195
196 ASSERT(isFilled && !isStroked || !isFilled && isStroked);
197
198 context->save();
199
200 if (isPaintingText) {
201 #if PLATFORM(CG)
202 if (!createMaskAndSwapContextForTextGradient(context, m_savedContext, m_imageBuffer, object)) {
203 context->restore();
204 return false;
205 }
206 #endif
207 context->setTextDrawingMode(isFilled ? cTextFill : cTextStroke);
208 }
209
210 if (isFilled) {
211 context->setAlpha(style->fillOpacity());
212 context->setFillGradient(m_gradient);
213 context->setFillRule(style->fillRule());
214 }
215 if (isStroked) {
216 context->setAlpha(style->strokeOpacity());
217 context->setStrokeGradient(m_gradient);
218 applyStrokeStyleToContext(context, object->style(), object);
219 }
220
221 TransformationMatrix matrix;
222 // CG platforms will handle the gradient space transform for text in
223 // teardown, so we don't apply it here. For non-CG platforms, we
224 // want the text bounding box applied to the gradient space transform now,
225 // so the gradient shader can use it.
226 #if PLATFORM(CG)
227 if (boundingBoxMode() && !isPaintingText) {
228 #else
229 if (boundingBoxMode()) {
230 #endif
231 FloatRect bbox = object->objectBoundingBox();
232 // Don't use gradients for 1d objects like horizontal/vertical
233 // lines or rectangles without width or height.
234 if (bbox.width() == 0 || bbox.height() == 0) {
235 Color color(0, 0, 0);
236 context->setStrokeColor(color);
237 return true;
238 }
239 matrix.translate(bbox.x(), bbox.y());
240 matrix.scaleNonUniform(bbox.width(), bbox.height());
241 }
242 matrix.multiply(gradientTransform());
243 m_gradient->setGradientSpaceTransform(matrix);
244
245 return true;
246 }
247
248 void SVGPaintServerGradient::teardown(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType, bool isPaintingText) const
249 {
250 #if PLATFORM(CG)
251 // renderPath() is not used when painting text, so we paint the gradient during teardown()
252 if (isPaintingText && m_savedContext) {
253
254 // Restore on-screen drawing context
255 context = m_savedContext;
256 m_savedContext = 0;
257
258 TransformationMatrix matrix = clipToTextMask(context, m_imageBuffer, object, this);
259 m_gradient->setGradientSpaceTransform(matrix);
260 context->setFillGradient(m_gradient);
261
262 FloatRect maskBBox = const_cast<RenderObject*>(findTextRootObject(object))->objectBoundingBox();
263
264 context->fillRect(maskBBox);
265
266 m_imageBuffer.clear(); // we're done with our text mask buffer
267 }
268 #endif
269 context->restore();
270 }
271
272 TextStream& SVGPaintServerGradient::externalRepresentation(TextStream& ts) const
273 {
274 // Gradients/patterns aren't setup, until they are used for painting. Work around that fact.
275 m_ownerElement->buildGradient();
276
277 // abstract, don't stream type
278 ts << "[stops=" << gradientStops() << "]";
279 if (m_gradient->spreadMethod() != SpreadMethodPad)
280 ts << "[method=" << m_gradient->spreadMethod() << "]";
281 if (!boundingBoxMode())
282 ts << " [bounding box mode=" << boundingBoxMode() << "]";
283 if (!gradientTransform().isIdentity())
284 ts << " [transform=" << gradientTransform() << "]";
285
286 return ts;
287 }
288
289 } // namespace WebCore
290
291 #endif
292