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#include "RenderSVGResourceGradient.h"
25
26#include "GradientAttributes.h"
27#include "GraphicsContext.h"
28#include "RenderSVGText.h"
29#include "SVGRenderingContext.h"
30#include <wtf/IsoMallocInlines.h>
31
32namespace WebCore {
33
34WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGResourceGradient);
35
36RenderSVGResourceGradient::RenderSVGResourceGradient(SVGGradientElement& node, RenderStyle&& style)
37 : RenderSVGResourceContainer(node, WTFMove(style))
38{
39}
40
41void RenderSVGResourceGradient::removeAllClientsFromCache(bool markForInvalidation)
42{
43 m_gradientMap.clear();
44 m_shouldCollectGradientAttributes = true;
45 markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
46}
47
48void RenderSVGResourceGradient::removeClientFromCache(RenderElement& client, bool markForInvalidation)
49{
50 m_gradientMap.remove(&client);
51 markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
52}
53
54#if USE(CG)
55static inline bool createMaskAndSwapContextForTextGradient(GraphicsContext*& context, GraphicsContext*& savedContext, std::unique_ptr<ImageBuffer>& imageBuffer, RenderObject* object)
56{
57 auto* textRootBlock = RenderSVGText::locateRenderSVGTextAncestor(*object);
58 ASSERT(textRootBlock);
59
60 AffineTransform absoluteTransform = SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(*textRootBlock);
61 FloatRect repaintRect = textRootBlock->repaintRectInLocalCoordinates();
62
63 auto maskImage = SVGRenderingContext::createImageBuffer(repaintRect, absoluteTransform, ColorSpaceSRGB, context->renderingMode());
64 if (!maskImage)
65 return false;
66
67 GraphicsContext& maskImageContext = maskImage->context();
68 ASSERT(maskImage);
69 savedContext = context;
70 context = &maskImageContext;
71 imageBuffer = WTFMove(maskImage);
72 return true;
73}
74
75static inline AffineTransform clipToTextMask(GraphicsContext& context, std::unique_ptr<ImageBuffer>& imageBuffer, FloatRect& targetRect, RenderObject* object, bool boundingBoxMode, const AffineTransform& gradientTransform)
76{
77 auto* textRootBlock = RenderSVGText::locateRenderSVGTextAncestor(*object);
78 ASSERT(textRootBlock);
79
80 AffineTransform absoluteTransform = SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(*textRootBlock);
81
82 targetRect = textRootBlock->repaintRectInLocalCoordinates();
83
84 SVGRenderingContext::clipToImageBuffer(context, absoluteTransform, targetRect, imageBuffer, false);
85
86 AffineTransform matrix;
87 if (boundingBoxMode) {
88 FloatRect maskBoundingBox = textRootBlock->objectBoundingBox();
89 matrix.translate(maskBoundingBox.location());
90 matrix.scale(maskBoundingBox.size());
91 }
92 matrix *= gradientTransform;
93 return matrix;
94}
95#endif
96
97bool RenderSVGResourceGradient::applyResource(RenderElement& renderer, const RenderStyle& style, GraphicsContext*& context, OptionSet<RenderSVGResourceMode> resourceMode)
98{
99 ASSERT(context);
100 ASSERT(!resourceMode.isEmpty());
101
102 // Be sure to synchronize all SVG properties on the gradientElement _before_ processing any further.
103 // Otherwhise the call to collectGradientAttributes() in createTileImage(), may cause the SVG DOM property
104 // synchronization to kick in, which causes removeAllClientsFromCache() to be called, which in turn deletes our
105 // GradientData object! Leaving out the line below will cause svg/dynamic-updates/SVG*GradientElement-svgdom* to crash.
106 if (m_shouldCollectGradientAttributes) {
107 gradientElement().synchronizeAllAttributes();
108 if (!collectGradientAttributes())
109 return false;
110
111 m_shouldCollectGradientAttributes = false;
112 }
113
114 // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified,
115 // then the given effect (e.g. a gradient or a filter) will be ignored.
116 FloatRect objectBoundingBox = renderer.objectBoundingBox();
117 if (gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && objectBoundingBox.isEmpty())
118 return false;
119
120 auto& gradientData = m_gradientMap.add(&renderer, nullptr).iterator->value;
121 if (!gradientData)
122 gradientData = std::make_unique<GradientData>();
123
124 bool isPaintingText = resourceMode.contains(RenderSVGResourceMode::ApplyToText);
125
126 // Create gradient object
127 if (!gradientData->gradient) {
128 buildGradient(gradientData.get(), style);
129
130 // CG platforms will handle the gradient space transform for text after applying the
131 // resource, so don't apply it here. For non-CG platforms, we want the text bounding
132 // box applied to the gradient space transform now, so the gradient shader can use it.
133#if USE(CG)
134 if (gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && !objectBoundingBox.isEmpty() && !isPaintingText) {
135#else
136 if (gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && !objectBoundingBox.isEmpty()) {
137#endif
138 gradientData->userspaceTransform.translate(objectBoundingBox.location());
139 gradientData->userspaceTransform.scale(objectBoundingBox.size());
140 }
141
142 AffineTransform gradientTransform;
143 calculateGradientTransform(gradientTransform);
144
145 gradientData->userspaceTransform *= gradientTransform;
146 if (isPaintingText) {
147 // Depending on font scaling factor, we may need to rescale the gradient here since
148 // text painting removes the scale factor from the context.
149 AffineTransform additionalTextTransform;
150 if (shouldTransformOnTextPainting(renderer, additionalTextTransform))
151 gradientData->userspaceTransform *= additionalTextTransform;
152 }
153 gradientData->gradient->setGradientSpaceTransform(gradientData->userspaceTransform);
154 }
155
156 if (!gradientData->gradient)
157 return false;
158
159 // Draw gradient
160 context->save();
161
162 if (isPaintingText) {
163#if USE(CG)
164 if (!createMaskAndSwapContextForTextGradient(context, m_savedContext, m_imageBuffer, &renderer)) {
165 context->restore();
166 return false;
167 }
168#endif
169
170 context->setTextDrawingMode(resourceMode.contains(RenderSVGResourceMode::ApplyToFill) ? TextModeFill : TextModeStroke);
171 }
172
173 const SVGRenderStyle& svgStyle = style.svgStyle();
174
175 if (resourceMode.contains(RenderSVGResourceMode::ApplyToFill)) {
176 context->setAlpha(svgStyle.fillOpacity());
177 context->setFillGradient(*gradientData->gradient);
178 context->setFillRule(svgStyle.fillRule());
179 } else if (resourceMode.contains(RenderSVGResourceMode::ApplyToStroke)) {
180 if (svgStyle.vectorEffect() == VectorEffect::NonScalingStroke)
181 gradientData->gradient->setGradientSpaceTransform(transformOnNonScalingStroke(&renderer, gradientData->userspaceTransform));
182 context->setAlpha(svgStyle.strokeOpacity());
183 context->setStrokeGradient(*gradientData->gradient);
184 SVGRenderSupport::applyStrokeStyleToContext(context, style, renderer);
185 }
186
187 return true;
188}
189
190void RenderSVGResourceGradient::postApplyResource(RenderElement& renderer, GraphicsContext*& context, OptionSet<RenderSVGResourceMode> resourceMode, const Path* path, const RenderSVGShape* shape)
191{
192 ASSERT(context);
193 ASSERT(!resourceMode.isEmpty());
194
195 if (resourceMode.contains(RenderSVGResourceMode::ApplyToText)) {
196#if USE(CG)
197 // CG requires special handling for gradient on text
198 GradientData* gradientData;
199 if (m_savedContext && (gradientData = m_gradientMap.get(&renderer))) {
200 // Restore on-screen drawing context
201 context = m_savedContext;
202 m_savedContext = nullptr;
203
204 AffineTransform gradientTransform;
205 calculateGradientTransform(gradientTransform);
206
207 FloatRect targetRect;
208 gradientData->gradient->setGradientSpaceTransform(clipToTextMask(*context, m_imageBuffer, targetRect, &renderer, gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, gradientTransform));
209 context->setFillGradient(*gradientData->gradient);
210
211 context->fillRect(targetRect);
212 m_imageBuffer.reset();
213 }
214#else
215 UNUSED_PARAM(renderer);
216#endif
217 } else {
218 if (resourceMode.contains(RenderSVGResourceMode::ApplyToFill)) {
219 if (path)
220 context->fillPath(*path);
221 else if (shape)
222 shape->fillShape(*context);
223 }
224 if (resourceMode.contains(RenderSVGResourceMode::ApplyToStroke)) {
225 if (path)
226 context->strokePath(*path);
227 else if (shape)
228 shape->strokeShape(*context);
229 }
230 }
231
232 context->restore();
233}
234
235void RenderSVGResourceGradient::addStops(GradientData* gradientData, const Vector<Gradient::ColorStop>& stops, const RenderStyle& style) const
236{
237 ASSERT(gradientData->gradient);
238
239 for (Gradient::ColorStop stop : stops) {
240 stop.color = style.colorByApplyingColorFilter(stop.color);
241 gradientData->gradient->addColorStop(stop);
242 }
243}
244
245GradientSpreadMethod RenderSVGResourceGradient::platformSpreadMethodFromSVGType(SVGSpreadMethodType method) const
246{
247 switch (method) {
248 case SVGSpreadMethodUnknown:
249 case SVGSpreadMethodPad:
250 return SpreadMethodPad;
251 case SVGSpreadMethodReflect:
252 return SpreadMethodReflect;
253 case SVGSpreadMethodRepeat:
254 return SpreadMethodRepeat;
255 }
256
257 ASSERT_NOT_REACHED();
258 return SpreadMethodPad;
259}
260
261}
262