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 41namespace WebCore { 42 43SVGRadialGradientElement::SVGRadialGradientElement(const QualifiedName& tagName, Document* doc) 44 : SVGGradientElement(tagName, doc) 45 , m_cx(LengthModeWidth, "50%") 46 , m_cy(LengthModeHeight, "50%") 47 , m_r(LengthModeOther, "50%") 48 , m_fx(LengthModeWidth) 49 , m_fy(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 54SVGRadialGradientElement::~SVGRadialGradientElement() 55{ 56} 57 58void 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 76void 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 89void SVGRadialGradientElement::synchronizeProperty(const QualifiedName& attrName) 90{ 91 SVGGradientElement::synchronizeProperty(attrName); 92 93 if (attrName == anyQName()) { 94 synchronizeCx(); 95 synchronizeCy(); 96 synchronizeFx(); 97 synchronizeFy(); 98 synchronizeR(); 99 return; 100 } 101 102 if (attrName == SVGNames::cxAttr) 103 synchronizeCx(); 104 else if (attrName == SVGNames::cyAttr) 105 synchronizeCy(); 106 else if (attrName == SVGNames::fxAttr) 107 synchronizeFx(); 108 else if (attrName == SVGNames::fyAttr) 109 synchronizeFy(); 110 else if (attrName == SVGNames::rAttr) 111 synchronizeR(); 112} 113 114void SVGRadialGradientElement::buildGradient() const 115{ 116 RadialGradientAttributes attributes = collectGradientProperties(); 117 118 RefPtr<SVGPaintServerRadialGradient> radialGradient = WTF::static_pointer_cast<SVGPaintServerRadialGradient>(m_resource); 119 120 FloatPoint focalPoint; 121 FloatPoint centerPoint; 122 float radius; 123 if (attributes.boundingBoxMode()) { 124 focalPoint = FloatPoint(attributes.fx().valueAsPercentage(), attributes.fy().valueAsPercentage()); 125 centerPoint = FloatPoint(attributes.cx().valueAsPercentage(), attributes.cy().valueAsPercentage()); 126 radius = attributes.r().valueAsPercentage(); 127 } else { 128 focalPoint = FloatPoint(attributes.fx().value(this), attributes.fy().value(this)); 129 centerPoint = FloatPoint(attributes.cx().value(this), attributes.cy().value(this)); 130 radius = attributes.r().value(this); 131 } 132 133 FloatPoint adjustedFocalPoint = focalPoint; 134 float dfx = focalPoint.x() - centerPoint.x(); 135 float dfy = focalPoint.y() - centerPoint.y(); 136 float rMax = 0.99f * radius; 137 138 // Spec: If (fx, fy) lies outside the circle defined by (cx, cy) and 139 // r, set (fx, fy) to the point of intersection of the line through 140 // (fx, fy) and the circle. 141 // We scale the radius by 0.99 to match the behavior of FireFox. 142 if (sqrt(dfx * dfx + dfy * dfy) > rMax) { 143 float angle = atan2f(dfy, dfx); 144 145 dfx = cosf(angle) * rMax; 146 dfy = sinf(angle) * rMax; 147 adjustedFocalPoint = FloatPoint(dfx + centerPoint.x(), dfy + centerPoint.y()); 148 } 149 150 RefPtr<Gradient> gradient = Gradient::create( 151 adjustedFocalPoint, 152 0.f, // SVG does not support a "focus radius" 153 centerPoint, 154 radius); 155 gradient->setSpreadMethod(attributes.spreadMethod()); 156 157 Vector<SVGGradientStop> stops = attributes.stops(); 158 float previousOffset = 0.0f; 159 for (unsigned i = 0; i < stops.size(); ++i) { 160 float offset = std::min(std::max(previousOffset, stops[i].first), 1.0f); 161 previousOffset = offset; 162 gradient->addColorStop(offset, stops[i].second); 163 } 164 165 radialGradient->setGradient(gradient); 166 167 if (attributes.stops().isEmpty()) 168 return; 169 170 radialGradient->setBoundingBoxMode(attributes.boundingBoxMode()); 171 radialGradient->setGradientTransform(attributes.gradientTransform()); 172 radialGradient->setGradientCenter(centerPoint); 173 radialGradient->setGradientFocal(focalPoint); 174 radialGradient->setGradientRadius(radius); 175 radialGradient->setGradientStops(attributes.stops()); 176} 177 178RadialGradientAttributes SVGRadialGradientElement::collectGradientProperties() const 179{ 180 RadialGradientAttributes attributes; 181 HashSet<const SVGGradientElement*> processedGradients; 182 183 bool isRadial = true; 184 const SVGGradientElement* current = this; 185 186 while (current) { 187 if (!attributes.hasSpreadMethod() && current->hasAttribute(SVGNames::spreadMethodAttr)) 188 attributes.setSpreadMethod((GradientSpreadMethod) current->spreadMethod()); 189 190 if (!attributes.hasBoundingBoxMode() && current->hasAttribute(SVGNames::gradientUnitsAttr)) 191 attributes.setBoundingBoxMode(current->gradientUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX); 192 193 if (!attributes.hasGradientTransform() && current->hasAttribute(SVGNames::gradientTransformAttr)) 194 attributes.setGradientTransform(current->gradientTransform()->consolidate().matrix()); 195 196 if (!attributes.hasStops()) { 197 const Vector<SVGGradientStop>& stops(current->buildStops()); 198 if (!stops.isEmpty()) 199 attributes.setStops(stops); 200 } 201 202 if (isRadial) { 203 const SVGRadialGradientElement* radial = static_cast<const SVGRadialGradientElement*>(current); 204 205 if (!attributes.hasCx() && current->hasAttribute(SVGNames::cxAttr)) 206 attributes.setCx(radial->cx()); 207 208 if (!attributes.hasCy() && current->hasAttribute(SVGNames::cyAttr)) 209 attributes.setCy(radial->cy()); 210 211 if (!attributes.hasR() && current->hasAttribute(SVGNames::rAttr)) 212 attributes.setR(radial->r()); 213 214 if (!attributes.hasFx() && current->hasAttribute(SVGNames::fxAttr)) 215 attributes.setFx(radial->fx()); 216 217 if (!attributes.hasFy() && current->hasAttribute(SVGNames::fyAttr)) 218 attributes.setFy(radial->fy()); 219 } 220 221 processedGradients.add(current); 222 223 // Respect xlink:href, take attributes from referenced element 224 Node* refNode = ownerDocument()->getElementById(SVGURIReference::getTarget(current->href())); 225 if (refNode && (refNode->hasTagName(SVGNames::radialGradientTag) || refNode->hasTagName(SVGNames::linearGradientTag))) { 226 current = static_cast<const SVGGradientElement*>(const_cast<const Node*>(refNode)); 227 228 // Cycle detection 229 if (processedGradients.contains(current)) 230 return RadialGradientAttributes(); 231 232 isRadial = current->gradientType() == RadialGradientPaintServer; 233 } else 234 current = 0; 235 } 236 237 // Handle default values for fx/fy 238 if (!attributes.hasFx()) 239 attributes.setFx(attributes.cx()); 240 241 if (!attributes.hasFy()) 242 attributes.setFy(attributes.cy()); 243 244 return attributes; 245} 246} 247 248#endif // ENABLE(SVG) 249