/*
* 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)
{
}
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)