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#include "RenderSVGResourcePattern.h"
23
24#include "ElementIterator.h"
25#include "FrameView.h"
26#include "GraphicsContext.h"
27#include "RenderSVGRoot.h"
28#include "SVGFitToViewBox.h"
29#include "SVGRenderingContext.h"
30#include "SVGResources.h"
31#include "SVGResourcesCache.h"
32#include <wtf/IsoMallocInlines.h>
33
34namespace WebCore {
35
36WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGResourcePattern);
37
38RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement& element, RenderStyle&& style)
39 : RenderSVGResourceContainer(element, WTFMove(style))
40{
41}
42
43SVGPatternElement& RenderSVGResourcePattern::patternElement() const
44{
45 return downcast<SVGPatternElement>(RenderSVGResourceContainer::element());
46}
47
48void RenderSVGResourcePattern::removeAllClientsFromCache(bool markForInvalidation)
49{
50 m_patternMap.clear();
51 m_shouldCollectPatternAttributes = true;
52 markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
53}
54
55void RenderSVGResourcePattern::removeClientFromCache(RenderElement& client, bool markForInvalidation)
56{
57 m_patternMap.remove(&client);
58 markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
59}
60
61void RenderSVGResourcePattern::collectPatternAttributes(PatternAttributes& attributes) const
62{
63 const RenderSVGResourcePattern* current = this;
64
65 while (current) {
66 const SVGPatternElement& pattern = current->patternElement();
67 pattern.collectPatternAttributes(attributes);
68
69 auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*current);
70 ASSERT_IMPLIES(resources && resources->linkedResource(), is<RenderSVGResourcePattern>(resources->linkedResource()));
71 current = resources ? downcast<RenderSVGResourcePattern>(resources->linkedResource()) : nullptr;
72 }
73}
74
75PatternData* RenderSVGResourcePattern::buildPattern(RenderElement& renderer, OptionSet<RenderSVGResourceMode> resourceMode, GraphicsContext& context)
76{
77 ASSERT(!m_shouldCollectPatternAttributes);
78
79 PatternData* currentData = m_patternMap.get(&renderer);
80 if (currentData && currentData->pattern)
81 return currentData;
82
83 // If we couldn't determine the pattern content element root, stop here.
84 if (!m_attributes.patternContentElement())
85 return nullptr;
86
87 // An empty viewBox disables rendering.
88 if (m_attributes.hasViewBox() && m_attributes.viewBox().isEmpty())
89 return nullptr;
90
91 // Compute all necessary transformations to build the tile image & the pattern.
92 FloatRect tileBoundaries;
93 AffineTransform tileImageTransform;
94 if (!buildTileImageTransform(renderer, m_attributes, patternElement(), tileBoundaries, tileImageTransform))
95 return nullptr;
96
97 AffineTransform absoluteTransformIgnoringRotation = SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(renderer);
98
99 // Ignore 2D rotation, as it doesn't affect the size of the tile.
100 SVGRenderingContext::clear2DRotation(absoluteTransformIgnoringRotation);
101 FloatRect absoluteTileBoundaries = absoluteTransformIgnoringRotation.mapRect(tileBoundaries);
102 FloatRect clampedAbsoluteTileBoundaries;
103
104 // Scale the tile size to match the scale level of the patternTransform.
105 absoluteTileBoundaries.scale(static_cast<float>(m_attributes.patternTransform().xScale()),
106 static_cast<float>(m_attributes.patternTransform().yScale()));
107
108 // Build tile image.
109 auto tileImage = createTileImage(m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform, clampedAbsoluteTileBoundaries, context.renderingMode());
110 if (!tileImage)
111 return nullptr;
112
113 const IntSize tileImageSize = tileImage->logicalSize();
114
115 RefPtr<Image> copiedImage = ImageBuffer::sinkIntoImage(WTFMove(tileImage));
116 if (!copiedImage)
117 return nullptr;
118
119 // Build pattern.
120 auto patternData = std::make_unique<PatternData>();
121 patternData->pattern = Pattern::create(copiedImage.releaseNonNull(), true, true);
122
123 // Compute pattern space transformation.
124
125 patternData->transform.translate(tileBoundaries.location());
126 patternData->transform.scale(tileBoundaries.size() / tileImageSize);
127
128 AffineTransform patternTransform = m_attributes.patternTransform();
129 if (!patternTransform.isIdentity())
130 patternData->transform = patternTransform * patternData->transform;
131
132 // Account for text drawing resetting the context to non-scaled, see SVGInlineTextBox::paintTextWithShadows.
133 if (resourceMode.contains(RenderSVGResourceMode::ApplyToText)) {
134 AffineTransform additionalTextTransformation;
135 if (shouldTransformOnTextPainting(renderer, additionalTextTransformation))
136 patternData->transform *= additionalTextTransformation;
137 }
138 patternData->pattern->setPatternSpaceTransform(patternData->transform);
139
140 // Various calls above may trigger invalidations in some fringe cases (ImageBuffer allocation
141 // failures in the SVG image cache for example). To avoid having our PatternData deleted by
142 // removeAllClientsFromCache(), we only make it visible in the cache at the very end.
143 return m_patternMap.set(&renderer, WTFMove(patternData)).iterator->value.get();
144}
145
146bool RenderSVGResourcePattern::applyResource(RenderElement& renderer, const RenderStyle& style, GraphicsContext*& context, OptionSet<RenderSVGResourceMode> resourceMode)
147{
148 ASSERT(context);
149 ASSERT(!resourceMode.isEmpty());
150
151 if (m_shouldCollectPatternAttributes) {
152 patternElement().synchronizeAllAttributes();
153
154 m_attributes = PatternAttributes();
155 collectPatternAttributes(m_attributes);
156 m_shouldCollectPatternAttributes = false;
157 }
158
159 // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified,
160 // then the given effect (e.g. a gradient or a filter) will be ignored.
161 FloatRect objectBoundingBox = renderer.objectBoundingBox();
162 if (m_attributes.patternUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && objectBoundingBox.isEmpty())
163 return false;
164
165 PatternData* patternData = buildPattern(renderer, resourceMode, *context);
166 if (!patternData)
167 return false;
168
169 // Draw pattern
170 context->save();
171
172 const SVGRenderStyle& svgStyle = style.svgStyle();
173
174 if (resourceMode.contains(RenderSVGResourceMode::ApplyToFill)) {
175 context->setAlpha(svgStyle.fillOpacity());
176 context->setFillPattern(*patternData->pattern);
177 context->setFillRule(svgStyle.fillRule());
178 } else if (resourceMode.contains(RenderSVGResourceMode::ApplyToStroke)) {
179 if (svgStyle.vectorEffect() == VectorEffect::NonScalingStroke)
180 patternData->pattern->setPatternSpaceTransform(transformOnNonScalingStroke(&renderer, patternData->transform));
181 context->setAlpha(svgStyle.strokeOpacity());
182 context->setStrokePattern(*patternData->pattern);
183 SVGRenderSupport::applyStrokeStyleToContext(context, style, renderer);
184 }
185
186 if (resourceMode.contains(RenderSVGResourceMode::ApplyToText)) {
187 if (resourceMode.contains(RenderSVGResourceMode::ApplyToFill)) {
188 context->setTextDrawingMode(TextModeFill);
189
190#if USE(CG)
191 context->applyFillPattern();
192#endif
193 } else if (resourceMode.contains(RenderSVGResourceMode::ApplyToStroke)) {
194 context->setTextDrawingMode(TextModeStroke);
195
196#if USE(CG)
197 context->applyStrokePattern();
198#endif
199 }
200 }
201
202 return true;
203}
204
205void RenderSVGResourcePattern::postApplyResource(RenderElement&, GraphicsContext*& context, OptionSet<RenderSVGResourceMode> resourceMode, const Path* path, const RenderSVGShape* shape)
206{
207 ASSERT(context);
208 ASSERT(!resourceMode.isEmpty());
209
210 if (resourceMode.contains(RenderSVGResourceMode::ApplyToFill)) {
211 if (path)
212 context->fillPath(*path);
213 else if (shape)
214 shape->fillShape(*context);
215 }
216 if (resourceMode.contains(RenderSVGResourceMode::ApplyToStroke)) {
217 if (path)
218 context->strokePath(*path);
219 else if (shape)
220 shape->strokeShape(*context);
221 }
222
223 context->restore();
224}
225
226static inline FloatRect calculatePatternBoundaries(const PatternAttributes& attributes,
227 const FloatRect& objectBoundingBox,
228 const SVGPatternElement& patternElement)
229{
230 return SVGLengthContext::resolveRectangle(&patternElement, attributes.patternUnits(), objectBoundingBox, attributes.x(), attributes.y(), attributes.width(), attributes.height());
231}
232
233bool RenderSVGResourcePattern::buildTileImageTransform(RenderElement& renderer,
234 const PatternAttributes& attributes,
235 const SVGPatternElement& patternElement,
236 FloatRect& patternBoundaries,
237 AffineTransform& tileImageTransform) const
238{
239 FloatRect objectBoundingBox = renderer.objectBoundingBox();
240 patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement);
241 if (patternBoundaries.width() <= 0 || patternBoundaries.height() <= 0)
242 return false;
243
244 AffineTransform viewBoxCTM = SVGFitToViewBox::viewBoxToViewTransform(attributes.viewBox(), attributes.preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height());
245
246 // Apply viewBox/objectBoundingBox transformations.
247 if (!viewBoxCTM.isIdentity())
248 tileImageTransform = viewBoxCTM;
249 else if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
250 tileImageTransform.scale(objectBoundingBox.width(), objectBoundingBox.height());
251
252 return true;
253}
254
255std::unique_ptr<ImageBuffer> RenderSVGResourcePattern::createTileImage(const PatternAttributes& attributes, const FloatRect& tileBoundaries, const FloatRect& absoluteTileBoundaries, const AffineTransform& tileImageTransform, FloatRect& clampedAbsoluteTileBoundaries, RenderingMode renderingMode) const
256{
257 clampedAbsoluteTileBoundaries = ImageBuffer::clampedRect(absoluteTileBoundaries);
258 auto tileImage = SVGRenderingContext::createImageBuffer(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, ColorSpaceSRGB, renderingMode);
259 if (!tileImage)
260 return nullptr;
261
262 GraphicsContext& tileImageContext = tileImage->context();
263
264 // The image buffer represents the final rendered size, so the content has to be scaled (to avoid pixelation).
265 tileImageContext.scale(clampedAbsoluteTileBoundaries.size() / tileBoundaries.size());
266
267 // Apply tile image transformations.
268 if (!tileImageTransform.isIdentity())
269 tileImageContext.concatCTM(tileImageTransform);
270
271 AffineTransform contentTransformation;
272 if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
273 contentTransformation = tileImageTransform;
274
275 // Draw the content into the ImageBuffer.
276 for (auto& child : childrenOfType<SVGElement>(*attributes.patternContentElement())) {
277 if (!child.renderer())
278 continue;
279 if (child.renderer()->needsLayout())
280 return nullptr;
281 SVGRenderingContext::renderSubtreeToImageBuffer(tileImage.get(), *child.renderer(), contentTransformation);
282 }
283
284 return tileImage;
285}
286
287}
288