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 | |
32 | namespace WebCore { |
33 | |
34 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGResourceGradient); |
35 | |
36 | RenderSVGResourceGradient::RenderSVGResourceGradient(SVGGradientElement& node, RenderStyle&& style) |
37 | : RenderSVGResourceContainer(node, WTFMove(style)) |
38 | { |
39 | } |
40 | |
41 | void RenderSVGResourceGradient::removeAllClientsFromCache(bool markForInvalidation) |
42 | { |
43 | m_gradientMap.clear(); |
44 | m_shouldCollectGradientAttributes = true; |
45 | markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); |
46 | } |
47 | |
48 | void RenderSVGResourceGradient::removeClientFromCache(RenderElement& client, bool markForInvalidation) |
49 | { |
50 | m_gradientMap.remove(&client); |
51 | markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); |
52 | } |
53 | |
54 | #if USE(CG) |
55 | static 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 | |
75 | static 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 | |
97 | bool 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 | |
190 | void 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 | |
235 | void 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 | |
245 | GradientSpreadMethod 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 | |