Home | History | Annotate | Download | only in svg
      1 /*
      2     Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox (at) kde.org>
      3                   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 #if ENABLE(SVG) && ENABLE(SVG_ANIMATION)
     24 #include "SVGAnimateElement.h"
     25 
     26 #include "ColorDistance.h"
     27 #include "FloatConversion.h"
     28 #include "SVGColor.h"
     29 #include "SVGParserUtilities.h"
     30 #include "SVGPathSegList.h"
     31 #include <math.h>
     32 
     33 using namespace std;
     34 
     35 namespace WebCore {
     36 
     37 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* doc)
     38     : SVGAnimationElement(tagName, doc)
     39     , m_propertyType(StringProperty)
     40     , m_fromNumber(0)
     41     , m_toNumber(0)
     42     , m_animatedNumber(numeric_limits<double>::infinity())
     43 {
     44 }
     45 
     46 SVGAnimateElement::~SVGAnimateElement()
     47 {
     48 }
     49 
     50 static bool parseNumberValueAndUnit(const String& in, double& value, String& unit)
     51 {
     52     // FIXME: These are from top of my head, figure out all property types that can be animated as numbers.
     53     unsigned unitLength = 0;
     54     String parse = in.stripWhiteSpace();
     55     if (parse.endsWith("%"))
     56         unitLength = 1;
     57     else if (parse.endsWith("px") || parse.endsWith("pt") || parse.endsWith("em"))
     58         unitLength = 2;
     59     else if (parse.endsWith("deg") || parse.endsWith("rad"))
     60         unitLength = 3;
     61     else if (parse.endsWith("grad"))
     62         unitLength = 4;
     63     String newUnit = parse.right(unitLength);
     64     String number = parse.left(parse.length() - unitLength);
     65     if ((!unit.isEmpty() && newUnit != unit) || number.isEmpty())
     66         return false;
     67     UChar last = number[number.length() - 1];
     68     if (last < '0' || last > '9')
     69         return false;
     70     unit = newUnit;
     71     bool ok;
     72     value = number.toDouble(&ok);
     73     return ok;
     74 }
     75 
     76 SVGAnimateElement::PropertyType SVGAnimateElement::determinePropertyType(const String& attribute) const
     77 {
     78     // FIXME: We need a full property table for figuring this out reliably.
     79     if (hasTagName(SVGNames::animateColorTag))
     80         return ColorProperty;
     81     if (attribute == "d")
     82         return PathProperty;
     83     if (attribute == "color" || attribute == "fill" || attribute == "stroke")
     84         return ColorProperty;
     85     return NumberProperty;
     86 }
     87 
     88 void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeat, SVGSMILElement* resultElement)
     89 {
     90     ASSERT(percentage >= 0.f && percentage <= 1.f);
     91     ASSERT(resultElement);
     92     if (hasTagName(SVGNames::setTag))
     93         percentage = 1.f;
     94     if (!resultElement->hasTagName(SVGNames::animateTag) && !resultElement->hasTagName(SVGNames::animateColorTag)
     95         && !resultElement->hasTagName(SVGNames::setTag))
     96         return;
     97     SVGAnimateElement* results = static_cast<SVGAnimateElement*>(resultElement);
     98     // Can't accumulate over a string property.
     99     if (results->m_propertyType == StringProperty && m_propertyType != StringProperty)
    100         return;
    101     if (m_propertyType == NumberProperty) {
    102         // To animation uses contributions from the lower priority animations as the base value.
    103         if (animationMode() == ToAnimation)
    104             m_fromNumber = results->m_animatedNumber;
    105 
    106         double number = (m_toNumber - m_fromNumber) * percentage + m_fromNumber;
    107 
    108         // FIXME: This is not correct for values animation.
    109         if (isAccumulated() && repeat)
    110             number += m_toNumber * repeat;
    111         if (isAdditive() && animationMode() != ToAnimation)
    112             results->m_animatedNumber += number;
    113         else
    114             results->m_animatedNumber = number;
    115         return;
    116     }
    117     if (m_propertyType == ColorProperty) {
    118         if (animationMode() == ToAnimation)
    119             m_fromColor = results->m_animatedColor;
    120         Color color = ColorDistance(m_fromColor, m_toColor).scaledDistance(percentage).addToColorAndClamp(m_fromColor);
    121         // FIXME: Accumulate colors.
    122         if (isAdditive() && animationMode() != ToAnimation)
    123             results->m_animatedColor = ColorDistance::addColorsAndClamp(results->m_animatedColor, color);
    124         else
    125             results->m_animatedColor = color;
    126         return;
    127     }
    128     AnimationMode animationMode = this->animationMode();
    129     if (m_propertyType == PathProperty) {
    130         if (percentage == 0)
    131             results->m_animatedPath = m_fromPath;
    132         else if (percentage == 1.f)
    133             results->m_animatedPath = m_toPath;
    134         else {
    135             if (m_fromPath && m_toPath)
    136                 results->m_animatedPath = SVGPathSegList::createAnimated(m_fromPath.get(), m_toPath.get(), percentage);
    137             else
    138                 results->m_animatedPath.clear();
    139             // Fall back to discrete animation if the paths are not compatible
    140             if (!results->m_animatedPath)
    141                 results->m_animatedPath = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f)
    142                     ? m_toPath : m_fromPath;
    143         }
    144         return;
    145     }
    146     ASSERT(animationMode == FromToAnimation || animationMode == ToAnimation || animationMode == ValuesAnimation);
    147     if ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f)
    148         results->m_animatedString = m_toString;
    149     else
    150         results->m_animatedString = m_fromString;
    151     // Higher priority replace animation overrides any additive results so far.
    152     results->m_propertyType = StringProperty;
    153 }
    154 
    155 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
    156 {
    157     // FIXME: Needs more solid way determine target attribute type.
    158     m_propertyType = determinePropertyType(attributeName());
    159     if (m_propertyType == ColorProperty) {
    160         m_fromColor = SVGColor::colorFromRGBColorString(fromString);
    161         m_toColor = SVGColor::colorFromRGBColorString(toString);
    162         if (m_fromColor.isValid() && m_toColor.isValid())
    163             return true;
    164     } else if (m_propertyType == NumberProperty) {
    165         m_numberUnit = String();
    166         if (parseNumberValueAndUnit(toString, m_toNumber, m_numberUnit)) {
    167             // For to-animations the from number is calculated later
    168             if (animationMode() == ToAnimation || parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
    169                 return true;
    170         }
    171     } else if (m_propertyType == PathProperty) {
    172         m_fromPath = SVGPathSegList::create(SVGNames::dAttr);
    173         if (pathSegListFromSVGData(m_fromPath.get(), fromString)) {
    174             m_toPath = SVGPathSegList::create(SVGNames::dAttr);
    175             if (pathSegListFromSVGData(m_toPath.get(), toString))
    176                 return true;
    177         }
    178         m_fromPath.clear();
    179         m_toPath.clear();
    180     }
    181     m_fromString = fromString;
    182     m_toString = toString;
    183     m_propertyType = StringProperty;
    184     return true;
    185 }
    186 
    187 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
    188 {
    189     ASSERT(!hasTagName(SVGNames::setTag));
    190     m_propertyType = determinePropertyType(attributeName());
    191     if (m_propertyType == ColorProperty) {
    192         m_fromColor = fromString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(fromString);
    193         m_toColor = ColorDistance::addColorsAndClamp(m_fromColor, SVGColor::colorFromRGBColorString(byString));
    194         if (!m_fromColor.isValid() || !m_toColor.isValid())
    195             return false;
    196     } else {
    197         m_numberUnit = String();
    198         m_fromNumber = 0;
    199         if (!fromString.isEmpty() && !parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
    200             return false;
    201         if (!parseNumberValueAndUnit(byString, m_toNumber, m_numberUnit))
    202             return false;
    203         m_toNumber += m_fromNumber;
    204     }
    205     return true;
    206 }
    207 
    208 void SVGAnimateElement::resetToBaseValue(const String& baseString)
    209 {
    210     m_animatedString = baseString;
    211     m_propertyType = determinePropertyType(attributeName());
    212     if (m_propertyType == ColorProperty) {
    213         m_animatedColor = baseString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(baseString);
    214         if (m_animatedColor.isValid())
    215             return;
    216     } else if (m_propertyType == NumberProperty) {
    217         if (baseString.isEmpty()) {
    218             m_animatedNumber = 0;
    219             m_numberUnit = String();
    220             return;
    221         }
    222         if (parseNumberValueAndUnit(baseString, m_animatedNumber, m_numberUnit))
    223             return;
    224     } else if (m_propertyType == PathProperty) {
    225         m_animatedPath.clear();
    226         return;
    227     }
    228     m_propertyType = StringProperty;
    229 }
    230 
    231 void SVGAnimateElement::applyResultsToTarget()
    232 {
    233     String valueToApply;
    234     if (m_propertyType == ColorProperty)
    235         valueToApply = m_animatedColor.name();
    236     else if (m_propertyType == NumberProperty)
    237         valueToApply = String::number(m_animatedNumber) + m_numberUnit;
    238     else if (m_propertyType == PathProperty) {
    239         if (!m_animatedPath || !m_animatedPath->numberOfItems())
    240             valueToApply = m_animatedString;
    241         else {
    242             // We need to keep going to string and back because we are currently only able to paint
    243             // "processed" paths where complex shapes are replaced with simpler ones. Path
    244             // morphing needs to be done with unprocessed paths.
    245             // FIXME: This could be optimized if paths were not processed at parse time.
    246             unsigned itemCount = m_animatedPath->numberOfItems();
    247             ExceptionCode ec;
    248             for (unsigned n = 0; n < itemCount; ++n) {
    249                 RefPtr<SVGPathSeg> segment = m_animatedPath->getItem(n, ec);
    250                 valueToApply.append(segment->toString() + " ");
    251             }
    252         }
    253     } else
    254         valueToApply = m_animatedString;
    255 
    256     setTargetAttributeAnimatedValue(valueToApply);
    257 }
    258 
    259 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
    260 {
    261     m_propertyType = determinePropertyType(attributeName());
    262     if (m_propertyType == NumberProperty) {
    263         double from;
    264         double to;
    265         String unit;
    266         if (!parseNumberValueAndUnit(fromString, from, unit))
    267             return -1.f;
    268         if (!parseNumberValueAndUnit(toString, to, unit))
    269             return -1.f;
    270         return narrowPrecisionToFloat(fabs(to - from));
    271     } else if (m_propertyType == ColorProperty) {
    272         Color from = SVGColor::colorFromRGBColorString(fromString);
    273         if (!from.isValid())
    274             return -1.f;
    275         Color to = SVGColor::colorFromRGBColorString(toString);
    276         if (!to.isValid())
    277             return -1.f;
    278         return ColorDistance(from, to).distance();
    279     }
    280     return -1.f;
    281 }
    282 
    283 }
    284 
    285 // vim:ts=4:noet
    286 #endif // ENABLE(SVG)
    287 
    288