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
25#include "core/rendering/svg/RenderSVGResourceGradient.h"
26
27#include "core/rendering/svg/RenderSVGShape.h"
28#include "core/rendering/svg/SVGRenderSupport.h"
29#include "platform/graphics/GraphicsContext.h"
30
31namespace WebCore {
32
33RenderSVGResourceGradient::RenderSVGResourceGradient(SVGGradientElement* node)
34    : RenderSVGResourceContainer(node)
35    , m_shouldCollectGradientAttributes(true)
36{
37}
38
39void RenderSVGResourceGradient::removeAllClientsFromCache(bool markForInvalidation)
40{
41    m_gradientMap.clear();
42    m_shouldCollectGradientAttributes = true;
43    markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
44}
45
46void RenderSVGResourceGradient::removeClientFromCache(RenderObject* client, bool markForInvalidation)
47{
48    ASSERT(client);
49    m_gradientMap.remove(client);
50    markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
51}
52
53bool RenderSVGResourceGradient::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode)
54{
55    ASSERT(object);
56    ASSERT(style);
57    ASSERT(context);
58    ASSERT(resourceMode != ApplyToDefaultMode);
59
60    clearInvalidationMask();
61
62    // Be sure to synchronize all SVG properties on the gradientElement _before_ processing any further.
63    // Otherwhise the call to collectGradientAttributes() in createTileImage(), may cause the SVG DOM property
64    // synchronization to kick in, which causes removeAllClientsFromCache() to be called, which in turn deletes our
65    // GradientData object! Leaving out the line below will cause svg/dynamic-updates/SVG*GradientElement-svgdom* to crash.
66    SVGGradientElement* gradientElement = toSVGGradientElement(element());
67    if (!gradientElement)
68        return false;
69
70    if (m_shouldCollectGradientAttributes) {
71        gradientElement->synchronizeAnimatedSVGAttribute(anyQName());
72        if (!collectGradientAttributes(gradientElement))
73            return false;
74
75        m_shouldCollectGradientAttributes = false;
76    }
77
78    // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified,
79    // then the given effect (e.g. a gradient or a filter) will be ignored.
80    FloatRect objectBoundingBox = object->objectBoundingBox();
81    if (gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && objectBoundingBox.isEmpty())
82        return false;
83
84    OwnPtr<GradientData>& gradientData = m_gradientMap.add(object, nullptr).iterator->value;
85    if (!gradientData)
86        gradientData = adoptPtr(new GradientData);
87
88    bool isPaintingText = resourceMode & ApplyToTextMode;
89
90    // Create gradient object
91    if (!gradientData->gradient) {
92        buildGradient(gradientData.get());
93
94        // We want the text bounding box applied to the gradient space transform now, so the gradient shader can use it.
95        if (gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && !objectBoundingBox.isEmpty()) {
96            gradientData->userspaceTransform.translate(objectBoundingBox.x(), objectBoundingBox.y());
97            gradientData->userspaceTransform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
98        }
99
100        AffineTransform gradientTransform;
101        calculateGradientTransform(gradientTransform);
102
103        gradientData->userspaceTransform *= gradientTransform;
104        if (isPaintingText) {
105            // Depending on font scaling factor, we may need to rescale the gradient here since
106            // text painting removes the scale factor from the context.
107            AffineTransform additionalTextTransform;
108            if (shouldTransformOnTextPainting(object, additionalTextTransform))
109                gradientData->userspaceTransform *= additionalTextTransform;
110        }
111        gradientData->gradient->setGradientSpaceTransform(gradientData->userspaceTransform);
112    }
113
114    if (!gradientData->gradient)
115        return false;
116
117    // Draw gradient
118    context->save();
119
120    if (isPaintingText)
121        context->setTextDrawingMode(resourceMode & ApplyToFillMode ? TextModeFill : TextModeStroke);
122
123    const SVGRenderStyle* svgStyle = style->svgStyle();
124    ASSERT(svgStyle);
125
126    if (resourceMode & ApplyToFillMode) {
127        context->setAlpha(svgStyle->fillOpacity());
128        context->setFillGradient(gradientData->gradient);
129        context->setFillRule(svgStyle->fillRule());
130    } else if (resourceMode & ApplyToStrokeMode) {
131        if (svgStyle->vectorEffect() == VE_NON_SCALING_STROKE)
132            gradientData->gradient->setGradientSpaceTransform(transformOnNonScalingStroke(object, gradientData->userspaceTransform));
133        context->setAlpha(svgStyle->strokeOpacity());
134        context->setStrokeGradient(gradientData->gradient);
135        SVGRenderSupport::applyStrokeStyleToContext(context, style, object);
136    }
137
138    return true;
139}
140
141void RenderSVGResourceGradient::postApplyResource(RenderObject*, GraphicsContext*& context, unsigned short resourceMode, const Path* path, const RenderSVGShape* shape)
142{
143    ASSERT(context);
144    ASSERT(resourceMode != ApplyToDefaultMode);
145
146    if (resourceMode & ApplyToFillMode) {
147        if (path)
148            context->fillPath(*path);
149        else if (shape)
150            shape->fillShape(context);
151    }
152    if (resourceMode & ApplyToStrokeMode) {
153        if (path)
154            context->strokePath(*path);
155        else if (shape)
156            shape->strokeShape(context);
157    }
158
159    context->restore();
160}
161
162void RenderSVGResourceGradient::addStops(GradientData* gradientData, const Vector<Gradient::ColorStop>& stops) const
163{
164    ASSERT(gradientData->gradient);
165
166    const Vector<Gradient::ColorStop>::const_iterator end = stops.end();
167    for (Vector<Gradient::ColorStop>::const_iterator it = stops.begin(); it != end; ++it)
168        gradientData->gradient->addColorStop(*it);
169}
170
171GradientSpreadMethod RenderSVGResourceGradient::platformSpreadMethodFromSVGType(SVGSpreadMethodType method) const
172{
173    switch (method) {
174    case SVGSpreadMethodUnknown:
175    case SVGSpreadMethodPad:
176        return SpreadMethodPad;
177    case SVGSpreadMethodReflect:
178        return SpreadMethodReflect;
179    case SVGSpreadMethodRepeat:
180        return SpreadMethodRepeat;
181    }
182
183    ASSERT_NOT_REACHED();
184    return SpreadMethodPad;
185}
186
187}
188