1 /*
2 Copyright (C) 2004, 2005, 2006, 2008 Nikolas Zimmermann <zimmermann@kde.org>
3 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
4 2008 Eric Seidel <eric@webkit.org>
5 2008 Dirk Schulze <krit@webkit.org>
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 #if ENABLE(SVG)
26 #include "SVGRadialGradientElement.h"
27
28 #include "FloatConversion.h"
29 #include "FloatPoint.h"
30 #include "MappedAttribute.h"
31 #include "RadialGradientAttributes.h"
32 #include "RenderObject.h"
33 #include "SVGLength.h"
34 #include "SVGNames.h"
35 #include "SVGPaintServerRadialGradient.h"
36 #include "SVGStopElement.h"
37 #include "SVGTransform.h"
38 #include "SVGTransformList.h"
39 #include "SVGUnitTypes.h"
40
41 namespace WebCore {
42
SVGRadialGradientElement(const QualifiedName & tagName,Document * doc)43 SVGRadialGradientElement::SVGRadialGradientElement(const QualifiedName& tagName, Document* doc)
44 : SVGGradientElement(tagName, doc)
45 , m_cx(this, SVGNames::cxAttr, LengthModeWidth, "50%")
46 , m_cy(this, SVGNames::cyAttr, LengthModeHeight, "50%")
47 , m_r(this, SVGNames::rAttr, LengthModeOther, "50%")
48 , m_fx(this, SVGNames::fxAttr, LengthModeWidth)
49 , m_fy(this, SVGNames::fyAttr, LengthModeHeight)
50 {
51 // Spec: If the cx/cy/r attribute is not specified, the effect is as if a value of "50%" were specified.
52 }
53
~SVGRadialGradientElement()54 SVGRadialGradientElement::~SVGRadialGradientElement()
55 {
56 }
57
parseMappedAttribute(MappedAttribute * attr)58 void SVGRadialGradientElement::parseMappedAttribute(MappedAttribute* attr)
59 {
60 if (attr->name() == SVGNames::cxAttr)
61 setCxBaseValue(SVGLength(LengthModeWidth, attr->value()));
62 else if (attr->name() == SVGNames::cyAttr)
63 setCyBaseValue(SVGLength(LengthModeHeight, attr->value()));
64 else if (attr->name() == SVGNames::rAttr) {
65 setRBaseValue(SVGLength(LengthModeOther, attr->value()));
66 if (rBaseValue().value(this) < 0.0)
67 document()->accessSVGExtensions()->reportError("A negative value for radial gradient radius <r> is not allowed");
68 } else if (attr->name() == SVGNames::fxAttr)
69 setFxBaseValue(SVGLength(LengthModeWidth, attr->value()));
70 else if (attr->name() == SVGNames::fyAttr)
71 setFyBaseValue(SVGLength(LengthModeHeight, attr->value()));
72 else
73 SVGGradientElement::parseMappedAttribute(attr);
74 }
75
svgAttributeChanged(const QualifiedName & attrName)76 void SVGRadialGradientElement::svgAttributeChanged(const QualifiedName& attrName)
77 {
78 SVGGradientElement::svgAttributeChanged(attrName);
79
80 if (!m_resource)
81 return;
82
83 if (attrName == SVGNames::cxAttr || attrName == SVGNames::cyAttr ||
84 attrName == SVGNames::fxAttr || attrName == SVGNames::fyAttr ||
85 attrName == SVGNames::rAttr)
86 m_resource->invalidate();
87 }
88
buildGradient() const89 void SVGRadialGradientElement::buildGradient() const
90 {
91 RadialGradientAttributes attributes = collectGradientProperties();
92
93 RefPtr<SVGPaintServerRadialGradient> radialGradient = WTF::static_pointer_cast<SVGPaintServerRadialGradient>(m_resource);
94
95 double adjustedFocusX = attributes.fx();
96 double adjustedFocusY = attributes.fy();
97
98 double fdx = attributes.fx() - attributes.cx();
99 double fdy = attributes.fy() - attributes.cy();
100
101 // Spec: If (fx, fy) lies outside the circle defined by (cx, cy) and
102 // r, set (fx, fy) to the point of intersection of the line through
103 // (fx, fy) and the circle.
104 if (sqrt(fdx * fdx + fdy * fdy) > attributes.r()) {
105 double angle = atan2(attributes.fy() * 100.0, attributes.fx() * 100.0);
106 adjustedFocusX = cos(angle) * attributes.r();
107 adjustedFocusY = sin(angle) * attributes.r();
108 }
109
110 FloatPoint focalPoint = FloatPoint::narrowPrecision(attributes.fx(), attributes.fy());
111 FloatPoint centerPoint = FloatPoint::narrowPrecision(attributes.cx(), attributes.cy());
112
113 RefPtr<Gradient> gradient = Gradient::create(
114 FloatPoint::narrowPrecision(adjustedFocusX, adjustedFocusY),
115 0.f, // SVG does not support a "focus radius"
116 centerPoint,
117 narrowPrecisionToFloat(attributes.r()));
118 gradient->setSpreadMethod(attributes.spreadMethod());
119
120 Vector<SVGGradientStop> stops = attributes.stops();
121 float previousOffset = 0.0f;
122 for (unsigned i = 0; i < stops.size(); ++i) {
123 float offset = std::min(std::max(previousOffset, stops[i].first), 1.0f);
124 previousOffset = offset;
125 gradient->addColorStop(offset, stops[i].second);
126 }
127
128 radialGradient->setGradient(gradient);
129
130 if (attributes.stops().isEmpty())
131 return;
132
133 radialGradient->setBoundingBoxMode(attributes.boundingBoxMode());
134 radialGradient->setGradientTransform(attributes.gradientTransform());
135 radialGradient->setGradientCenter(centerPoint);
136 radialGradient->setGradientFocal(focalPoint);
137 radialGradient->setGradientRadius(narrowPrecisionToFloat(attributes.r()));
138 radialGradient->setGradientStops(attributes.stops());
139 }
140
collectGradientProperties() const141 RadialGradientAttributes SVGRadialGradientElement::collectGradientProperties() const
142 {
143 RadialGradientAttributes attributes;
144 HashSet<const SVGGradientElement*> processedGradients;
145
146 bool isRadial = true;
147 const SVGGradientElement* current = this;
148
149 while (current) {
150 if (!attributes.hasSpreadMethod() && current->hasAttribute(SVGNames::spreadMethodAttr))
151 attributes.setSpreadMethod((GradientSpreadMethod) current->spreadMethod());
152
153 if (!attributes.hasBoundingBoxMode() && current->hasAttribute(SVGNames::gradientUnitsAttr))
154 attributes.setBoundingBoxMode(current->gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX);
155
156 if (!attributes.hasGradientTransform() && current->hasAttribute(SVGNames::gradientTransformAttr))
157 attributes.setGradientTransform(current->gradientTransform()->consolidate().matrix());
158
159 if (!attributes.hasStops()) {
160 const Vector<SVGGradientStop>& stops(current->buildStops());
161 if (!stops.isEmpty())
162 attributes.setStops(stops);
163 }
164
165 if (isRadial) {
166 const SVGRadialGradientElement* radial = static_cast<const SVGRadialGradientElement*>(current);
167
168 if (!attributes.hasCx() && current->hasAttribute(SVGNames::cxAttr))
169 attributes.setCx(radial->cx().valueAsPercentage());
170
171 if (!attributes.hasCy() && current->hasAttribute(SVGNames::cyAttr))
172 attributes.setCy(radial->cy().valueAsPercentage());
173
174 if (!attributes.hasR() && current->hasAttribute(SVGNames::rAttr))
175 attributes.setR(radial->r().valueAsPercentage());
176
177 if (!attributes.hasFx() && current->hasAttribute(SVGNames::fxAttr))
178 attributes.setFx(radial->fx().valueAsPercentage());
179
180 if (!attributes.hasFy() && current->hasAttribute(SVGNames::fyAttr))
181 attributes.setFy(radial->fy().valueAsPercentage());
182 }
183
184 processedGradients.add(current);
185
186 // Respect xlink:href, take attributes from referenced element
187 Node* refNode = ownerDocument()->getElementById(SVGURIReference::getTarget(current->href()));
188 if (refNode && (refNode->hasTagName(SVGNames::radialGradientTag) || refNode->hasTagName(SVGNames::linearGradientTag))) {
189 current = static_cast<const SVGGradientElement*>(const_cast<const Node*>(refNode));
190
191 // Cycle detection
192 if (processedGradients.contains(current))
193 return RadialGradientAttributes();
194
195 isRadial = current->gradientType() == RadialGradientPaintServer;
196 } else
197 current = 0;
198 }
199
200 // Handle default values for fx/fy
201 if (!attributes.hasFx())
202 attributes.setFx(attributes.cx());
203
204 if (!attributes.hasFy())
205 attributes.setFy(attributes.cy());
206
207 return attributes;
208 }
209 }
210
211 #endif // ENABLE(SVG)
212