Home | History | Annotate | Download | only in svg
      1 /*
      2     Copyright (C) 2004, 2005, 2006, 2008 Nikolas Zimmermann <zimmermann (at) kde.org>
      3                   2004, 2005, 2006, 2007 Rob Buis <buis (at) kde.org>
      4                   2008 Eric Seidel <eric (at) webkit.org>
      5                   2008 Dirk Schulze <krit (at) 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 
     43 SVGRadialGradientElement::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 
     54 SVGRadialGradientElement::~SVGRadialGradientElement()
     55 {
     56 }
     57 
     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 
     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 
     89 void 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 
    114 void 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 
    178 RadialGradientAttributes 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