summaryrefslogtreecommitdiffstats
path: root/WebCore/svg/SVGAnimationElement.cpp
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:30:52 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:30:52 -0800
commit8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2 (patch)
tree11425ea0b299d6fb89c6d3618a22d97d5bf68d0f /WebCore/svg/SVGAnimationElement.cpp
parent648161bb0edfc3d43db63caed5cc5213bc6cb78f (diff)
downloadexternal_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.zip
external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.gz
external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'WebCore/svg/SVGAnimationElement.cpp')
-rw-r--r--WebCore/svg/SVGAnimationElement.cpp533
1 files changed, 533 insertions, 0 deletions
diff --git a/WebCore/svg/SVGAnimationElement.cpp b/WebCore/svg/SVGAnimationElement.cpp
new file mode 100644
index 0000000..5910ac8
--- /dev/null
+++ b/WebCore/svg/SVGAnimationElement.cpp
@@ -0,0 +1,533 @@
+/*
+ Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
+ 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
+ Copyright (C) 2007 Eric Seidel <eric@webkit.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_ANIMATION)
+#include "SVGAnimationElement.h"
+
+#include "CSSComputedStyleDeclaration.h"
+#include "CSSParser.h"
+#include "CSSPropertyNames.h"
+#include "Document.h"
+#include "Event.h"
+#include "EventListener.h"
+#include "FloatConversion.h"
+#include "HTMLNames.h"
+#include "SVGElementInstance.h"
+#include "SVGNames.h"
+#include "SVGURIReference.h"
+#include "SVGUseElement.h"
+#include "XLinkNames.h"
+#include <math.h>
+
+using namespace std;
+
+namespace WebCore {
+
+SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* doc)
+ : SVGSMILElement(tagName, doc)
+ , SVGTests()
+ , SVGExternalResourcesRequired()
+ , m_animationValid(false)
+{
+}
+
+SVGAnimationElement::~SVGAnimationElement()
+{
+}
+
+static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder)
+{
+ result.clear();
+ Vector<String> parseList;
+ parse.split(';', parseList);
+ for (unsigned n = 0; n < parseList.size(); ++n) {
+ String timeString = parseList[n];
+ bool ok;
+ float time = timeString.toFloat(&ok);
+ if (!ok || time < 0 || time > 1.f)
+ goto fail;
+ if (verifyOrder) {
+ if (!n) {
+ if (time != 0)
+ goto fail;
+ } else if (time < result.last())
+ goto fail;
+ }
+ result.append(time);
+ }
+ return;
+fail:
+ result.clear();
+}
+
+static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
+{
+ result.clear();
+ Vector<String> parseList;
+ parse.split(';', parseList);
+ for (unsigned n = 0; n < parseList.size(); ++n) {
+ Vector<String> parseSpline;
+ parseList[n].split(',', parseSpline);
+ // The spec says the sepator is a space, all tests use commas. Weird.
+ if (parseSpline.size() == 1)
+ parseList[n].split(' ', parseSpline);
+ if (parseSpline.size() != 4)
+ goto fail;
+ double curveValues[4];
+ for (unsigned i = 0; i < 4; ++i) {
+ String parseNumber = parseSpline[i];
+ bool ok;
+ curveValues[i] = parseNumber.toDouble(&ok);
+ if (!ok || curveValues[i] < 0.0 || curveValues[i] > 1.0)
+ goto fail;
+ }
+ result.append(UnitBezier(curveValues[0], curveValues[1], curveValues[2], curveValues[3]));
+ }
+ return;
+fail:
+ result.clear();
+}
+
+void SVGAnimationElement::parseMappedAttribute(MappedAttribute* attr)
+{
+ if (attr->name() == SVGNames::valuesAttr)
+ attr->value().string().split(';', m_values);
+ else if (attr->name() == SVGNames::keyTimesAttr)
+ parseKeyTimes(attr->value(), m_keyTimes, true);
+ else if (attr->name() == SVGNames::keyPointsAttr && hasTagName(SVGNames::animateMotionTag)) {
+ // This is specified to be an animateMotion attribute only but it is simpler to put it here
+ // where the other timing calculatations are.
+ parseKeyTimes(attr->value(), m_keyPoints, false);
+ } else if (attr->name() == SVGNames::keySplinesAttr)
+ parseKeySplines(attr->value(), m_keySplines);
+ else {
+ if (SVGTests::parseMappedAttribute(attr))
+ return;
+ if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
+ return;
+ SVGSMILElement::parseMappedAttribute(attr);
+ }
+}
+
+void SVGAnimationElement::attributeChanged(Attribute* attr, bool preserveDecls)
+{
+ // Assumptions may not hold after an attribute change.
+ m_animationValid = false;
+ SVGSMILElement::attributeChanged(attr, preserveDecls);
+}
+
+float SVGAnimationElement::getStartTime() const
+{
+ return narrowPrecisionToFloat(intervalBegin().value());
+}
+
+float SVGAnimationElement::getCurrentTime() const
+{
+ return narrowPrecisionToFloat(elapsed().value());
+}
+
+float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const
+{
+ return narrowPrecisionToFloat(simpleDuration().value());
+}
+
+bool SVGAnimationElement::beginElement(ExceptionCode& ec)
+{
+ return beginElementAt(0, ec);
+}
+
+bool SVGAnimationElement::beginElementAt(float offset, ExceptionCode& ec)
+{
+ addBeginTime(elapsed() + offset);
+ return true;
+}
+
+bool SVGAnimationElement::endElement(ExceptionCode& ec)
+{
+ return endElementAt(0, ec);
+}
+
+bool SVGAnimationElement::endElementAt(float offset, ExceptionCode& ec)
+{
+ if (offset < 0)
+ return false;
+
+ addEndTime(elapsed() + offset);
+ return true;
+}
+
+SVGAnimationElement::AnimationMode SVGAnimationElement::animationMode() const
+{
+ // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
+ if (hasTagName(SVGNames::setTag))
+ return ToAnimation;
+ if (!animationPath().isEmpty())
+ return PathAnimation;
+ if (hasAttribute(SVGNames::valuesAttr))
+ return ValuesAnimation;
+ if (!toValue().isEmpty())
+ return fromValue().isEmpty() ? ToAnimation : FromToAnimation;
+ if (!byValue().isEmpty())
+ return fromValue().isEmpty() ? ByAnimation : FromByAnimation;
+ return NoAnimation;
+}
+
+SVGAnimationElement::CalcMode SVGAnimationElement::calcMode() const
+{
+ static const AtomicString discrete("discrete");
+ static const AtomicString linear("linear");
+ static const AtomicString paced("paced");
+ static const AtomicString spline("spline");
+ const AtomicString& value = getAttribute(SVGNames::calcModeAttr);
+ if (value == discrete)
+ return CalcModeDiscrete;
+ if (value == linear)
+ return CalcModeLinear;
+ if (value == paced)
+ return CalcModePaced;
+ if (value == spline)
+ return CalcModeSpline;
+ return hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear;
+}
+
+SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const
+{
+ static const AtomicString css("CSS");
+ static const AtomicString xml("XML");
+ const AtomicString& value = getAttribute(SVGNames::attributeTypeAttr);
+ if (value == css)
+ return AttributeTypeCSS;
+ if (value == xml)
+ return AttributeTypeXML;
+ return AttributeTypeAuto;
+}
+
+String SVGAnimationElement::toValue() const
+{
+ return getAttribute(SVGNames::toAttr);
+}
+
+String SVGAnimationElement::byValue() const
+{
+ return getAttribute(SVGNames::byAttr);
+}
+
+String SVGAnimationElement::fromValue() const
+{
+ return getAttribute(SVGNames::fromAttr);
+}
+
+bool SVGAnimationElement::isAdditive() const
+{
+ static const AtomicString sum("sum");
+ const AtomicString& value = getAttribute(SVGNames::additiveAttr);
+ return value == sum || animationMode() == ByAnimation;
+}
+
+bool SVGAnimationElement::isAccumulated() const
+{
+ static const AtomicString sum("sum");
+ const AtomicString& value = getAttribute(SVGNames::accumulateAttr);
+ return value == sum && animationMode() != ToAnimation;
+}
+
+bool SVGAnimationElement::hasValidTarget() const
+{
+ return targetElement();
+}
+
+bool SVGAnimationElement::attributeIsCSS(const String& attributeName)
+{
+ // FIXME: We should have a map of all SVG properties and their attribute types so we
+ // could validate animations better. The spec is very vague about this.
+ unsigned id = cssPropertyID(attributeName);
+ // SVG range
+ if (id >= CSSPropertyClipPath && id <= CSSPropertyWritingMode)
+ return true;
+ // Regular CSS properties also in SVG
+ return id == CSSPropertyColor || id == CSSPropertyDisplay || id == CSSPropertyOpacity
+ || (id >= CSSPropertyFont && id <= CSSPropertyFontWeight)
+ || id == CSSPropertyOverflow || id == CSSPropertyVisibility;
+}
+
+bool SVGAnimationElement::targetAttributeIsCSS() const
+{
+ AttributeType type = attributeType();
+ if (type == AttributeTypeCSS)
+ return true;
+ if (type == AttributeTypeXML)
+ return false;
+ return attributeIsCSS(attributeName());
+}
+
+void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value)
+{
+ if (!hasValidTarget())
+ return;
+ SVGElement* target = targetElement();
+ String attributeName = this->attributeName();
+ if (!target || attributeName.isEmpty() || value.isNull())
+ return;
+
+ // We don't want the instance tree to get rebuild. Instances are updated in the loop below.
+ if (target->isStyled())
+ static_cast<SVGStyledElement*>(target)->setInstanceUpdatesBlocked(true);
+
+ ExceptionCode ec;
+ bool isCSS = targetAttributeIsCSS();
+ if (isCSS) {
+ // FIXME: This should set the override style, not the inline style.
+ // Sadly override styles are not yet implemented.
+ target->style()->setProperty(attributeName, value, "", ec);
+ } else {
+ // FIXME: This should set the 'presentation' value, not the actual
+ // attribute value. Whatever that means in practice.
+ target->setAttribute(attributeName, value, ec);
+ }
+
+ if (target->isStyled())
+ static_cast<SVGStyledElement*>(target)->setInstanceUpdatesBlocked(false);
+
+ // If the target element is used in an <use> instance tree, update that as well.
+ HashSet<SVGElementInstance*> instances = target->instancesForElement();
+ HashSet<SVGElementInstance*>::iterator end = instances.end();
+ for (HashSet<SVGElementInstance*>::iterator it = instances.begin(); it != end; ++it) {
+ SVGElement* shadowTreeElement = (*it)->shadowTreeElement();
+ ASSERT(shadowTreeElement);
+ if (isCSS)
+ shadowTreeElement->style()->setProperty(attributeName, value, "", ec);
+ else
+ shadowTreeElement->setAttribute(attributeName, value, ec);
+ (*it)->correspondingUseElement()->setChanged();
+ }
+}
+
+void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
+{
+ ASSERT(calcMode() == CalcModePaced);
+ ASSERT(animationMode() == ValuesAnimation);
+
+ unsigned valuesCount = m_values.size();
+ ASSERT(valuesCount > 1);
+ Vector<float> keyTimesForPaced;
+ float totalDistance = 0;
+ keyTimesForPaced.append(0);
+ for (unsigned n = 0; n < valuesCount - 1; ++n) {
+ // Distance in any units
+ float distance = calculateDistance(m_values[n], m_values[n + 1]);
+ if (distance < 0)
+ return;
+ totalDistance += distance;
+ keyTimesForPaced.append(distance);
+ }
+ if (!totalDistance)
+ return;
+
+ // Normalize.
+ for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n)
+ keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance;
+ keyTimesForPaced[keyTimesForPaced.size() - 1] = 1.f;
+
+ // Use key times calculated based on pacing instead of the user provided ones.
+ m_keyTimes.swap(keyTimesForPaced);
+}
+
+static inline double solveEpsilon(double duration) { return 1. / (200. * duration); }
+
+float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
+{
+ ASSERT(calcMode() == CalcModeSpline);
+ ASSERT(splineIndex < m_keySplines.size());
+ UnitBezier bezier = m_keySplines[splineIndex];
+ SMILTime duration = simpleDuration();
+ if (!duration.isFinite())
+ duration = 100.0;
+ return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value())));
+}
+
+float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
+{
+ ASSERT(!m_keyPoints.isEmpty());
+ ASSERT(calcMode() != CalcModePaced);
+ unsigned keyTimesCount = m_keyTimes.size();
+ ASSERT(keyTimesCount > 1);
+ ASSERT(m_keyPoints.size() == keyTimesCount);
+
+ unsigned index;
+ for (index = 1; index < keyTimesCount; ++index) {
+ if (m_keyTimes[index] >= percent)
+ break;
+ }
+ --index;
+
+ float fromPercent = m_keyTimes[index];
+ float toPercent = m_keyTimes[index + 1];
+ float fromKeyPoint = m_keyPoints[index];
+ float toKeyPoint = m_keyPoints[index + 1];
+
+ if (calcMode() == CalcModeDiscrete)
+ return percent == 1.0f ? toKeyPoint : fromKeyPoint;
+
+ float keyPointPercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent);
+
+ if (calcMode() == CalcModeSpline) {
+ ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
+ keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
+ }
+ return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
+}
+
+void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const
+{
+ ASSERT(!m_keyPoints.isEmpty());
+ ASSERT(m_keyPoints.size() == m_keyTimes.size());
+ ASSERT(calcMode() != CalcModePaced);
+ effectivePercent = calculatePercentFromKeyPoints(percent);
+ unsigned index = effectivePercent == 1.0f ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1));
+ from = m_values[index];
+ to = m_values[index + 1];
+}
+
+void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to) const
+{
+ unsigned valuesCount = m_values.size();
+ ASSERT(m_animationValid);
+ ASSERT(valuesCount > 1);
+
+ CalcMode calcMode = this->calcMode();
+ if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced)
+ return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
+
+ unsigned keyTimesCount = m_keyTimes.size();
+ ASSERT(!keyTimesCount || valuesCount == keyTimesCount);
+ ASSERT(!keyTimesCount || (keyTimesCount > 1 && m_keyTimes[0] == 0));
+
+ unsigned index;
+ for (index = 1; index < keyTimesCount; ++index) {
+ if (m_keyTimes[index] >= percent)
+ break;
+ }
+ --index;
+
+ if (calcMode == CalcModeDiscrete) {
+ if (!keyTimesCount)
+ index = percent == 1.0f ? valuesCount - 1 : static_cast<unsigned>(percent * valuesCount);
+ from = m_values[index];
+ to = m_values[index];
+ effectivePercent = 0.0f;
+ return;
+ }
+
+ float fromPercent;
+ float toPercent;
+ if (keyTimesCount) {
+ fromPercent = m_keyTimes[index];
+ toPercent = m_keyTimes[index + 1];
+ } else {
+ index = static_cast<unsigned>(percent * (valuesCount - 1));
+ fromPercent = static_cast<float>(index) / (valuesCount - 1);
+ toPercent = static_cast<float>(index + 1) / (valuesCount - 1);
+ }
+
+ if (index == valuesCount - 1)
+ --index;
+ from = m_values[index];
+ to = m_values[index + 1];
+ ASSERT(toPercent > fromPercent);
+ effectivePercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent);
+
+ if (calcMode == CalcModeSpline) {
+ ASSERT(m_keySplines.size() == m_values.size() - 1);
+ effectivePercent = calculatePercentForSpline(effectivePercent, index);
+ }
+}
+
+void SVGAnimationElement::startedActiveInterval()
+{
+ m_animationValid = false;
+
+ if (!hasValidTarget())
+ return;
+
+ AnimationMode animationMode = this->animationMode();
+ if (animationMode == NoAnimation)
+ return;
+ if (animationMode == FromToAnimation)
+ m_animationValid = calculateFromAndToValues(fromValue(), toValue());
+ else if (animationMode == ToAnimation) {
+ // For to-animations the from value is the current accumulated value from lower priority animations.
+ // The value is not static and is determined during the animation.
+ m_animationValid = calculateFromAndToValues(String(), toValue());
+ } else if (animationMode == FromByAnimation)
+ m_animationValid = calculateFromAndByValues(fromValue(), byValue());
+ else if (animationMode == ByAnimation)
+ m_animationValid = calculateFromAndByValues(String(), byValue());
+ else if (animationMode == ValuesAnimation) {
+ CalcMode calcMode = this->calcMode();
+ m_animationValid = m_values.size() > 1
+ && (calcMode == CalcModePaced || !hasAttribute(SVGNames::keyTimesAttr) || hasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
+ && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1.0)
+ && (calcMode != CalcModeSpline || (m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1) || m_keySplines.size() == m_keyPoints.size() - 1))
+ && (!hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
+ if (calcMode == CalcModePaced && m_animationValid)
+ calculateKeyTimesForCalcModePaced();
+ } else if (animationMode == PathAnimation)
+ m_animationValid = calcMode() == CalcModePaced || !hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
+}
+
+void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement* resultElement)
+{
+ if (!m_animationValid)
+ return;
+
+ float effectivePercent;
+ if (animationMode() == ValuesAnimation) {
+ String from;
+ String to;
+ currentValuesForValuesAnimation(percent, effectivePercent, from, to);
+ if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo ) {
+ m_animationValid = calculateFromAndToValues(from, to);
+ if (!m_animationValid)
+ return;
+ m_lastValuesAnimationFrom = from;
+ m_lastValuesAnimationTo = to;
+ }
+ } else if (!m_keyPoints.isEmpty() && calcMode() != CalcModePaced)
+ effectivePercent = calculatePercentFromKeyPoints(percent);
+ else
+ effectivePercent = percent;
+
+ calculateAnimatedValue(effectivePercent, repeat, resultElement);
+}
+
+void SVGAnimationElement::endedActiveInterval()
+{
+}
+
+}
+
+// vim:ts=4:noet
+#endif // ENABLE(SVG_ANIMATION)
+