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
31namespace WebCore {
32
33RenderSVGResourceType RenderSVGResourcePattern::s_resourceType = PatternResourceType;
34
35RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement& element, PassRef<RenderStyle> style)
36    : RenderSVGResourceContainer(element, WTF::move(style))
37    , m_shouldCollectPatternAttributes(true)
38{
39}
40
41SVGPatternElement& RenderSVGResourcePattern::patternElement() const
42{
43    return toSVGPatternElement(RenderSVGResourceContainer::element());
44}
45
46void RenderSVGResourcePattern::removeAllClientsFromCache(bool markForInvalidation)
47{
48    m_patternMap.clear();
49    m_shouldCollectPatternAttributes = true;
50    markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
51}
52
53void RenderSVGResourcePattern::removeClientFromCache(RenderElement& client, bool markForInvalidation)
54{
55    m_patternMap.remove(&client);
56    markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
57}
58
59PatternData* RenderSVGResourcePattern::buildPattern(RenderElement& renderer, unsigned short resourceMode)
60{
61    PatternData* currentData = m_patternMap.get(&renderer);
62    if (currentData && currentData->pattern)
63        return currentData;
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(renderer, m_attributes, patternElement(), tileBoundaries, tileImageTransform))
85        return 0;
86
87    AffineTransform absoluteTransformIgnoringRotation;
88    SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(renderer, 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    FloatRect clampedAbsoluteTileBoundaries;
94
95    // Scale the tile size to match the scale level of the patternTransform.
96    absoluteTileBoundaries.scale(static_cast<float>(m_attributes.patternTransform().xScale()),
97        static_cast<float>(m_attributes.patternTransform().yScale()));
98
99    // Build tile image.
100    std::unique_ptr<ImageBuffer> tileImage = createTileImage(m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform, clampedAbsoluteTileBoundaries);
101    if (!tileImage)
102        return 0;
103
104    RefPtr<Image> copiedImage = tileImage->copyImage(CopyBackingStore);
105    if (!copiedImage)
106        return 0;
107
108    // Build pattern.
109    auto patternData = std::make_unique<PatternData>();
110    patternData->pattern = Pattern::create(copiedImage, true, true);
111
112    // Compute pattern space transformation.
113    const IntSize tileImageSize = tileImage->logicalSize();
114    patternData->transform.translate(tileBoundaries.x(), tileBoundaries.y());
115    patternData->transform.scale(tileBoundaries.width() / tileImageSize.width(), tileBoundaries.height() / tileImageSize.height());
116
117    AffineTransform patternTransform = m_attributes.patternTransform();
118    if (!patternTransform.isIdentity())
119        patternData->transform = patternTransform * patternData->transform;
120
121    // Account for text drawing resetting the context to non-scaled, see SVGInlineTextBox::paintTextWithShadows.
122    if (resourceMode & ApplyToTextMode) {
123        AffineTransform additionalTextTransformation;
124        if (shouldTransformOnTextPainting(renderer, additionalTextTransformation))
125            patternData->transform *= additionalTextTransformation;
126    }
127    patternData->pattern->setPatternSpaceTransform(patternData->transform);
128
129    // Various calls above may trigger invalidations in some fringe cases (ImageBuffer allocation
130    // failures in the SVG image cache for example). To avoid having our PatternData deleted by
131    // removeAllClientsFromCache(), we only make it visible in the cache at the very end.
132    return m_patternMap.set(&renderer, WTF::move(patternData)).iterator->value.get();
133}
134
135bool RenderSVGResourcePattern::applyResource(RenderElement& renderer, const RenderStyle& style, GraphicsContext*& context, unsigned short resourceMode)
136{
137    ASSERT(context);
138    ASSERT(resourceMode != ApplyToDefaultMode);
139
140    // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified,
141    // then the given effect (e.g. a gradient or a filter) will be ignored.
142    FloatRect objectBoundingBox = renderer.objectBoundingBox();
143    if (m_attributes.patternUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && objectBoundingBox.isEmpty())
144        return false;
145
146    PatternData* patternData = buildPattern(renderer, resourceMode);
147    if (!patternData)
148        return false;
149
150    // Draw pattern
151    context->save();
152
153    const SVGRenderStyle& svgStyle = style.svgStyle();
154
155    if (resourceMode & ApplyToFillMode) {
156        context->setAlpha(svgStyle.fillOpacity());
157        context->setFillPattern(patternData->pattern);
158        context->setFillRule(svgStyle.fillRule());
159    } else if (resourceMode & ApplyToStrokeMode) {
160        if (svgStyle.vectorEffect() == VE_NON_SCALING_STROKE)
161            patternData->pattern->setPatternSpaceTransform(transformOnNonScalingStroke(&renderer, patternData->transform));
162        context->setAlpha(svgStyle.strokeOpacity());
163        context->setStrokePattern(patternData->pattern);
164        SVGRenderSupport::applyStrokeStyleToContext(context, style, renderer);
165    }
166
167    if (resourceMode & ApplyToTextMode) {
168        if (resourceMode & ApplyToFillMode) {
169            context->setTextDrawingMode(TextModeFill);
170
171#if USE(CG)
172            context->applyFillPattern();
173#endif
174        } else if (resourceMode & ApplyToStrokeMode) {
175            context->setTextDrawingMode(TextModeStroke);
176
177#if USE(CG)
178            context->applyStrokePattern();
179#endif
180        }
181    }
182
183    return true;
184}
185
186void RenderSVGResourcePattern::postApplyResource(RenderElement&, GraphicsContext*& context, unsigned short resourceMode, const Path* path, const RenderSVGShape* shape)
187{
188    ASSERT(context);
189    ASSERT(resourceMode != ApplyToDefaultMode);
190
191    if (resourceMode & ApplyToFillMode) {
192        if (path)
193            context->fillPath(*path);
194        else if (shape)
195            shape->fillShape(context);
196    }
197    if (resourceMode & ApplyToStrokeMode) {
198        if (path)
199            context->strokePath(*path);
200        else if (shape)
201            shape->strokeShape(context);
202    }
203
204    context->restore();
205}
206
207static inline FloatRect calculatePatternBoundaries(const PatternAttributes& attributes,
208                                                   const FloatRect& objectBoundingBox,
209                                                   const SVGPatternElement& patternElement)
210{
211    return SVGLengthContext::resolveRectangle(&patternElement, attributes.patternUnits(), objectBoundingBox, attributes.x(), attributes.y(), attributes.width(), attributes.height());
212}
213
214bool RenderSVGResourcePattern::buildTileImageTransform(RenderElement& renderer,
215                                                       const PatternAttributes& attributes,
216                                                       const SVGPatternElement& patternElement,
217                                                       FloatRect& patternBoundaries,
218                                                       AffineTransform& tileImageTransform) const
219{
220    FloatRect objectBoundingBox = renderer.objectBoundingBox();
221    patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement);
222    if (patternBoundaries.width() <= 0 || patternBoundaries.height() <= 0)
223        return false;
224
225    AffineTransform viewBoxCTM = SVGFitToViewBox::viewBoxToViewTransform(attributes.viewBox(), attributes.preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height());
226
227    // Apply viewBox/objectBoundingBox transformations.
228    if (!viewBoxCTM.isIdentity())
229        tileImageTransform = viewBoxCTM;
230    else if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
231        tileImageTransform.scale(objectBoundingBox.width(), objectBoundingBox.height());
232
233    return true;
234}
235
236std::unique_ptr<ImageBuffer> RenderSVGResourcePattern::createTileImage(const PatternAttributes& attributes, const FloatRect& tileBoundaries, const FloatRect& absoluteTileBoundaries, const AffineTransform& tileImageTransform, FloatRect& clampedAbsoluteTileBoundaries) const
237{
238    clampedAbsoluteTileBoundaries = SVGRenderingContext::clampedAbsoluteTargetRect(absoluteTileBoundaries);
239
240    std::unique_ptr<ImageBuffer> tileImage;
241
242    if (!SVGRenderingContext::createImageBufferForPattern(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, tileImage, ColorSpaceDeviceRGB, Unaccelerated))
243        return nullptr;
244
245    GraphicsContext* tileImageContext = tileImage->context();
246    ASSERT(tileImageContext);
247
248    // The image buffer represents the final rendered size, so the content has to be scaled (to avoid pixelation).
249    tileImageContext->scale(FloatSize(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 (auto& child : childrenOfType<SVGElement>(*attributes.patternContentElement())) {
262        if (!child.renderer())
263            continue;
264        if (child.renderer()->needsLayout())
265            return nullptr;
266        SVGRenderingContext::renderSubtreeToImageBuffer(tileImage.get(), *child.renderer(), contentTransformation);
267    }
268
269    return tileImage;
270}
271
272}
273