Home | History | Annotate | Download | only in svg
      1 /*
      2  * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann (at) kde.org>
      3  * Copyright (C) 2004, 2005, 2006 Rob Buis <buis (at) kde.org>
      4  * Copyright (C) 2008 Apple Inc. All rights reserved.
      5  *
      6  * This library is free software; you can redistribute it and/or
      7  * modify it under the terms of the GNU Library General Public
      8  * License as published by the Free Software Foundation; either
      9  * version 2 of the License, or (at your option) any later version.
     10  *
     11  * This library is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14  * Library General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU Library General Public License
     17  * along with this library; see the file COPYING.LIB.  If not, write to
     18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     19  * Boston, MA 02110-1301, USA.
     20  */
     21 
     22 #include "config.h"
     23 
     24 #if ENABLE(SVG) && ENABLE(SVG_ANIMATION)
     25 #include "SVGAnimateElement.h"
     26 
     27 #include "CSSComputedStyleDeclaration.h"
     28 #include "CSSParser.h"
     29 #include "CSSPropertyNames.h"
     30 #include "ColorDistance.h"
     31 #include "FloatConversion.h"
     32 #include "QualifiedName.h"
     33 #include "RenderObject.h"
     34 #include "SVGColor.h"
     35 #include "SVGNames.h"
     36 #include "SVGParserUtilities.h"
     37 #include "SVGPathParserFactory.h"
     38 #include "SVGPathSegList.h"
     39 #include "SVGPointList.h"
     40 #include "SVGStyledElement.h"
     41 
     42 using namespace std;
     43 
     44 namespace WebCore {
     45 
     46 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* document)
     47     : SVGAnimationElement(tagName, document)
     48     , m_animatedAttributeType(AnimatedString)
     49     , m_fromNumber(0)
     50     , m_toNumber(0)
     51     , m_animatedNumber(numeric_limits<double>::infinity())
     52     , m_animatedPathPointer(0)
     53 {
     54 }
     55 
     56 PassRefPtr<SVGAnimateElement> SVGAnimateElement::create(const QualifiedName& tagName, Document* document)
     57 {
     58     return adoptRef(new SVGAnimateElement(tagName, document));
     59 }
     60 
     61 SVGAnimateElement::~SVGAnimateElement()
     62 {
     63 }
     64 
     65 static bool parseNumberValueAndUnit(const String& in, double& value, String& unit)
     66 {
     67     // FIXME: These are from top of my head, figure out all property types that can be animated as numbers.
     68     unsigned unitLength = 0;
     69     String parse = in.stripWhiteSpace();
     70     if (parse.endsWith("%"))
     71         unitLength = 1;
     72     else if (parse.endsWith("px") || parse.endsWith("pt") || parse.endsWith("em"))
     73         unitLength = 2;
     74     else if (parse.endsWith("deg") || parse.endsWith("rad"))
     75         unitLength = 3;
     76     else if (parse.endsWith("grad"))
     77         unitLength = 4;
     78     String newUnit = parse.right(unitLength);
     79     String number = parse.left(parse.length() - unitLength);
     80     if ((!unit.isEmpty() && newUnit != unit) || number.isEmpty())
     81         return false;
     82     UChar last = number[number.length() - 1];
     83     if (last < '0' || last > '9')
     84         return false;
     85     unit = newUnit;
     86     bool ok;
     87     value = number.toDouble(&ok);
     88     return ok;
     89 }
     90 
     91 static inline void adjustForCurrentColor(SVGElement* targetElement, Color& color)
     92 {
     93     ASSERT(targetElement);
     94 
     95     if (RenderObject* targetRenderer = targetElement->renderer())
     96         color = targetRenderer->style()->visitedDependentColor(CSSPropertyColor);
     97     else
     98         color = Color();
     99 }
    100 
    101 static inline void adjustForInheritance(SVGElement* targetElement, const QualifiedName& attributeName, String& value)
    102 {
    103     // FIXME: At the moment the computed style gets returned as a String and needs to get parsed again.
    104     // In the future we might want to work with the value type directly to avoid the String parsing.
    105     ASSERT(targetElement);
    106 
    107     Element* parent = targetElement->parentElement();
    108     if (!parent || !parent->isSVGElement())
    109         return;
    110 
    111     SVGElement* svgParent = static_cast<SVGElement*>(parent);
    112     if (svgParent->isStyled())
    113         value = computedStyle(svgParent)->getPropertyValue(cssPropertyID(attributeName.localName()));
    114 }
    115 
    116 bool SVGAnimateElement::hasValidAttributeType() const
    117 {
    118     SVGElement* targetElement = this->targetElement();
    119     if (!targetElement)
    120         return false;
    121 
    122     return determineAnimatedAttributeType(targetElement) != AnimatedUnknown;
    123 }
    124 
    125 AnimatedAttributeType SVGAnimateElement::determineAnimatedAttributeType(SVGElement* targetElement) const
    126 {
    127     ASSERT(targetElement);
    128 
    129     AnimatedAttributeType type = targetElement->animatedPropertyTypeForAttribute(attributeName());
    130     if (type == AnimatedUnknown || (hasTagName(SVGNames::animateColorTag) && type != AnimatedColor))
    131         return AnimatedUnknown;
    132 
    133     // FIXME: We need type specific animations in the future. Many animations marked as AnimatedString today will
    134     // support continuous animations.
    135     switch (type) {
    136     case AnimatedBoolean:
    137     case AnimatedEnumeration:
    138     case AnimatedLengthList:
    139     case AnimatedNumberList:
    140     case AnimatedNumberOptionalNumber:
    141     case AnimatedPreserveAspectRatio:
    142     case AnimatedRect:
    143     case AnimatedString:
    144         return AnimatedString;
    145     case AnimatedAngle:
    146     case AnimatedInteger:
    147     case AnimatedLength:
    148     case AnimatedNumber:
    149         return AnimatedNumber;
    150     case AnimatedPath:
    151         return AnimatedPath;
    152     case AnimatedPoints:
    153         return AnimatedPoints;
    154     case AnimatedColor:
    155         return AnimatedColor;
    156     case AnimatedUnknown:
    157     case AnimatedTransformList:
    158         // Animations of transform lists are not allowed for <animate> or <set>
    159         // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties
    160         return AnimatedUnknown;
    161     }
    162 
    163     ASSERT_NOT_REACHED();
    164     return AnimatedUnknown;
    165 }
    166 
    167 void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeat, SVGSMILElement* resultElement)
    168 {
    169     ASSERT(percentage >= 0 && percentage <= 1);
    170     ASSERT(resultElement);
    171     bool isInFirstHalfOfAnimation = percentage < 0.5f;
    172     AnimationMode animationMode = this->animationMode();
    173     SVGElement* targetElement = 0;
    174     // Avoid targetElement() call if possible. It might slow down animations.
    175     if (m_fromPropertyValueType == InheritValue || m_toPropertyValueType == InheritValue
    176         || m_fromPropertyValueType == CurrentColorValue || m_toPropertyValueType == CurrentColorValue) {
    177         targetElement = this->targetElement();
    178         if (!targetElement)
    179             return;
    180     }
    181 
    182     if (hasTagName(SVGNames::setTag))
    183         percentage = 1;
    184     if (!resultElement->hasTagName(SVGNames::animateTag) && !resultElement->hasTagName(SVGNames::animateColorTag)
    185         && !resultElement->hasTagName(SVGNames::setTag))
    186         return;
    187     SVGAnimateElement* results = static_cast<SVGAnimateElement*>(resultElement);
    188     // Can't accumulate over a string property.
    189     if (results->m_animatedAttributeType == AnimatedString && m_animatedAttributeType != AnimatedString)
    190         return;
    191     if (m_animatedAttributeType == AnimatedNumber) {
    192         // To animation uses contributions from the lower priority animations as the base value.
    193         if (animationMode == ToAnimation)
    194             m_fromNumber = results->m_animatedNumber;
    195 
    196         // Replace 'currentColor' / 'inherit' by their computed property values.
    197         if (m_fromPropertyValueType == InheritValue) {
    198             String fromNumberString;
    199             adjustForInheritance(targetElement, attributeName(), fromNumberString);
    200             if (!parseNumberValueAndUnit(fromNumberString, m_fromNumber, m_numberUnit))
    201                 return;
    202         }
    203         if (m_toPropertyValueType == InheritValue) {
    204             String toNumberString;
    205             adjustForInheritance(targetElement, attributeName(), toNumberString);
    206             if (!parseNumberValueAndUnit(toNumberString, m_toNumber, m_numberUnit))
    207                 return;
    208         }
    209 
    210         double number;
    211         if (calcMode() == CalcModeDiscrete)
    212             number = isInFirstHalfOfAnimation ? m_fromNumber : m_toNumber;
    213         else
    214             number = (m_toNumber - m_fromNumber) * percentage + m_fromNumber;
    215 
    216         // FIXME: This is not correct for values animation.
    217         if (isAccumulated() && repeat)
    218             number += m_toNumber * repeat;
    219         if (isAdditive() && animationMode != ToAnimation)
    220             results->m_animatedNumber += number;
    221         else
    222             results->m_animatedNumber = number;
    223         return;
    224     }
    225     if (m_animatedAttributeType == AnimatedColor) {
    226         if (animationMode == ToAnimation)
    227             m_fromColor = results->m_animatedColor;
    228 
    229         // Replace 'currentColor' / 'inherit' by their computed property values.
    230         if (m_fromPropertyValueType == CurrentColorValue)
    231             adjustForCurrentColor(targetElement, m_fromColor);
    232         else if (m_fromPropertyValueType == InheritValue) {
    233             String fromColorString;
    234             adjustForInheritance(targetElement, attributeName(), fromColorString);
    235             m_fromColor = SVGColor::colorFromRGBColorString(fromColorString);
    236         }
    237         if (m_toPropertyValueType == CurrentColorValue)
    238             adjustForCurrentColor(targetElement, m_toColor);
    239         else if (m_toPropertyValueType == InheritValue) {
    240             String toColorString;
    241             adjustForInheritance(targetElement, attributeName(), toColorString);
    242             m_toColor = SVGColor::colorFromRGBColorString(toColorString);
    243         }
    244 
    245         Color color;
    246         if (calcMode() == CalcModeDiscrete)
    247             color = isInFirstHalfOfAnimation ? m_fromColor : m_toColor;
    248         else
    249             color = ColorDistance(m_fromColor, m_toColor).scaledDistance(percentage).addToColorAndClamp(m_fromColor);
    250 
    251         // FIXME: Accumulate colors.
    252         if (isAdditive() && animationMode != ToAnimation)
    253             results->m_animatedColor = ColorDistance::addColorsAndClamp(results->m_animatedColor, color);
    254         else
    255             results->m_animatedColor = color;
    256         return;
    257     }
    258     if (m_animatedAttributeType == AnimatedPath) {
    259         if (animationMode == ToAnimation) {
    260             ASSERT(results->m_animatedPathPointer);
    261             m_fromPath = results->m_animatedPathPointer->copy();
    262         }
    263         if (!percentage) {
    264             ASSERT(m_fromPath);
    265             ASSERT(percentage >= 0);
    266             results->m_animatedPathPointer = m_fromPath.get();
    267         } else if (percentage == 1) {
    268             ASSERT(m_toPath);
    269             results->m_animatedPathPointer = m_toPath.get();
    270         } else {
    271             if (m_fromPath && m_toPath) {
    272                 SVGPathParserFactory* factory = SVGPathParserFactory::self();
    273                 if (!factory->buildAnimatedSVGPathByteStream(m_fromPath.get(), m_toPath.get(), results->m_animatedPath, percentage)) {
    274                     results->m_animatedPath.clear();
    275                     results->m_animatedPathPointer = 0;
    276                 } else
    277                     results->m_animatedPathPointer = results->m_animatedPath.get();
    278             } else
    279                 results->m_animatedPathPointer = 0;
    280             // Fall back to discrete animation if the paths are not compatible
    281             if (!results->m_animatedPathPointer) {
    282                 ASSERT(m_fromPath);
    283                 ASSERT(m_toPath);
    284                 ASSERT(!results->m_animatedPath);
    285                 results->m_animatedPathPointer = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1)
    286                     ? m_toPath.get() : m_fromPath.get();
    287             }
    288         }
    289         return;
    290     }
    291     if (m_animatedAttributeType == AnimatedPoints) {
    292         if (!percentage)
    293             results->m_animatedPoints = m_fromPoints;
    294         else if (percentage == 1)
    295             results->m_animatedPoints = m_toPoints;
    296         else {
    297             if (!m_fromPoints.isEmpty() && !m_toPoints.isEmpty())
    298                 SVGPointList::createAnimated(m_fromPoints, m_toPoints, results->m_animatedPoints, percentage);
    299             else
    300                 results->m_animatedPoints.clear();
    301             // Fall back to discrete animation if the points are not compatible
    302             if (results->m_animatedPoints.isEmpty())
    303                 results->m_animatedPoints = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1)
    304                     ? m_toPoints : m_fromPoints;
    305         }
    306         return;
    307     }
    308     ASSERT(animationMode == FromToAnimation || animationMode == ToAnimation || animationMode == ValuesAnimation);
    309     // Replace 'currentColor' / 'inherit' by their computed property values.
    310     if (m_fromPropertyValueType == InheritValue)
    311         adjustForInheritance(targetElement, attributeName(), m_fromString);
    312     if (m_toPropertyValueType == InheritValue)
    313         adjustForInheritance(targetElement, attributeName(), m_toString);
    314 
    315     if ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1)
    316         results->m_animatedString = m_toString;
    317     else
    318         results->m_animatedString = m_fromString;
    319     // Higher priority replace animation overrides any additive results so far.
    320     results->m_animatedAttributeType = AnimatedString;
    321 }
    322 
    323 static bool inheritsFromProperty(SVGElement* targetElement, const QualifiedName& attributeName, const String& value)
    324 {
    325     ASSERT(targetElement);
    326     DEFINE_STATIC_LOCAL(const AtomicString, inherit, ("inherit"));
    327 
    328     if (value.isEmpty() || value != inherit || !targetElement->isStyled())
    329         return false;
    330     return SVGStyledElement::isAnimatableCSSProperty(attributeName);
    331 }
    332 
    333 static bool attributeValueIsCurrentColor(const String& value)
    334 {
    335     DEFINE_STATIC_LOCAL(const AtomicString, currentColor, ("currentColor"));
    336     return value == currentColor;
    337 }
    338 
    339 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
    340 {
    341     SVGElement* targetElement = this->targetElement();
    342     if (!targetElement)
    343         return false;
    344     m_fromPropertyValueType = inheritsFromProperty(targetElement, attributeName(), fromString) ? InheritValue : RegularPropertyValue;
    345     m_toPropertyValueType = inheritsFromProperty(targetElement, attributeName(), toString) ? InheritValue : RegularPropertyValue;
    346 
    347     // FIXME: Needs more solid way determine target attribute type.
    348     m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
    349     if (m_animatedAttributeType == AnimatedColor) {
    350         bool fromIsCurrentColor = attributeValueIsCurrentColor(fromString);
    351         bool toIsCurrentColor = attributeValueIsCurrentColor(toString);
    352         if (fromIsCurrentColor)
    353             m_fromPropertyValueType = CurrentColorValue;
    354         else
    355             m_fromColor = SVGColor::colorFromRGBColorString(fromString);
    356         if (toIsCurrentColor)
    357             m_toPropertyValueType = CurrentColorValue;
    358         else
    359             m_toColor = SVGColor::colorFromRGBColorString(toString);
    360         bool fromIsValid = m_fromColor.isValid() || fromIsCurrentColor || m_fromPropertyValueType == InheritValue;
    361         bool toIsValid = m_toColor.isValid() || toIsCurrentColor || m_toPropertyValueType == InheritValue;
    362         if ((fromIsValid && toIsValid) || (toIsValid && animationMode() == ToAnimation))
    363             return true;
    364     } else if (m_animatedAttributeType == AnimatedNumber) {
    365         m_numberUnit = String();
    366         if (parseNumberValueAndUnit(toString, m_toNumber, m_numberUnit)) {
    367             // For to-animations the from number is calculated later
    368             if (animationMode() == ToAnimation || parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
    369                 return true;
    370         }
    371     } else if (m_animatedAttributeType == AnimatedPath) {
    372         SVGPathParserFactory* factory = SVGPathParserFactory::self();
    373         if (factory->buildSVGPathByteStreamFromString(toString, m_toPath, UnalteredParsing)) {
    374             // For to-animations the from number is calculated later
    375             if (animationMode() == ToAnimation || factory->buildSVGPathByteStreamFromString(fromString, m_fromPath, UnalteredParsing))
    376                 return true;
    377         }
    378         m_fromPath.clear();
    379         m_toPath.clear();
    380     } else if (m_animatedAttributeType == AnimatedPoints) {
    381         m_fromPoints.clear();
    382         if (pointsListFromSVGData(m_fromPoints, fromString)) {
    383             m_toPoints.clear();
    384             if (pointsListFromSVGData(m_toPoints, toString))
    385                 return true;
    386         }
    387     }
    388     m_fromString = fromString;
    389     m_toString = toString;
    390     m_animatedAttributeType = AnimatedString;
    391     return true;
    392 }
    393 
    394 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
    395 {
    396     SVGElement* targetElement = this->targetElement();
    397     if (!targetElement)
    398         return false;
    399     m_fromPropertyValueType = inheritsFromProperty(targetElement, attributeName(), fromString) ? InheritValue : RegularPropertyValue;
    400     m_toPropertyValueType = inheritsFromProperty(targetElement, attributeName(), byString) ? InheritValue : RegularPropertyValue;
    401 
    402     ASSERT(!hasTagName(SVGNames::setTag));
    403     m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
    404     if (m_animatedAttributeType == AnimatedColor) {
    405         bool fromIsCurrentColor = attributeValueIsCurrentColor(fromString);
    406         bool byIsCurrentColor = attributeValueIsCurrentColor(byString);
    407         if (fromIsCurrentColor)
    408             m_fromPropertyValueType = CurrentColorValue;
    409         else
    410             m_fromColor = SVGColor::colorFromRGBColorString(fromString);
    411         if (byIsCurrentColor)
    412             m_toPropertyValueType = CurrentColorValue;
    413         else
    414             m_toColor = SVGColor::colorFromRGBColorString(byString);
    415 
    416         if ((!m_fromColor.isValid() && !fromIsCurrentColor)
    417             || (!m_toColor.isValid() && !byIsCurrentColor))
    418             return false;
    419     } else {
    420         m_numberUnit = String();
    421         m_fromNumber = 0;
    422         if (!fromString.isEmpty() && !parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
    423             return false;
    424         if (!parseNumberValueAndUnit(byString, m_toNumber, m_numberUnit))
    425             return false;
    426         m_toNumber += m_fromNumber;
    427     }
    428     return true;
    429 }
    430 
    431 void SVGAnimateElement::resetToBaseValue(const String& baseString)
    432 {
    433     SVGElement* targetElement = this->targetElement();
    434     ASSERT(targetElement);
    435     m_animatedString = baseString;
    436     AnimatedAttributeType lastType = m_animatedAttributeType;
    437     m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
    438     if (m_animatedAttributeType == AnimatedColor) {
    439         m_animatedColor = baseString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(baseString);
    440         if (isContributing(elapsed())) {
    441             m_animatedAttributeType = lastType;
    442             return;
    443         }
    444     } else if (m_animatedAttributeType == AnimatedNumber) {
    445         if (baseString.isEmpty()) {
    446             m_animatedNumber = 0;
    447             m_numberUnit = String();
    448             return;
    449         }
    450         if (parseNumberValueAndUnit(baseString, m_animatedNumber, m_numberUnit))
    451             return;
    452     } else if (m_animatedAttributeType == AnimatedPath) {
    453         m_animatedPath.clear();
    454         SVGPathParserFactory* factory = SVGPathParserFactory::self();
    455         factory->buildSVGPathByteStreamFromString(baseString, m_animatedPath, UnalteredParsing);
    456         m_animatedPathPointer = m_animatedPath.get();
    457         return;
    458     } else if (m_animatedAttributeType == AnimatedPoints) {
    459         m_animatedPoints.clear();
    460         return;
    461     }
    462     m_animatedAttributeType = AnimatedString;
    463 }
    464 
    465 void SVGAnimateElement::applyResultsToTarget()
    466 {
    467     String valueToApply;
    468     if (m_animatedAttributeType == AnimatedColor)
    469         valueToApply = m_animatedColor.serialized();
    470     else if (m_animatedAttributeType == AnimatedNumber)
    471         valueToApply = String::number(m_animatedNumber) + m_numberUnit;
    472     else if (m_animatedAttributeType == AnimatedPath) {
    473         if (!m_animatedPathPointer || m_animatedPathPointer->isEmpty())
    474             valueToApply = m_animatedString;
    475         else {
    476             // We need to keep going to string and back because we are currently only able to paint
    477             // "processed" paths where complex shapes are replaced with simpler ones. Path
    478             // morphing needs to be done with unprocessed paths.
    479             // FIXME: This could be optimized if paths were not processed at parse time.
    480             SVGPathParserFactory* factory = SVGPathParserFactory::self();
    481             factory->buildStringFromByteStream(m_animatedPathPointer, valueToApply, UnalteredParsing);
    482         }
    483     } else if (m_animatedAttributeType == AnimatedPoints)
    484         valueToApply = m_animatedPoints.isEmpty() ? m_animatedString : m_animatedPoints.valueAsString();
    485     else
    486         valueToApply = m_animatedString;
    487 
    488     setTargetAttributeAnimatedValue(valueToApply);
    489 }
    490 
    491 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
    492 {
    493     SVGElement* targetElement = this->targetElement();
    494     if (!targetElement)
    495         return -1;
    496     m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
    497     if (m_animatedAttributeType == AnimatedNumber) {
    498         double from;
    499         double to;
    500         String unit;
    501         if (!parseNumberValueAndUnit(fromString, from, unit))
    502             return -1;
    503         if (!parseNumberValueAndUnit(toString, to, unit))
    504             return -1;
    505         return narrowPrecisionToFloat(fabs(to - from));
    506     }
    507     if (m_animatedAttributeType == AnimatedColor) {
    508         Color from = SVGColor::colorFromRGBColorString(fromString);
    509         if (!from.isValid())
    510             return -1;
    511         Color to = SVGColor::colorFromRGBColorString(toString);
    512         if (!to.isValid())
    513             return -1;
    514         return ColorDistance(from, to).distance();
    515     }
    516     return -1;
    517 }
    518 
    519 }
    520 #endif // ENABLE(SVG)
    521