blob: 5ccc17f6adf708cee866619758d40ea4ac15c6d2 [file] [log] [blame]
/*
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>
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)
#include "SVGAnimationElement.h"
#include "CSSPropertyNames.h"
#include "Document.h"
#include "FloatConversion.h"
#include "SVGParserUtilities.h"
#include "SVGSVGElement.h"
#include "SVGURIReference.h"
#include "TimeScheduler.h"
#include "XLinkNames.h"
#include <float.h>
#include <math.h>
#include <wtf/MathExtras.h>
#include <wtf/Vector.h>
using namespace std;
namespace WebCore {
SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* doc)
: SVGElement(tagName, doc)
, SVGTests()
, SVGExternalResourcesRequired()
, m_targetElement(0)
, m_connectedToTimer(false)
, m_currentTime(0.0)
, m_simpleDuration(0.0)
, m_fill(FILL_REMOVE)
, m_restart(RESTART_ALWAYS)
, m_calcMode(CALCMODE_LINEAR)
, m_additive(ADDITIVE_REPLACE)
, m_accumulate(ACCUMULATE_NONE)
, m_attributeType(ATTRIBUTETYPE_AUTO)
, m_max(0.0)
, m_min(0.0)
, m_end(0.0)
, m_begin(0.0)
, m_repetitions(0)
, m_repeatCount(0)
{
}
SVGAnimationElement::~SVGAnimationElement()
{
}
bool SVGAnimationElement::hasValidTarget() const
{
return targetElement();
}
SVGElement* SVGAnimationElement::targetElement() const
{
if (!m_targetElement) {
Node *target = 0;
if (!m_href.isEmpty()) {
target = document()->getElementById(SVGURIReference::getTarget(m_href));
} else if (parentNode()) {
// TODO : do we really need to skip non element nodes? Can that happen at all?
target = parentNode();
while (target) {
if (target->nodeType() != ELEMENT_NODE)
target = target->parentNode();
else
break;
}
}
if (target && target->isSVGElement())
m_targetElement = static_cast<SVGElement*>(target);
}
return m_targetElement;
}
float SVGAnimationElement::getEndTime() const
{
return narrowPrecisionToFloat(m_end);
}
float SVGAnimationElement::getStartTime() const
{
return narrowPrecisionToFloat(m_begin);
}
float SVGAnimationElement::getCurrentTime() const
{
return narrowPrecisionToFloat(m_currentTime);
}
float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const
{
return narrowPrecisionToFloat(m_simpleDuration);
}
void SVGAnimationElement::parseKeyNumbers(Vector<float>& keyNumbers, const String& value)
{
float number = 0.0f;
const UChar* ptr = value.characters();
const UChar* end = ptr + value.length();
skipOptionalSpaces(ptr, end);
while (ptr < end) {
if (!parseNumber(ptr, end, number, false))
return;
keyNumbers.append(number);
if (!skipOptionalSpaces(ptr, end) || *ptr != ';')
return;
ptr++;
skipOptionalSpaces(ptr, end);
}
}
static void parseKeySplines(Vector<SVGAnimationElement::KeySpline>& keySplines, const String& value)
{
float number = 0.0f;
SVGAnimationElement::KeySpline keySpline;
const UChar* ptr = value.characters();
const UChar* end = ptr + value.length();
skipOptionalSpaces(ptr, end);
while (ptr < end) {
if (!(parseNumber(ptr, end, number, false) && skipOptionalSpaces(ptr, end)))
return;
keySpline.control1.setX(number);
if (!(parseNumber(ptr, end, number, false) && skipOptionalSpaces(ptr, end)))
return;
keySpline.control1.setY(number);
if (!(parseNumber(ptr, end, number, false) && skipOptionalSpaces(ptr, end)))
return;
keySpline.control2.setX(number);
if (!parseNumber(ptr, end, number, false))
return;
keySpline.control2.setY(number);
keySplines.append(keySpline);
if (!skipOptionalSpaces(ptr, end) || *ptr != ';')
return;
ptr++;
skipOptionalSpaces(ptr, end);
}
}
void SVGAnimationElement::parseBeginOrEndValue(double& number, const String& value)
{
// TODO: Don't use SVGStringList for parsing.
AtomicString dummy;
RefPtr<SVGStringList> valueList = new SVGStringList(QualifiedName(dummy, dummy, dummy));
valueList->parse(value, ';');
ExceptionCode ec = 0;
for (unsigned int i = 0; i < valueList->numberOfItems(); i++) {
String current = valueList->getItem(i, ec);
if (current.startsWith("accessKey")) {
// Register keyDownEventListener for the character
String character = current.substring(current.length() - 2, 1);
// FIXME: Not implemented! Supposed to register accessKey character
} else if (current.startsWith("wallclock")) {
int firstBrace = current.find('(');
int secondBrace = current.find(')');
String wallclockValue = current.substring(firstBrace + 1, secondBrace - firstBrace - 2);
// FIXME: Not implemented! Supposed to use wallClock value
} else if (current.contains('.')) {
int dotPosition = current.find('.');
String element = current.substring(0, dotPosition);
String clockValue;
if (current.contains("begin"))
clockValue = current.substring(dotPosition + 6);
else if (current.contains("end"))
clockValue = current.substring(dotPosition + 4);
else if (current.contains("repeat"))
clockValue = current.substring(dotPosition + 7);
else { // DOM2 Event Reference
int plusMinusPosition = -1;
if (current.contains('+'))
plusMinusPosition = current.find('+');
else if (current.contains('-'))
plusMinusPosition = current.find('-');
String event = current.substring(dotPosition + 1, plusMinusPosition - dotPosition - 1);
clockValue = current.substring(dotPosition + event.length() + 1);
// FIXME: supposed to use DOM Event
}
} else {
number = parseClockValue(current);
if (!isIndefinite(number))
number *= 1000.0;
// FIXME: supposed to set begin/end time
}
}
}
void SVGAnimationElement::parseMappedAttribute(MappedAttribute* attr)
{
if (attr->name().matches(XLinkNames::hrefAttr))
m_href = attr->value();
else if (attr->name() == SVGNames::attributeNameAttr)
m_attributeName = attr->value();
else if (attr->name() == SVGNames::attributeTypeAttr) {
if (attr->value() == "CSS")
m_attributeType = ATTRIBUTETYPE_CSS;
else if (attr->value() == "XML")
m_attributeType = ATTRIBUTETYPE_XML;
else if (attr->value() == "auto")
m_attributeType = ATTRIBUTETYPE_AUTO;
} else if (attr->name() == SVGNames::beginAttr)
parseBeginOrEndValue(m_begin, attr->value());
else if (attr->name() == SVGNames::endAttr)
parseBeginOrEndValue(m_end, attr->value());
else if (attr->name() == SVGNames::durAttr) {
m_simpleDuration = parseClockValue(attr->value());
if (!isIndefinite(m_simpleDuration))
m_simpleDuration *= 1000.0;
} else if (attr->name() == SVGNames::minAttr) {
m_min = parseClockValue(attr->value());
if (!isIndefinite(m_min))
m_min *= 1000.0;
} else if (attr->name() == SVGNames::maxAttr) {
m_max = parseClockValue(attr->value());
if (!isIndefinite(m_max))
m_max *= 1000.0;
} else if (attr->name() == SVGNames::restartAttr) {
if (attr->value() == "whenNotActive")
m_restart = RESTART_WHENNOTACTIVE;
else if (attr->value() == "never")
m_restart = RESTART_NEVER;
else if (attr->value() == "always")
m_restart = RESTART_ALWAYS;
} else if (attr->name() == SVGNames::repeatCountAttr) {
if (attr->value() == "indefinite")
m_repeatCount = DBL_MAX;
else
m_repeatCount = attr->value().toDouble();
} else if (attr->name() == SVGNames::repeatDurAttr)
m_repeatDur = attr->value();
else if (attr->name() == SVGNames::fillAttr) {
if (attr->value() == "freeze")
m_fill = FILL_FREEZE;
else if (attr->value() == "remove")
m_fill = FILL_REMOVE;
} else if (attr->name() == SVGNames::calcModeAttr) {
if (attr->value() == "discrete")
m_calcMode = CALCMODE_DISCRETE;
else if (attr->value() == "linear")
m_calcMode = CALCMODE_LINEAR;
else if (attr->value() == "spline")
m_calcMode = CALCMODE_SPLINE;
else if (attr->value() == "paced")
m_calcMode = CALCMODE_PACED;
} else if (attr->name() == SVGNames::valuesAttr) {
m_values.clear();
m_values = parseDelimitedString(attr->value(), ';');
} else if (attr->name() == SVGNames::keyTimesAttr) {
m_keyTimes.clear();
parseKeyNumbers(m_keyTimes, attr->value());
} else if (attr->name() == SVGNames::keySplinesAttr) {
m_keySplines.clear();
parseKeySplines(m_keySplines, attr->value());
} else if (attr->name() == SVGNames::fromAttr)
m_from = attr->value();
else if (attr->name() == SVGNames::toAttr)
m_to = attr->value();
else if (attr->name() == SVGNames::byAttr)
m_by = attr->value();
else if (attr->name() == SVGNames::additiveAttr) {
if (attr->value() == "sum")
m_additive = ADDITIVE_SUM;
else if (attr->value() == "replace")
m_additive = ADDITIVE_REPLACE;
} else if (attr->name() == SVGNames::accumulateAttr) {
if (attr->value() == "sum")
m_accumulate = ACCUMULATE_SUM;
else if (attr->value() == "none")
m_accumulate = ACCUMULATE_NONE;
} else {
if (SVGTests::parseMappedAttribute(attr))
return;
if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
return;
SVGElement::parseMappedAttribute(attr);
}
}
double SVGAnimationElement::parseClockValue(const String& data)
{
DeprecatedString parse = data.deprecatedString().stripWhiteSpace();
if (parse == "indefinite") // Saves some time...
return DBL_MAX;
double result;
int doublePointOne = parse.find(':');
int doublePointTwo = parse.find(':', doublePointOne + 1);
if (doublePointOne != -1 && doublePointTwo != -1) { // Spec: "Full clock values"
unsigned int hours = parse.mid(0, 2).toUInt();
unsigned int minutes = parse.mid(3, 2).toUInt();
unsigned int seconds = parse.mid(6, 2).toUInt();
unsigned int milliseconds = 0;
result = (3600 * hours) + (60 * minutes) + seconds;
if (parse.find('.') != -1) {
DeprecatedString temp = parse.mid(9, 2);
milliseconds = temp.toUInt();
result += (milliseconds * (1 / pow(10.0, int(temp.length()))));
}
} else if (doublePointOne != -1 && doublePointTwo == -1) { // Spec: "Partial clock values"
unsigned int minutes = parse.mid(0, 2).toUInt();
unsigned int seconds = parse.mid(3, 2).toUInt();
unsigned int milliseconds = 0;
result = (60 * minutes) + seconds;
if (parse.find('.') != -1) {
DeprecatedString temp = parse.mid(6, 2);
milliseconds = temp.toUInt();
result += (milliseconds * (1 / pow(10.0, int(temp.length()))));
}
} else { // Spec: "Timecount values"
int dotPosition = parse.find('.');
if (parse.endsWith("h")) {
if (dotPosition == -1)
result = parse.mid(0, parse.length() - 1).toUInt() * 3600;
else {
result = parse.mid(0, dotPosition).toUInt() * 3600;
DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 2);
result += (3600.0 * temp.toUInt()) * (1 / pow(10.0, int(temp.length())));
}
} else if (parse.endsWith("min")) {
if (dotPosition == -1)
result = parse.mid(0, parse.length() - 3).toUInt() * 60;
else {
result = parse.mid(0, dotPosition).toUInt() * 60;
DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 4);
result += (60.0 * temp.toUInt()) * (1 / pow(10.0, int(temp.length())));
}
} else if (parse.endsWith("ms")) {
if (dotPosition == -1)
result = parse.mid(0, parse.length() - 2).toUInt() / 1000.0;
else {
result = parse.mid(0, dotPosition).toUInt() / 1000.0;
DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 3);
result += (temp.toUInt() / 1000.0) * (1 / pow(10.0, int(temp.length())));
}
} else if (parse.endsWith("s")) {
if (dotPosition == -1)
result = parse.mid(0, parse.length() - 1).toUInt();
else {
result = parse.mid(0, dotPosition).toUInt();
DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 2);
result += temp.toUInt() * (1 / pow(10.0, int(temp.length())));
}
} else
result = parse.toDouble();
}
return result;
}
void SVGAnimationElement::finishParsingChildren()
{
if (ownerSVGElement())
ownerSVGElement()->timeScheduler()->addTimer(this, lround(getStartTime()));
SVGElement::finishParsingChildren();
}
String SVGAnimationElement::targetAttributeAnimatedValue() const
{
// FIXME: This method is not entirely correct
// It does not properly grab the true "animVal" instead grabs the baseVal (or something very close)
if (!targetElement())
return String();
SVGElement* target = targetElement();
SVGStyledElement* styled = 0;
if (target && target->isStyled())
styled = static_cast<SVGStyledElement*>(target);
String ret;
EAttributeType attributeType = static_cast<EAttributeType>(m_attributeType);
if (attributeType == ATTRIBUTETYPE_AUTO) {
attributeType = ATTRIBUTETYPE_XML;
// Spec: The implementation should match the attributeName to an attribute
// for the target element. The implementation must first search through the
// list of CSS properties for a matching property name, and if none is found,
// search the default XML namespace for the element.
if (styled && styled->style()) {
if (styled->style()->getPropertyCSSValue(m_attributeName))
attributeType = ATTRIBUTETYPE_CSS;
}
}
if (attributeType == ATTRIBUTETYPE_CSS) {
if (styled && styled->style())
ret = styled->style()->getPropertyValue(m_attributeName);
}
if (attributeType == ATTRIBUTETYPE_XML || ret.isEmpty())
ret = targetElement()->getAttribute(m_attributeName);
return ret;
}
void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value)
{
// FIXME: This method is not entirely correct
// It does not properly set the "animVal", rather it sets the "baseVal"
SVGAnimationElement::setTargetAttribute(targetElement(), m_attributeName, value, static_cast<EAttributeType>(m_attributeType));
}
void SVGAnimationElement::setTargetAttribute(SVGElement* target, const String& name, const String& value, EAttributeType type)
{
if (!target || name.isNull() || value.isNull())
return;
SVGStyledElement* styled = (target && target->isStyled()) ? static_cast<SVGStyledElement*>(target) : 0;
EAttributeType attributeType = type;
if (type == ATTRIBUTETYPE_AUTO) {
// Spec: The implementation should match the attributeName to an attribute
// for the target element. The implementation must first search through the
// list of CSS properties for a matching property name, and if none is found,
// search the default XML namespace for the element.
if (styled && styled->style() && styled->style()->getPropertyCSSValue(name))
attributeType = ATTRIBUTETYPE_CSS;
else
attributeType = ATTRIBUTETYPE_XML;
}
ExceptionCode ec = 0;
if (attributeType == ATTRIBUTETYPE_CSS && styled && styled->style())
styled->style()->setProperty(name, value, "", ec);
else if (attributeType == ATTRIBUTETYPE_XML)
target->setAttribute(name, value, ec);
}
String SVGAnimationElement::attributeName() const
{
return m_attributeName;
}
bool SVGAnimationElement::connectedToTimer() const
{
return m_connectedToTimer;
}
bool SVGAnimationElement::isFrozen() const
{
return (m_fill == FILL_FREEZE);
}
bool SVGAnimationElement::isAdditive() const
{
return (m_additive == ADDITIVE_SUM) || (detectAnimationMode() == BY_ANIMATION);
}
bool SVGAnimationElement::isAccumulated() const
{
return (m_accumulate == ACCUMULATE_SUM) && (detectAnimationMode() != TO_ANIMATION);
}
EAnimationMode SVGAnimationElement::detectAnimationMode() const
{
if (hasAttribute(SVGNames::valuesAttr))
return VALUES_ANIMATION;
else if ((!m_from.isEmpty() && !m_to.isEmpty()) || (!m_to.isEmpty())) { // to/from-to animation
if (!m_from.isEmpty()) // from-to animation
return FROM_TO_ANIMATION;
else
return TO_ANIMATION;
} else if ((m_from.isEmpty() && m_to.isEmpty() && !m_by.isEmpty()) ||
(!m_from.isEmpty() && !m_by.isEmpty())) { // by/from-by animation
if (!m_from.isEmpty()) // from-by animation
return FROM_BY_ANIMATION;
else
return BY_ANIMATION;
}
return NO_ANIMATION;
}
double SVGAnimationElement::repeations() const
{
return m_repetitions;
}
bool SVGAnimationElement::isIndefinite(double value)
{
return (value == DBL_MAX);
}
void SVGAnimationElement::connectTimer()
{
ASSERT(!m_connectedToTimer);
ownerSVGElement()->timeScheduler()->connectIntervalTimer(this);
m_connectedToTimer = true;
}
void SVGAnimationElement::disconnectTimer()
{
ASSERT(m_connectedToTimer);
ownerSVGElement()->timeScheduler()->disconnectIntervalTimer(this);
m_connectedToTimer = false;
}
static double calculateTimePercentage(double elapsedSeconds, double start, double end, double duration, double repetitions)
{
double percentage = 0.0;
double useElapsed = elapsedSeconds - (duration * repetitions);
if (duration > 0.0 && end == 0.0)
percentage = 1.0 - (((start + duration) - useElapsed) / duration);
else if (duration > 0.0 && end != 0.0) {
if (duration > end)
percentage = 1.0 - (((start + end) - useElapsed) / end);
else
percentage = 1.0 - (((start + duration) - useElapsed) / duration);
} else if (duration == 0.0 && end != 0.0)
percentage = 1.0 - (((start + end) - useElapsed) / end);
return percentage;
}
static inline void adjustPercentagePastForKeySplines(const Vector<SVGAnimationElement::KeySpline>& keySplines, unsigned valueIndex, float& percentagePast)
{
if (percentagePast == 0.0f) // values at key times need no spline adjustment
return;
const SVGAnimationElement::KeySpline& keySpline = keySplines[valueIndex];
Path path;
path.moveTo(FloatPoint());
path.addBezierCurveTo(keySpline.control1, keySpline.control2, FloatPoint(1.0f, 1.0f));
// FIXME: This needs to use a y-at-x function on path, to compute the y value then multiply percentagePast by that value
}
void SVGAnimationElement::valueIndexAndPercentagePastForDistance(float distancePercentage, unsigned& valueIndex, float& percentagePast)
{
// Unspecified: animation elements which do not support CALCMODE_PACED, we just always show the first value
valueIndex = 0;
percentagePast = 0;
}
float SVGAnimationElement::calculateTotalDistance()
{
return 0;
}
static inline void caculateValueIndexForKeyTimes(float timePercentage, const Vector<float>& keyTimes, unsigned& valueIndex, float& lastKeyTime, float& nextKeyTime)
{
unsigned keyTimesCountMinusOne = keyTimes.size() - 1;
valueIndex = 0;
ASSERT(timePercentage >= keyTimes.first());
while ((valueIndex < keyTimesCountMinusOne) && (timePercentage >= keyTimes[valueIndex + 1]))
valueIndex++;
lastKeyTime = keyTimes[valueIndex];
if (valueIndex < keyTimesCountMinusOne)
nextKeyTime = keyTimes[valueIndex + 1];
else
nextKeyTime = lastKeyTime;
}
bool SVGAnimationElement::isValidAnimation() const
{
EAnimationMode animationMode = detectAnimationMode();
if (!hasValidTarget() || (animationMode == NO_ANIMATION))
return false;
if (animationMode == VALUES_ANIMATION) {
if (!m_values.size())
return false;
if (m_keyTimes.size()) {
if ((m_values.size() != m_keyTimes.size()) || (m_keyTimes.first() != 0))
return false;
if (((m_calcMode == CALCMODE_SPLINE) || (m_calcMode == CALCMODE_LINEAR)) && (m_keyTimes.last() != 1))
return false;
float lastKeyTime = 0;
for (unsigned x = 0; x < m_keyTimes.size(); x++) {
if (m_keyTimes[x] < lastKeyTime || m_keyTimes[x] > 1)
return false;
}
}
if (m_keySplines.size()) {
if ((m_values.size() - 1) != m_keySplines.size())
return false;
for (unsigned x = 0; x < m_keyTimes.size(); x++)
if (m_keyTimes[x] < 0 || m_keyTimes[x] > 1)
return false;
}
}
return true;
}
void SVGAnimationElement::calculateValueIndexAndPercentagePast(float timePercentage, unsigned& valueIndex, float& percentagePast)
{
ASSERT(timePercentage <= 1.0f);
ASSERT(isValidAnimation());
EAnimationMode animationMode = detectAnimationMode();
// to-animations have their own special handling
if (animationMode == TO_ANIMATION)
return;
// paced is special, caculates values based on distance instead of time
if (m_calcMode == CALCMODE_PACED) {
float totalDistance = calculateTotalDistance();
float distancePercentage = totalDistance * timePercentage;
valueIndexAndPercentagePastForDistance(distancePercentage, valueIndex, percentagePast);
return;
}
// Figure out what our current index is based on on time
// all further calculations are based on time percentages, to allow unifying keyTimes handling & normal animation
float lastKeyTimePercentage = 0;
float nextKeyTimePercentage = 0;
if (m_keyTimes.size() && (m_keyTimes.size() == m_values.size()))
caculateValueIndexForKeyTimes(timePercentage, m_keyTimes, valueIndex, lastKeyTimePercentage, nextKeyTimePercentage);
else {
unsigned lastPossibleIndex = (m_values.size() ? m_values.size() - 1: 1);
unsigned flooredValueIndex = static_cast<unsigned>(timePercentage * lastPossibleIndex);
valueIndex = flooredValueIndex;
lastKeyTimePercentage = flooredValueIndex / (float)lastPossibleIndex;
nextKeyTimePercentage = (flooredValueIndex + 1) / (float)lastPossibleIndex;
}
// No further caculation is needed if we're exactly on an index.
if (timePercentage == lastKeyTimePercentage || lastKeyTimePercentage == nextKeyTimePercentage) {
percentagePast = 0.0f;
return;
}
// otherwise we decide what percent after that index
if ((m_calcMode == CALCMODE_SPLINE) && (m_keySplines.size() == (m_values.size() - 1)))
adjustPercentagePastForKeySplines(m_keySplines, valueIndex, percentagePast);
else if (m_calcMode == CALCMODE_DISCRETE)
percentagePast = 0.0f;
else { // default (and fallback) mode: linear
float keyTimeSpan = nextKeyTimePercentage - lastKeyTimePercentage;
float timeSinceLastKeyTime = timePercentage - lastKeyTimePercentage;
percentagePast = (timeSinceLastKeyTime / keyTimeSpan);
}
}
bool SVGAnimationElement::updateAnimationBaseValueFromElement()
{
m_baseValue = targetAttributeAnimatedValue();
return true;
}
void SVGAnimationElement::applyAnimatedValueToElement()
{
setTargetAttributeAnimatedValue(m_animatedValue);
}
void SVGAnimationElement::handleTimerEvent(double elapsedSeconds, double timePercentage)
{
timePercentage = min(timePercentage, 1.0);
if (!connectedToTimer()) {
connectTimer();
return;
}
// FIXME: accumulate="sum" will not work w/o code similar to this:
// if (isAccumulated() && repeations() != 0.0)
// accumulateForRepetitions(m_repetitions);
EAnimationMode animationMode = detectAnimationMode();
unsigned valueIndex = 0;
float percentagePast = 0;
calculateValueIndexAndPercentagePast(narrowPrecisionToFloat(timePercentage), valueIndex, percentagePast);
calculateFromAndToValues(animationMode, valueIndex);
updateAnimatedValue(animationMode, narrowPrecisionToFloat(timePercentage), valueIndex, percentagePast);
if (timePercentage == 1.0) {
if ((m_repeatCount > 0 && m_repetitions < m_repeatCount - 1) || isIndefinite(m_repeatCount)) {
m_repetitions++;
return;
} else
disconnectTimer();
}
}
bool SVGAnimationElement::updateAnimatedValueForElapsedSeconds(double elapsedSeconds)
{
// FIXME: fill="freeze" will not work without saving off the m_stoppedTime in a stop() method and having code similar to this:
// if (isStopped()) {
// if (m_fill == FILL_FREEZE)
// elapsedSeconds = m_stoppedTime;
// else
// return false;
// }
// Validate animation timing settings:
// #1 (duration > 0) -> fine
// #2 (duration <= 0.0 && end > 0) -> fine
if ((m_simpleDuration <= 0.0 && m_end <= 0.0) || (isIndefinite(m_simpleDuration) && m_end <= 0.0))
return false; // Ignore dur="0" or dur="-neg"
double percentage = calculateTimePercentage(elapsedSeconds, m_begin, m_end, m_simpleDuration, m_repetitions);
if (percentage <= 1.0 || connectedToTimer())
handleTimerEvent(elapsedSeconds, percentage);
return true; // value was updated, need to apply
}
}
// vim:ts=4:noet
#endif // ENABLE(SVG)