• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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