SVGAnimateElement.cpp   [plain text]


/*
 * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org>
 * Copyright (C) 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 "CSSComputedStyleDeclaration.h"
#include "CSSParser.h"
#include "CSSPropertyNames.h"
#include "ColorDistance.h"
#include "FloatConversion.h"
#include "QualifiedName.h"
#include "RenderObject.h"
#include "SVGColor.h"
#include "SVGNames.h"
#include "SVGParserUtilities.h"
#include "SVGPathParserFactory.h"
#include "SVGPathSegList.h"
#include "SVGPointList.h"
#include "SVGStyledElement.h"

using namespace std;

namespace WebCore {

SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* document)
    : SVGAnimationElement(tagName, document)
    , m_animatedAttributeType(AnimatedString)
    , m_fromNumber(0)
    , m_toNumber(0)
    , m_animatedNumber(numeric_limits<double>::infinity())
    , m_animatedPathPointer(0)
{
    ASSERT(hasTagName(SVGNames::animateTag) || hasTagName(SVGNames::setTag) || hasTagName(SVGNames::animateColorTag));
}

PassRefPtr<SVGAnimateElement> SVGAnimateElement::create(const QualifiedName& tagName, Document* document)
{
    return adoptRef(new SVGAnimateElement(tagName, document));
}

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;
}

static inline void adjustForCurrentColor(SVGElement* targetElement, Color& color)
{
    ASSERT(targetElement);
    
    if (RenderObject* targetRenderer = targetElement->renderer())
        color = targetRenderer->style()->visitedDependentColor(CSSPropertyColor);
    else
        color = Color();
}

static inline void adjustForInheritance(SVGElement* targetElement, const QualifiedName& attributeName, String& value)
{
    // FIXME: At the moment the computed style gets returned as a String and needs to get parsed again.
    // In the future we might want to work with the value type directly to avoid the String parsing.
    ASSERT(targetElement);

    Element* parent = targetElement->parentElement();
    if (!parent || !parent->isSVGElement())
        return;

    SVGElement* svgParent = static_cast<SVGElement*>(parent);
    if (svgParent->isStyled())
        value = computedStyle(svgParent)->getPropertyValue(cssPropertyID(attributeName.localName()));
}

bool SVGAnimateElement::hasValidAttributeType() const
{
    SVGElement* targetElement = this->targetElement();
    if (!targetElement)
        return false;
    
    return determineAnimatedAttributeType(targetElement) != AnimatedUnknown;
}

AnimatedAttributeType SVGAnimateElement::determineAnimatedAttributeType(SVGElement* targetElement) const
{
    ASSERT(targetElement);

    AnimatedAttributeType type = targetElement->animatedPropertyTypeForAttribute(attributeName());
    if (type == AnimatedUnknown || (hasTagName(SVGNames::animateColorTag) && type != AnimatedColor))
        return AnimatedUnknown;

    // FIXME: We need type specific animations in the future. Many animations marked as AnimatedString today will
    // support continuous animations.
    switch (type) {
    case AnimatedBoolean:
    case AnimatedEnumeration:
    case AnimatedLengthList:
    case AnimatedNumberList:
    case AnimatedNumberOptionalNumber:
    case AnimatedPreserveAspectRatio:
    case AnimatedRect:
    case AnimatedString:
        return AnimatedString;
    case AnimatedAngle:
    case AnimatedInteger:
    case AnimatedLength:
    case AnimatedNumber:
        return AnimatedNumber;
    case AnimatedPath:
        return AnimatedPath;
    case AnimatedPoints:
        return AnimatedPoints;
    case AnimatedColor:
        return AnimatedColor;
    case AnimatedUnknown:
    case AnimatedTransformList:
        // Animations of transform lists are not allowed for <animate> or <set>
        // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties
        return AnimatedUnknown;
    }

    ASSERT_NOT_REACHED();
    return AnimatedUnknown;
}

void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeat, SVGSMILElement* resultElement)
{
    ASSERT(percentage >= 0 && percentage <= 1);
    ASSERT(resultElement);
    bool isInFirstHalfOfAnimation = percentage < 0.5f;
    AnimationMode animationMode = this->animationMode();
    SVGElement* targetElement = 0;
    // Avoid targetElement() call if possible. It might slow down animations.
    if (m_fromPropertyValueType == InheritValue || m_toPropertyValueType == InheritValue
        || m_fromPropertyValueType == CurrentColorValue || m_toPropertyValueType == CurrentColorValue) {
        targetElement = this->targetElement();
        if (!targetElement)
            return;
    }
    
    if (hasTagName(SVGNames::setTag))
        percentage = 1;
    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_animatedAttributeType == AnimatedString && m_animatedAttributeType != AnimatedString)
        return;
    if (m_animatedAttributeType == AnimatedNumber) {
        // To animation uses contributions from the lower priority animations as the base value.
        if (animationMode == ToAnimation)
            m_fromNumber = results->m_animatedNumber;
        
        // Replace 'currentColor' / 'inherit' by their computed property values.
        if (m_fromPropertyValueType == InheritValue) {
            String fromNumberString;
            adjustForInheritance(targetElement, attributeName(), fromNumberString);
            if (!parseNumberValueAndUnit(fromNumberString, m_fromNumber, m_numberUnit))
                return;
        }
        if (m_toPropertyValueType == InheritValue) {
            String toNumberString;
            adjustForInheritance(targetElement, attributeName(), toNumberString);
            if (!parseNumberValueAndUnit(toNumberString, m_toNumber, m_numberUnit))
                return;
        }

        double number;
        if (calcMode() == CalcModeDiscrete)
            number = isInFirstHalfOfAnimation ? m_fromNumber : m_toNumber;
        else
            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_animatedAttributeType == AnimatedColor) {
        if (animationMode == ToAnimation)
            m_fromColor = results->m_animatedColor;

        // Replace 'currentColor' / 'inherit' by their computed property values.
        if (m_fromPropertyValueType == CurrentColorValue)
            adjustForCurrentColor(targetElement, m_fromColor);
        else if (m_fromPropertyValueType == InheritValue) {
            String fromColorString;
            adjustForInheritance(targetElement, attributeName(), fromColorString);
            m_fromColor = SVGColor::colorFromRGBColorString(fromColorString);
        }
        if (m_toPropertyValueType == CurrentColorValue)
            adjustForCurrentColor(targetElement, m_toColor);
        else if (m_toPropertyValueType == InheritValue) {
            String toColorString;
            adjustForInheritance(targetElement, attributeName(), toColorString);
            m_toColor = SVGColor::colorFromRGBColorString(toColorString);
        }

        Color color;
        if (calcMode() == CalcModeDiscrete)
            color = isInFirstHalfOfAnimation ? m_fromColor : m_toColor;
        else
            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;
    }
    if (m_animatedAttributeType == AnimatedPath) {
        if (animationMode == ToAnimation) {
            ASSERT(results->m_animatedPathPointer);
            m_fromPath = results->m_animatedPathPointer->copy();
        }
        if (!percentage) {
            ASSERT(m_fromPath);
            ASSERT(percentage >= 0);
            results->m_animatedPathPointer = m_fromPath.get();
        } else if (percentage == 1) {
            ASSERT(m_toPath);
            results->m_animatedPathPointer = m_toPath.get();
        } else {
            if (m_fromPath && m_toPath) {
                SVGPathParserFactory* factory = SVGPathParserFactory::self();
                if (!factory->buildAnimatedSVGPathByteStream(m_fromPath.get(), m_toPath.get(), results->m_animatedPath, percentage)) {
                    results->m_animatedPath.clear();
                    results->m_animatedPathPointer = 0;
                } else
                    results->m_animatedPathPointer = results->m_animatedPath.get();
            } else
                results->m_animatedPathPointer = 0;
            // Fall back to discrete animation if the paths are not compatible
            if (!results->m_animatedPathPointer) {
                ASSERT(m_fromPath);
                ASSERT(m_toPath);
                ASSERT(!results->m_animatedPath);
                results->m_animatedPathPointer = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1) 
                    ? m_toPath.get() : m_fromPath.get();
            }
        }
        return;
    }
    if (m_animatedAttributeType == AnimatedPoints) {
        if (!percentage)
            results->m_animatedPoints = m_fromPoints;
        else if (percentage == 1)
            results->m_animatedPoints = m_toPoints;
        else {
            if (!m_fromPoints.isEmpty() && !m_toPoints.isEmpty())
                SVGPointList::createAnimated(m_fromPoints, m_toPoints, results->m_animatedPoints, percentage);
            else
                results->m_animatedPoints.clear();
            // Fall back to discrete animation if the points are not compatible
            if (results->m_animatedPoints.isEmpty())
                results->m_animatedPoints = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1) 
                    ? m_toPoints : m_fromPoints;
        }
        return;
    }
    ASSERT(animationMode == FromToAnimation || animationMode == ToAnimation || animationMode == ValuesAnimation);
    // Replace 'currentColor' / 'inherit' by their computed property values.
    if (m_fromPropertyValueType == InheritValue)
        adjustForInheritance(targetElement, attributeName(), m_fromString);
    if (m_toPropertyValueType == InheritValue)
        adjustForInheritance(targetElement, attributeName(), m_toString);

    if ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1)
        results->m_animatedString = m_toString;
    else
        results->m_animatedString = m_fromString;
    // Higher priority replace animation overrides any additive results so far.
    results->m_animatedAttributeType = AnimatedString;
}

static bool inheritsFromProperty(SVGElement* targetElement, const QualifiedName& attributeName, const String& value)
{
    ASSERT(targetElement);
    DEFINE_STATIC_LOCAL(const AtomicString, inherit, ("inherit"));

    if (value.isEmpty() || value != inherit || !targetElement->isStyled())
        return false;
    return SVGStyledElement::isAnimatableCSSProperty(attributeName);
}

static bool attributeValueIsCurrentColor(const String& value)
{
    DEFINE_STATIC_LOCAL(const AtomicString, currentColor, ("currentColor"));
    return value == currentColor;
}

bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
{
    SVGElement* targetElement = this->targetElement();
    if (!targetElement)
        return false;
    m_fromPropertyValueType = inheritsFromProperty(targetElement, attributeName(), fromString) ? InheritValue : RegularPropertyValue;
    m_toPropertyValueType = inheritsFromProperty(targetElement, attributeName(), toString) ? InheritValue : RegularPropertyValue;

    // FIXME: Needs more solid way determine target attribute type.
    m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
    if (m_animatedAttributeType == AnimatedColor) {
        bool fromIsCurrentColor = attributeValueIsCurrentColor(fromString);
        bool toIsCurrentColor = attributeValueIsCurrentColor(toString);
        if (fromIsCurrentColor)
            m_fromPropertyValueType = CurrentColorValue;
        else
            m_fromColor = SVGColor::colorFromRGBColorString(fromString);
        if (toIsCurrentColor)
            m_toPropertyValueType = CurrentColorValue;
        else
            m_toColor = SVGColor::colorFromRGBColorString(toString);
        bool fromIsValid = m_fromColor.isValid() || fromIsCurrentColor || m_fromPropertyValueType == InheritValue;
        bool toIsValid = m_toColor.isValid() || toIsCurrentColor || m_toPropertyValueType == InheritValue;
        if ((fromIsValid && toIsValid) || (toIsValid && animationMode() == ToAnimation))
            return true;
    } else if (m_animatedAttributeType == AnimatedNumber) {
        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_animatedAttributeType == AnimatedPath) {
        SVGPathParserFactory* factory = SVGPathParserFactory::self();
        if (factory->buildSVGPathByteStreamFromString(toString, m_toPath, UnalteredParsing)) {
            // For to-animations the from number is calculated later
            if (animationMode() == ToAnimation || factory->buildSVGPathByteStreamFromString(fromString, m_fromPath, UnalteredParsing))
                return true;
        }
        m_fromPath.clear();
        m_toPath.clear();
    } else if (m_animatedAttributeType == AnimatedPoints) {
        m_fromPoints.clear();
        if (pointsListFromSVGData(m_fromPoints, fromString)) {
            m_toPoints.clear();
            if (pointsListFromSVGData(m_toPoints, toString))
                return true;
        }
    }
    m_fromString = fromString;
    m_toString = toString;
    m_animatedAttributeType = AnimatedString;
    return true;
}

bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
{
    SVGElement* targetElement = this->targetElement();
    if (!targetElement)
        return false;
    m_fromPropertyValueType = inheritsFromProperty(targetElement, attributeName(), fromString) ? InheritValue : RegularPropertyValue;
    m_toPropertyValueType = inheritsFromProperty(targetElement, attributeName(), byString) ? InheritValue : RegularPropertyValue;

    ASSERT(!hasTagName(SVGNames::setTag));
    m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
    if (m_animatedAttributeType == AnimatedColor) {
        bool fromIsCurrentColor = attributeValueIsCurrentColor(fromString);
        bool byIsCurrentColor = attributeValueIsCurrentColor(byString);
        if (fromIsCurrentColor)
            m_fromPropertyValueType = CurrentColorValue;
        else
            m_fromColor = SVGColor::colorFromRGBColorString(fromString);
        if (byIsCurrentColor)
            m_toPropertyValueType = CurrentColorValue;
        else
            m_toColor = SVGColor::colorFromRGBColorString(byString);
        
        if ((!m_fromColor.isValid() && !fromIsCurrentColor)
            || (!m_toColor.isValid() && !byIsCurrentColor))
            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)
{
    SVGElement* targetElement = this->targetElement();
    ASSERT(targetElement);
    m_animatedString = baseString;
    AnimatedAttributeType lastType = m_animatedAttributeType;
    m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
    if (m_animatedAttributeType == AnimatedColor) {
        m_animatedColor = baseString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(baseString);
        if (isContributing(elapsed())) {
            m_animatedAttributeType = lastType;
            return;
        }
    } else if (m_animatedAttributeType == AnimatedNumber) {
        if (baseString.isEmpty()) {
            m_animatedNumber = 0;
            m_numberUnit = String();
            return;
        }
        if (parseNumberValueAndUnit(baseString, m_animatedNumber, m_numberUnit))
            return;
    } else if (m_animatedAttributeType == AnimatedPath) {
        m_animatedPath.clear();
        SVGPathParserFactory* factory = SVGPathParserFactory::self();
        factory->buildSVGPathByteStreamFromString(baseString, m_animatedPath, UnalteredParsing);
        m_animatedPathPointer = m_animatedPath.get();
        return;
    } else if (m_animatedAttributeType == AnimatedPoints) {
        m_animatedPoints.clear();
        return;
    }
    m_animatedAttributeType = AnimatedString;
}
    
void SVGAnimateElement::applyResultsToTarget()
{
    String valueToApply;
    if (m_animatedAttributeType == AnimatedColor)
        valueToApply = m_animatedColor.serialized();
    else if (m_animatedAttributeType == AnimatedNumber)
        valueToApply = String::number(m_animatedNumber) + m_numberUnit;
    else if (m_animatedAttributeType == AnimatedPath) {
        if (!m_animatedPathPointer || m_animatedPathPointer->isEmpty())
            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.
            SVGPathParserFactory* factory = SVGPathParserFactory::self();
            factory->buildStringFromByteStream(m_animatedPathPointer, valueToApply, UnalteredParsing);
        }
    } else if (m_animatedAttributeType == AnimatedPoints)
        valueToApply = m_animatedPoints.isEmpty() ? m_animatedString : m_animatedPoints.valueAsString();
    else
        valueToApply = m_animatedString;
    
    setTargetAttributeAnimatedValue(valueToApply);
}
    
float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
{
    SVGElement* targetElement = this->targetElement();
    if (!targetElement)
        return -1;
    m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
    if (m_animatedAttributeType == AnimatedNumber) {
        double from;
        double to;
        String unit;
        if (!parseNumberValueAndUnit(fromString, from, unit))
            return -1;
        if (!parseNumberValueAndUnit(toString, to, unit))
            return -1;
        return narrowPrecisionToFloat(fabs(to - from));
    }
    if (m_animatedAttributeType == AnimatedColor) {
        Color from = SVGColor::colorFromRGBColorString(fromString);
        if (!from.isValid())
            return -1;
        Color to = SVGColor::colorFromRGBColorString(toString);
        if (!to.isValid())
            return -1;
        return ColorDistance(from, to).distance();
    }
    return -1;
}
   
}
#endif // ENABLE(SVG)