1 /*
2 * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #include "config.h"
22
23 #include "core/rendering/svg/RenderSVGResourcePattern.h"
24
25 #include "core/rendering/svg/SVGRenderSupport.h"
26 #include "core/rendering/svg/SVGRenderingContext.h"
27 #include "core/svg/SVGFitToViewBox.h"
28 #include "platform/graphics/GraphicsContext.h"
29
30 namespace WebCore {
31
32 const RenderSVGResourceType RenderSVGResourcePattern::s_resourceType = PatternResourceType;
33
RenderSVGResourcePattern(SVGPatternElement * node)34 RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement* node)
35 : RenderSVGResourceContainer(node)
36 , m_shouldCollectPatternAttributes(true)
37 {
38 }
39
removeAllClientsFromCache(bool markForInvalidation)40 void RenderSVGResourcePattern::removeAllClientsFromCache(bool markForInvalidation)
41 {
42 m_patternMap.clear();
43 m_shouldCollectPatternAttributes = true;
44 markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
45 }
46
removeClientFromCache(RenderObject * client,bool markForInvalidation)47 void RenderSVGResourcePattern::removeClientFromCache(RenderObject* client, bool markForInvalidation)
48 {
49 ASSERT(client);
50 m_patternMap.remove(client);
51 markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
52 }
53
buildPattern(RenderObject * object,unsigned short resourceMode)54 PatternData* RenderSVGResourcePattern::buildPattern(RenderObject* object, unsigned short resourceMode)
55 {
56 ASSERT(object);
57 PatternData* currentData = m_patternMap.get(object);
58 if (currentData && currentData->pattern)
59 return currentData;
60
61 SVGPatternElement* patternElement = toSVGPatternElement(element());
62 if (!patternElement)
63 return 0;
64
65 if (m_shouldCollectPatternAttributes) {
66 patternElement->synchronizeAnimatedSVGAttribute(anyQName());
67
68 m_attributes = PatternAttributes();
69 patternElement->collectPatternAttributes(m_attributes);
70 m_shouldCollectPatternAttributes = false;
71 }
72
73 // If we couldn't determine the pattern content element root, stop here.
74 if (!m_attributes.patternContentElement())
75 return 0;
76
77 // An empty viewBox disables rendering.
78 if (m_attributes.hasViewBox() && m_attributes.viewBox().isEmpty())
79 return 0;
80
81 // Compute all necessary transformations to build the tile image & the pattern.
82 FloatRect tileBoundaries;
83 AffineTransform tileImageTransform;
84 if (!buildTileImageTransform(object, m_attributes, patternElement, tileBoundaries, tileImageTransform))
85 return 0;
86
87 AffineTransform absoluteTransformIgnoringRotation;
88 SVGRenderingContext::calculateDeviceSpaceTransformation(object, absoluteTransformIgnoringRotation);
89
90 // Ignore 2D rotation, as it doesn't affect the size of the tile.
91 SVGRenderingContext::clear2DRotation(absoluteTransformIgnoringRotation);
92 FloatRect absoluteTileBoundaries = absoluteTransformIgnoringRotation.mapRect(tileBoundaries);
93
94 // Scale the tile size to match the scale level of the patternTransform.
95 absoluteTileBoundaries.scale(static_cast<float>(m_attributes.patternTransform().xScale()),
96 static_cast<float>(m_attributes.patternTransform().yScale()));
97
98 // Build tile image.
99 OwnPtr<ImageBuffer> tileImage = createTileImage(m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform);
100 if (!tileImage)
101 return 0;
102
103 RefPtr<Image> copiedImage = tileImage->copyImage(CopyBackingStore);
104 if (!copiedImage)
105 return 0;
106
107 // Build pattern.
108 OwnPtr<PatternData> patternData = adoptPtr(new PatternData);
109 patternData->pattern = Pattern::create(copiedImage, true, true);
110
111 // Compute pattern space transformation.
112 const IntSize tileImageSize = tileImage->size();
113 patternData->transform.translate(tileBoundaries.x(), tileBoundaries.y());
114 patternData->transform.scale(tileBoundaries.width() / tileImageSize.width(), tileBoundaries.height() / tileImageSize.height());
115
116 AffineTransform patternTransform = m_attributes.patternTransform();
117 if (!patternTransform.isIdentity())
118 patternData->transform = patternTransform * patternData->transform;
119
120 // Various calls above may trigger invalidations in some fringe cases (ImageBuffer allocation
121 // failures in the SVG image cache for example). To avoid having our PatternData deleted by
122 // removeAllClientsFromCache(), we only make it visible in the cache at the very end.
123 return m_patternMap.set(object, patternData.release()).storedValue->value.get();
124 }
125
applyResource(RenderObject * object,RenderStyle * style,GraphicsContext * & context,unsigned short resourceMode)126 bool RenderSVGResourcePattern::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode)
127 {
128 ASSERT(object);
129 ASSERT(style);
130 ASSERT(context);
131 ASSERT(resourceMode != ApplyToDefaultMode);
132
133 clearInvalidationMask();
134
135 // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified,
136 // then the given effect (e.g. a gradient or a filter) will be ignored.
137 FloatRect objectBoundingBox = object->objectBoundingBox();
138 if (m_attributes.patternUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && objectBoundingBox.isEmpty())
139 return false;
140
141 PatternData* patternData = buildPattern(object, resourceMode);
142 if (!patternData)
143 return false;
144
145 const SVGRenderStyle* svgStyle = style->svgStyle();
146 ASSERT(svgStyle);
147
148 AffineTransform computedPatternSpaceTransform = computeResourceSpaceTransform(object, patternData->transform, svgStyle, resourceMode);
149 patternData->pattern->setPatternSpaceTransform(computedPatternSpaceTransform);
150
151 // Draw pattern
152 context->save();
153
154 if (resourceMode & ApplyToFillMode) {
155 context->setAlphaAsFloat(svgStyle->fillOpacity());
156 context->setFillPattern(patternData->pattern);
157 context->setFillRule(svgStyle->fillRule());
158 } else if (resourceMode & ApplyToStrokeMode) {
159 context->setAlphaAsFloat(svgStyle->strokeOpacity());
160 context->setStrokePattern(patternData->pattern);
161 SVGRenderSupport::applyStrokeStyleToContext(context, style, object);
162 }
163
164 if (resourceMode & ApplyToTextMode) {
165 if (resourceMode & ApplyToFillMode)
166 context->setTextDrawingMode(TextModeFill);
167 else if (resourceMode & ApplyToStrokeMode)
168 context->setTextDrawingMode(TextModeStroke);
169 }
170
171 return true;
172 }
173
postApplyResource(RenderObject *,GraphicsContext * & context,unsigned short resourceMode,const Path * path,const RenderSVGShape * shape)174 void RenderSVGResourcePattern::postApplyResource(RenderObject*, GraphicsContext*& context, unsigned short resourceMode, const Path* path, const RenderSVGShape* shape)
175 {
176 ASSERT(context);
177 ASSERT(resourceMode != ApplyToDefaultMode);
178
179 if (resourceMode & ApplyToFillMode) {
180 if (path)
181 context->fillPath(*path);
182 else if (shape)
183 shape->fillShape(context);
184 }
185 if (resourceMode & ApplyToStrokeMode) {
186 if (path)
187 context->strokePath(*path);
188 else if (shape)
189 shape->strokeShape(context);
190 }
191
192 context->restore();
193 }
194
calculatePatternBoundaries(const PatternAttributes & attributes,const FloatRect & objectBoundingBox,const SVGPatternElement * patternElement)195 static inline FloatRect calculatePatternBoundaries(const PatternAttributes& attributes,
196 const FloatRect& objectBoundingBox,
197 const SVGPatternElement* patternElement)
198 {
199 ASSERT(patternElement);
200 return SVGLengthContext::resolveRectangle(patternElement, attributes.patternUnits(), objectBoundingBox, attributes.x(), attributes.y(), attributes.width(), attributes.height());
201 }
202
buildTileImageTransform(RenderObject * renderer,const PatternAttributes & attributes,const SVGPatternElement * patternElement,FloatRect & patternBoundaries,AffineTransform & tileImageTransform) const203 bool RenderSVGResourcePattern::buildTileImageTransform(RenderObject* renderer,
204 const PatternAttributes& attributes,
205 const SVGPatternElement* patternElement,
206 FloatRect& patternBoundaries,
207 AffineTransform& tileImageTransform) const
208 {
209 ASSERT(renderer);
210 ASSERT(patternElement);
211
212 FloatRect objectBoundingBox = renderer->objectBoundingBox();
213 patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement);
214 if (patternBoundaries.width() <= 0 || patternBoundaries.height() <= 0)
215 return false;
216
217 AffineTransform viewBoxCTM = SVGFitToViewBox::viewBoxToViewTransform(attributes.viewBox(), attributes.preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height());
218
219 // Apply viewBox/objectBoundingBox transformations.
220 if (!viewBoxCTM.isIdentity())
221 tileImageTransform = viewBoxCTM;
222 else if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
223 tileImageTransform.scale(objectBoundingBox.width(), objectBoundingBox.height());
224
225 return true;
226 }
227
createTileImage(const PatternAttributes & attributes,const FloatRect & tileBoundaries,const FloatRect & absoluteTileBoundaries,const AffineTransform & tileImageTransform) const228 PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(const PatternAttributes& attributes,
229 const FloatRect& tileBoundaries,
230 const FloatRect& absoluteTileBoundaries,
231 const AffineTransform& tileImageTransform) const
232 {
233 FloatRect clampedAbsoluteTileBoundaries = SVGRenderingContext::clampedAbsoluteTargetRect(absoluteTileBoundaries);
234
235 IntSize imageSize(roundedIntSize(clampedAbsoluteTileBoundaries.size()));
236 if (imageSize.isEmpty())
237 return nullptr;
238 OwnPtr<ImageBuffer> tileImage = ImageBuffer::create(imageSize);
239 if (!tileImage)
240 return nullptr;
241
242 GraphicsContext* tileImageContext = tileImage->context();
243 ASSERT(tileImageContext);
244 IntSize unclampedImageSize(roundedIntSize(absoluteTileBoundaries.size()));
245 tileImageContext->scale(unclampedImageSize.width() / absoluteTileBoundaries.width(), unclampedImageSize.height() / absoluteTileBoundaries.height());
246
247 // The image buffer represents the final rendered size, so the content has to be scaled (to avoid pixelation).
248 tileImageContext->scale(
249 clampedAbsoluteTileBoundaries.width() / tileBoundaries.width(),
250 clampedAbsoluteTileBoundaries.height() / tileBoundaries.height());
251
252 // Apply tile image transformations.
253 if (!tileImageTransform.isIdentity())
254 tileImageContext->concatCTM(tileImageTransform);
255
256 AffineTransform contentTransformation;
257 if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
258 contentTransformation = tileImageTransform;
259
260 // Draw the content into the ImageBuffer.
261 for (Element* element = ElementTraversal::firstWithin(*attributes.patternContentElement()); element; element = ElementTraversal::nextSibling(*element)) {
262 if (!element->isSVGElement() || !element->renderer())
263 continue;
264 if (element->renderer()->needsLayout())
265 return nullptr;
266 SVGRenderingContext::renderSubtree(tileImage->context(), element->renderer(), contentTransformation);
267 }
268
269 return tileImage.release();
270 }
271
272 }
273