blob: c2a83c016cdea6364e103b22e8dcfa1e616c1b3f [file] [log] [blame]
/*
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 file is part of the KDE project
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 <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 == "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;
}
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())
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);
if (pathSegListFromSVGData(m_fromPath.get(), fromString)) {
m_toPath = SVGPathSegList::create(SVGNames::dAttr);
if (pathSegListFromSVGData(m_toPath.get(), toString))
return true;
}
m_fromPath.clear();
m_toPath.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;
m_propertyType = determinePropertyType(attributeName());
if (m_propertyType == ColorProperty) {
m_animatedColor = baseString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(baseString);
if (m_animatedColor.isValid())
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;
}
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
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)