1 /*
2     Copyright (C) 2004, 2005, 2006, 2008 Nikolas Zimmermann <zimmermann@kde.org>
3                   2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
4 
5     This file is part of the KDE project
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 "wtf/Platform.h"
24 
25 #if ENABLE(SVG)
26 #include "SVGRadialGradientElement.h"
27 
28 #include "FloatConversion.h"
29 #include "FloatPoint.h"
30 #include "RadialGradientAttributes.h"
31 #include "RenderObject.h"
32 #include "SVGLength.h"
33 #include "SVGNames.h"
34 #include "SVGPaintServerRadialGradient.h"
35 #include "SVGStopElement.h"
36 #include "SVGTransform.h"
37 #include "SVGTransformList.h"
38 #include "SVGUnitTypes.h"
39 
40 namespace WebCore
41 {
42 
SVGRadialGradientElement(const QualifiedName & tagName,Document * doc)43 SVGRadialGradientElement::SVGRadialGradientElement(const QualifiedName &tagName, Document *doc)
44     : SVGGradientElement(tagName, doc)
45     , m_cx(this, LengthModeWidth)
46     , m_cy(this, LengthModeHeight)
47     , m_r(this, LengthModeOther)
48     , m_fx(this, LengthModeWidth)
49     , m_fy(this, LengthModeHeight)
50 {
51     // Spec: If the attribute is not specified, the effect is as if a value of "50%" were specified.
52     setCxBaseValue(SVGLength(this, LengthModeWidth, "50%"));
53     setCyBaseValue(SVGLength(this, LengthModeHeight, "50%"));
54     setRBaseValue(SVGLength(this, LengthModeOther, "50%"));
55 }
56 
~SVGRadialGradientElement()57 SVGRadialGradientElement::~SVGRadialGradientElement()
58 {
59 }
60 
ANIMATED_PROPERTY_DEFINITIONS(SVGRadialGradientElement,SVGLength,Length,length,Cx,cx,SVGNames::cxAttr,m_cx)61 ANIMATED_PROPERTY_DEFINITIONS(SVGRadialGradientElement, SVGLength, Length, length, Cx, cx, SVGNames::cxAttr, m_cx)
62 ANIMATED_PROPERTY_DEFINITIONS(SVGRadialGradientElement, SVGLength, Length, length, Cy, cy, SVGNames::cyAttr, m_cy)
63 ANIMATED_PROPERTY_DEFINITIONS(SVGRadialGradientElement, SVGLength, Length, length, Fx, fx, SVGNames::fxAttr, m_fx)
64 ANIMATED_PROPERTY_DEFINITIONS(SVGRadialGradientElement, SVGLength, Length, length, Fy, fy, SVGNames::fyAttr, m_fy)
65 ANIMATED_PROPERTY_DEFINITIONS(SVGRadialGradientElement, SVGLength, Length, length, R, r, SVGNames::rAttr, m_r)
66 
67 void SVGRadialGradientElement::parseMappedAttribute(MappedAttribute *attr)
68 {
69     if (attr->name() == SVGNames::cxAttr) {
70         setCxBaseValue(SVGLength(this, LengthModeWidth, attr->value()));
71     } else if (attr->name() == SVGNames::cyAttr) {
72         setCyBaseValue(SVGLength(this, LengthModeHeight, attr->value()));
73     } else if (attr->name() == SVGNames::rAttr) {
74         setRBaseValue(SVGLength(this, LengthModeOther, attr->value()));
75         if (r().value() < 0.0) {
76             document()->accessSVGExtensions()->reportError("A negative value for radial gradient radius <r> is not allowed");
77         }
78     } else if (attr->name() == SVGNames::fxAttr) {
79         setFxBaseValue(SVGLength(this, LengthModeWidth, attr->value()));
80     } else if (attr->name() == SVGNames::fyAttr) {
81         setFyBaseValue(SVGLength(this, LengthModeHeight, attr->value()));
82     } else {
83         SVGGradientElement::parseMappedAttribute(attr);
84     }
85 }
86 
svgAttributeChanged(const QualifiedName & attrName)87 void SVGRadialGradientElement::svgAttributeChanged(const QualifiedName &attrName)
88 {
89     SVGGradientElement::svgAttributeChanged(attrName);
90 
91     if (!m_resource) {
92         return;
93     }
94 
95     if (attrName == SVGNames::cxAttr || attrName == SVGNames::cyAttr ||
96             attrName == SVGNames::fxAttr || attrName == SVGNames::fyAttr ||
97             attrName == SVGNames::rAttr) {
98         m_resource->invalidate();
99     }
100 }
101 
buildGradient() const102 void SVGRadialGradientElement::buildGradient() const
103 {
104     RadialGradientAttributes attributes = collectGradientProperties();
105 
106     // If we didn't find any gradient containing stop elements, ignore the request.
107     if (attributes.stops().isEmpty()) {
108         return;
109     }
110 
111     RefPtr<SVGPaintServerRadialGradient> radialGradient = WTF::static_pointer_cast<SVGPaintServerRadialGradient>(m_resource);
112 
113     radialGradient->setGradientStops(attributes.stops());
114     radialGradient->setBoundingBoxMode(attributes.boundingBoxMode());
115     radialGradient->setGradientSpreadMethod(attributes.spreadMethod());
116     radialGradient->setGradientTransform(attributes.gradientTransform());
117     radialGradient->setGradientCenter(FloatPoint::narrowPrecision(attributes.cx(), attributes.cy()));
118     radialGradient->setGradientFocal(FloatPoint::narrowPrecision(attributes.fx(), attributes.fy()));
119     radialGradient->setGradientRadius(narrowPrecisionToFloat(attributes.r()));
120 }
121 
collectGradientProperties() const122 RadialGradientAttributes SVGRadialGradientElement::collectGradientProperties() const
123 {
124     RadialGradientAttributes attributes;
125     HashSet<const SVGGradientElement *> processedGradients;
126 
127     bool isRadial = true;
128     const SVGGradientElement *current = this;
129 
130     while (current) {
131         if (!attributes.hasSpreadMethod() && current->hasAttribute(SVGNames::spreadMethodAttr)) {
132             attributes.setSpreadMethod((SVGGradientSpreadMethod) current->spreadMethod());
133         }
134 
135         if (!attributes.hasBoundingBoxMode() && current->hasAttribute(SVGNames::gradientUnitsAttr)) {
136             attributes.setBoundingBoxMode(current->getAttribute(SVGNames::gradientUnitsAttr) == "objectBoundingBox");
137         }
138 
139         if (!attributes.hasGradientTransform() && current->hasAttribute(SVGNames::gradientTransformAttr)) {
140             attributes.setGradientTransform(current->gradientTransform()->consolidate().matrix());
141         }
142 
143         if (!attributes.hasStops()) {
144             const Vector<SVGGradientStop> &stops(current->buildStops());
145             if (!stops.isEmpty()) {
146                 attributes.setStops(stops);
147             }
148         }
149 
150         if (isRadial) {
151             const SVGRadialGradientElement *radial = static_cast<const SVGRadialGradientElement *>(current);
152 
153             if (!attributes.hasCx() && current->hasAttribute(SVGNames::cxAttr)) {
154                 attributes.setCx(radial->cx().valueAsPercentage());
155             }
156 
157             if (!attributes.hasCy() && current->hasAttribute(SVGNames::cyAttr)) {
158                 attributes.setCy(radial->cy().valueAsPercentage());
159             }
160 
161             if (!attributes.hasR() && current->hasAttribute(SVGNames::rAttr)) {
162                 attributes.setR(radial->r().valueAsPercentage());
163             }
164 
165             if (!attributes.hasFx() && current->hasAttribute(SVGNames::fxAttr)) {
166                 attributes.setFx(radial->fx().valueAsPercentage());
167             }
168 
169             if (!attributes.hasFy() && current->hasAttribute(SVGNames::fyAttr)) {
170                 attributes.setFy(radial->fy().valueAsPercentage());
171             }
172         }
173 
174         processedGradients.add(current);
175 
176         // Respect xlink:href, take attributes from referenced element
177         Node *refNode = ownerDocument()->getElementById(SVGURIReference::getTarget(current->href()));
178         if (refNode && (refNode->hasTagName(SVGNames::radialGradientTag) || refNode->hasTagName(SVGNames::linearGradientTag))) {
179             current = static_cast<const SVGGradientElement *>(const_cast<const Node *>(refNode));
180 
181             // Cycle detection
182             if (processedGradients.contains(current)) {
183                 return RadialGradientAttributes();
184             }
185 
186             isRadial = current->gradientType() == RadialGradientPaintServer;
187         } else {
188             current = nullptr;
189         }
190     }
191 
192     // Handle default values for fx/fy
193     if (!attributes.hasFx()) {
194         attributes.setFx(attributes.cx());
195     }
196 
197     if (!attributes.hasFy()) {
198         attributes.setFy(attributes.cy());
199     }
200 
201     return attributes;
202 }
203 
204 // KHTML ElementImpl pure virtual method
id() const205 quint32 SVGRadialGradientElement::id() const
206 {
207     return SVGNames::radialGradientTag.id();
208 }
209 
210 }
211 
212 #endif // ENABLE(SVG)
213