WebCore/svg/SVGAnimateElement.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/svg/SVGAnimateElement.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,329 @@
+/*
+    Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
+                  2004, 2005, 2006 Rob Buis <buis@kde.org>
+    Copyright (C) 2008 Apple Inc. All rights reserved.
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to
+    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+    Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+#if ENABLE(SVG) && ENABLE(SVG_ANIMATION)
+#include "SVGAnimateElement.h"
+
+#include "ColorDistance.h"
+#include "FloatConversion.h"
+#include "SVGColor.h"
+#include "SVGParserUtilities.h"
+#include "SVGPathSegList.h"
+#include "SVGPathSegListBuilder.h"
+#include "SVGPointList.h"
+#include <math.h>
+
+using namespace std;
+
+namespace WebCore {
+
+SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* doc)
+    : SVGAnimationElement(tagName, doc)
+    , m_propertyType(StringProperty)
+    , m_fromNumber(0)
+    , m_toNumber(0)
+    , m_animatedNumber(numeric_limits<double>::infinity())
+{
+}
+
+SVGAnimateElement::~SVGAnimateElement()
+{
+}
+
+static bool parseNumberValueAndUnit(const String& in, double& value, String& unit)
+{
+    // FIXME: These are from top of my head, figure out all property types that can be animated as numbers.
+    unsigned unitLength = 0;
+    String parse = in.stripWhiteSpace();
+    if (parse.endsWith("%"))
+        unitLength = 1;
+    else if (parse.endsWith("px") || parse.endsWith("pt") || parse.endsWith("em"))
+        unitLength = 2;
+    else if (parse.endsWith("deg") || parse.endsWith("rad"))
+        unitLength = 3;
+    else if (parse.endsWith("grad"))
+        unitLength = 4;
+    String newUnit = parse.right(unitLength);
+    String number = parse.left(parse.length() - unitLength);
+    if ((!unit.isEmpty() && newUnit != unit) || number.isEmpty())
+        return false;
+    UChar last = number[number.length() - 1];
+    if (last < '0' || last > '9')
+        return false;
+    unit = newUnit;
+    bool ok;
+    value = number.toDouble(&ok);
+    return ok;
+}
+
+SVGAnimateElement::PropertyType SVGAnimateElement::determinePropertyType(const String& attribute) const
+{
+    // FIXME: We need a full property table for figuring this out reliably.
+    if (hasTagName(SVGNames::animateColorTag))
+        return ColorProperty;
+    if (attribute == "d")
+        return PathProperty;
+    if (attribute == "points")
+        return PointsProperty;
+    if (attribute == "color" || attribute == "fill" || attribute == "stroke")
+        return ColorProperty;
+    return NumberProperty;
+}
+
+void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeat, SVGSMILElement* resultElement)
+{
+    ASSERT(percentage >= 0.f && percentage <= 1.f);
+    ASSERT(resultElement);
+    if (hasTagName(SVGNames::setTag))
+        percentage = 1.f;
+    if (!resultElement->hasTagName(SVGNames::animateTag) && !resultElement->hasTagName(SVGNames::animateColorTag) 
+        && !resultElement->hasTagName(SVGNames::setTag))
+        return;
+    SVGAnimateElement* results = static_cast<SVGAnimateElement*>(resultElement);
+    // Can't accumulate over a string property.
+    if (results->m_propertyType == StringProperty && m_propertyType != StringProperty)
+        return;
+    if (m_propertyType == NumberProperty) {
+        // To animation uses contributions from the lower priority animations as the base value.
+        if (animationMode() == ToAnimation)
+            m_fromNumber = results->m_animatedNumber;
+    
+        double number = (m_toNumber - m_fromNumber) * percentage + m_fromNumber;
+
+        // FIXME: This is not correct for values animation.
+        if (isAccumulated() && repeat)
+            number += m_toNumber * repeat;
+        if (isAdditive() && animationMode() != ToAnimation)
+            results->m_animatedNumber += number;
+        else 
+            results->m_animatedNumber = number;
+        return;
+    } 
+    if (m_propertyType == ColorProperty) {
+        if (animationMode() == ToAnimation)
+            m_fromColor = results->m_animatedColor;
+        Color color = ColorDistance(m_fromColor, m_toColor).scaledDistance(percentage).addToColorAndClamp(m_fromColor);
+        // FIXME: Accumulate colors.
+        if (isAdditive() && animationMode() != ToAnimation)
+            results->m_animatedColor = ColorDistance::addColorsAndClamp(results->m_animatedColor, color);
+        else
+            results->m_animatedColor = color;
+        return;
+    }
+    AnimationMode animationMode = this->animationMode();
+    if (m_propertyType == PathProperty) {
+        if (percentage == 0)
+            results->m_animatedPath = m_fromPath;
+        else if (percentage == 1.f)
+            results->m_animatedPath = m_toPath;
+        else {
+            if (m_fromPath && m_toPath)
+                results->m_animatedPath = SVGPathSegList::createAnimated(m_fromPath.get(), m_toPath.get(), percentage);
+            else
+                results->m_animatedPath.clear();
+            // Fall back to discrete animation if the paths are not compatible
+            if (!results->m_animatedPath)
+                results->m_animatedPath = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f) 
+                    ? m_toPath : m_fromPath;
+        }
+        return;
+    } else if (m_propertyType == PointsProperty) {
+        if (percentage == 0)
+            results->m_animatedPoints = m_fromPoints;
+        else if (percentage == 1.f)
+            results->m_animatedPoints = m_toPoints;
+        else {
+            if (m_fromPoints && m_toPoints)
+                results->m_animatedPoints = SVGPointList::createAnimated(m_fromPoints.get(), m_toPoints.get(), percentage);
+            else
+                results->m_animatedPoints.clear();
+            // Fall back to discrete animation if the points are not compatible
+            if (!results->m_animatedPoints)
+                results->m_animatedPoints = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f) 
+                    ? m_toPoints : m_fromPoints;
+        }
+        return;
+    }
+    ASSERT(animationMode == FromToAnimation || animationMode == ToAnimation || animationMode == ValuesAnimation);
+    if ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f)
+        results->m_animatedString = m_toString;
+    else
+        results->m_animatedString = m_fromString;
+    // Higher priority replace animation overrides any additive results so far.
+    results->m_propertyType = StringProperty;
+}
+
+bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
+{
+    // FIXME: Needs more solid way determine target attribute type.
+    m_propertyType = determinePropertyType(attributeName());
+    if (m_propertyType == ColorProperty) {
+        m_fromColor = SVGColor::colorFromRGBColorString(fromString);
+        m_toColor = SVGColor::colorFromRGBColorString(toString);
+        if ((m_fromColor.isValid() && m_toColor.isValid()) || (m_toColor.isValid() && animationMode() == ToAnimation))
+            return true;
+    } else if (m_propertyType == NumberProperty) {
+        m_numberUnit = String();
+        if (parseNumberValueAndUnit(toString, m_toNumber, m_numberUnit)) {
+            // For to-animations the from number is calculated later
+            if (animationMode() == ToAnimation || parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
+                return true;
+        }
+    } else if (m_propertyType == PathProperty) {
+        m_fromPath = SVGPathSegList::create(SVGNames::dAttr);
+        SVGPathSegListBuilder fromParser(m_fromPath.get());
+        if (fromParser.build(fromString, UnalteredParsing)) {
+            m_toPath = SVGPathSegList::create(SVGNames::dAttr);
+            SVGPathSegListBuilder toParser(m_toPath.get());
+            if (toParser.build(toString, UnalteredParsing))
+                return true;
+        }
+        m_fromPath.clear();
+        m_toPath.clear();
+    } else if (m_propertyType == PointsProperty) {
+        m_fromPoints = SVGPointList::create(SVGNames::pointsAttr);
+        if (pointsListFromSVGData(m_fromPoints.get(), fromString)) {
+            m_toPoints = SVGPointList::create(SVGNames::pointsAttr);
+            if (pointsListFromSVGData(m_toPoints.get(), toString))
+                return true;
+        }
+        m_fromPoints.clear();
+        m_toPoints.clear();
+    }
+    m_fromString = fromString;
+    m_toString = toString;
+    m_propertyType = StringProperty;
+    return true;
+}
+
+bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
+{
+    ASSERT(!hasTagName(SVGNames::setTag));
+    m_propertyType = determinePropertyType(attributeName());
+    if (m_propertyType == ColorProperty) {
+        m_fromColor = fromString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(fromString);
+        m_toColor = ColorDistance::addColorsAndClamp(m_fromColor, SVGColor::colorFromRGBColorString(byString));
+        if (!m_fromColor.isValid() || !m_toColor.isValid())
+            return false;
+    } else {
+        m_numberUnit = String();
+        m_fromNumber = 0;
+        if (!fromString.isEmpty() && !parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
+            return false;
+        if (!parseNumberValueAndUnit(byString, m_toNumber, m_numberUnit))
+            return false;
+        m_toNumber += m_fromNumber;
+    }
+    return true;
+}
+
+void SVGAnimateElement::resetToBaseValue(const String& baseString)
+{
+    m_animatedString = baseString;
+    PropertyType lastType = m_propertyType;
+    m_propertyType = determinePropertyType(attributeName());
+    if (m_propertyType == ColorProperty) {
+        m_animatedColor = baseString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(baseString);
+        if (isContributing(elapsed())) {
+            m_propertyType = lastType;
+            return;
+        }
+    } else if (m_propertyType == NumberProperty) {
+        if (baseString.isEmpty()) {
+            m_animatedNumber = 0;
+            m_numberUnit = String();
+            return;
+        }
+        if (parseNumberValueAndUnit(baseString, m_animatedNumber, m_numberUnit))
+            return;
+    } else if (m_propertyType == PathProperty) {
+        m_animatedPath.clear();
+        return;
+    } else if (m_propertyType == PointsProperty) {
+        m_animatedPoints.clear();
+        return;
+    }
+    m_propertyType = StringProperty;
+}
+    
+void SVGAnimateElement::applyResultsToTarget()
+{
+    String valueToApply;
+    if (m_propertyType == ColorProperty)
+        valueToApply = m_animatedColor.name();
+    else if (m_propertyType == NumberProperty)
+        valueToApply = String::number(m_animatedNumber) + m_numberUnit;
+    else if (m_propertyType == PathProperty) {
+        if (!m_animatedPath || !m_animatedPath->numberOfItems())
+            valueToApply = m_animatedString;
+        else {
+            // We need to keep going to string and back because we are currently only able to paint
+            // "processed" paths where complex shapes are replaced with simpler ones. Path 
+            // morphing needs to be done with unprocessed paths.
+            // FIXME: This could be optimized if paths were not processed at parse time.
+            unsigned itemCount = m_animatedPath->numberOfItems();
+            ExceptionCode ec;
+            for (unsigned n = 0; n < itemCount; ++n) {
+                RefPtr<SVGPathSeg> segment = m_animatedPath->getItem(n, ec);
+                valueToApply.append(segment->toString() + " ");
+            }
+        }
+    } else if (m_propertyType == PointsProperty) {
+        if (!m_animatedPoints || !m_animatedPoints->numberOfItems())
+            valueToApply = m_animatedString;
+        else
+            valueToApply = m_animatedPoints->valueAsString();
+    } else
+        valueToApply = m_animatedString;
+    
+    setTargetAttributeAnimatedValue(valueToApply);
+}
+    
+float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
+{
+    m_propertyType = determinePropertyType(attributeName());
+    if (m_propertyType == NumberProperty) {
+        double from;
+        double to;
+        String unit;
+        if (!parseNumberValueAndUnit(fromString, from, unit))
+            return -1.f;
+        if (!parseNumberValueAndUnit(toString, to, unit))
+            return -1.f;
+        return narrowPrecisionToFloat(fabs(to - from));
+    } else if (m_propertyType == ColorProperty) {
+        Color from = SVGColor::colorFromRGBColorString(fromString);
+        if (!from.isValid())
+            return -1.f;
+        Color to = SVGColor::colorFromRGBColorString(toString);
+        if (!to.isValid())
+            return -1.f;
+        return ColorDistance(from, to).distance();
+    }
+    return -1.f;
+}
+   
+}
+
+// vim:ts=4:noet
+#endif // ENABLE(SVG)
+