mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-26 03:42:50 +00:00
924 lines
31 KiB
C++
924 lines
31 KiB
C++
![]() |
/*
|
||
|
* Copyright (C) 2008 Apple Inc. All Rights Reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions
|
||
|
* are met:
|
||
|
* 1. Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*/
|
||
|
|
||
|
#include "config.h"
|
||
|
#if ENABLE(SVG_ANIMATION)
|
||
|
#include "SVGSMILElement.h"
|
||
|
|
||
|
#include "CSSPropertyNames.h"
|
||
|
#include "Document.h"
|
||
|
#include "Event.h"
|
||
|
#include "EventListener.h"
|
||
|
#include "FloatConversion.h"
|
||
|
#include "FrameView.h"
|
||
|
#include "HTMLNames.h"
|
||
|
#include "SVGNames.h"
|
||
|
#include "SVGParserUtilities.h"
|
||
|
#include "SVGSVGElement.h"
|
||
|
#include "SVGURIReference.h"
|
||
|
#include "SMILTimeContainer.h"
|
||
|
#include "XLinkNames.h"
|
||
|
#include <math.h>
|
||
|
#include <wtf/MathExtras.h>
|
||
|
#include <wtf/Vector.h>
|
||
|
|
||
|
using namespace std;
|
||
|
|
||
|
namespace WebCore {
|
||
|
|
||
|
// This is used for duration type time values that can't be negative.
|
||
|
static const double invalidCachedTime = -1.;
|
||
|
|
||
|
class ConditionEventListener : public EventListener {
|
||
|
public:
|
||
|
ConditionEventListener(SVGSMILElement* animation, Element* eventBase, SVGSMILElement::Condition* condition)
|
||
|
: m_animation(animation)
|
||
|
, m_condition(condition)
|
||
|
, m_eventBase(eventBase)
|
||
|
{
|
||
|
m_eventBase->addEventListener(m_condition->m_name, this, false);
|
||
|
}
|
||
|
|
||
|
void unregister()
|
||
|
{
|
||
|
// If this has only one ref then the event base is dead already and we don't need to remove ourself.
|
||
|
if (!hasOneRef())
|
||
|
m_eventBase->removeEventListener(m_condition->m_name, this, false);
|
||
|
}
|
||
|
|
||
|
virtual void handleEvent(Event* event, bool isWindowEvent)
|
||
|
{
|
||
|
m_animation->handleConditionEvent(event, m_condition);
|
||
|
}
|
||
|
private:
|
||
|
SVGSMILElement* m_animation;
|
||
|
SVGSMILElement::Condition* m_condition;
|
||
|
Element* m_eventBase;
|
||
|
};
|
||
|
|
||
|
SVGSMILElement::Condition::Condition(Type type, BeginOrEnd beginOrEnd, const String& baseID, const String& name, SMILTime offset, int repeats)
|
||
|
: m_type(type)
|
||
|
, m_beginOrEnd(beginOrEnd)
|
||
|
, m_baseID(baseID)
|
||
|
, m_name(name)
|
||
|
, m_offset(offset)
|
||
|
, m_repeats(repeats)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
SVGSMILElement::SVGSMILElement(const QualifiedName& tagName, Document* doc)
|
||
|
: SVGElement(tagName, doc)
|
||
|
, m_conditionsConnected(false)
|
||
|
, m_hasEndEventConditions(false)
|
||
|
, m_intervalBegin(SMILTime::unresolved())
|
||
|
, m_intervalEnd(SMILTime::unresolved())
|
||
|
, m_previousIntervalBegin(SMILTime::unresolved())
|
||
|
, m_isWaitingForFirstInterval(true)
|
||
|
, m_activeState(Inactive)
|
||
|
, m_lastPercent(0)
|
||
|
, m_lastRepeat(0)
|
||
|
, m_nextProgressTime(0)
|
||
|
, m_documentOrderIndex(0)
|
||
|
, m_cachedDur(invalidCachedTime)
|
||
|
, m_cachedRepeatDur(invalidCachedTime)
|
||
|
, m_cachedRepeatCount(invalidCachedTime)
|
||
|
, m_cachedMin(invalidCachedTime)
|
||
|
, m_cachedMax(invalidCachedTime)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
SVGSMILElement::~SVGSMILElement()
|
||
|
{
|
||
|
disconnectConditions();
|
||
|
if (m_timeContainer)
|
||
|
m_timeContainer->unschedule(this);
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::insertedIntoDocument()
|
||
|
{
|
||
|
SVGElement::insertedIntoDocument();
|
||
|
#ifndef NDEBUG
|
||
|
// Verify we are not in <use> instance tree.
|
||
|
for (Node* n = this; n; n = n->parent())
|
||
|
ASSERT(!n->isShadowNode());
|
||
|
#endif
|
||
|
SVGSVGElement* owner = ownerSVGElement();
|
||
|
if (!owner)
|
||
|
return;
|
||
|
m_timeContainer = owner->timeContainer();
|
||
|
ASSERT(m_timeContainer);
|
||
|
m_timeContainer->setDocumentOrderIndexesDirty();
|
||
|
reschedule();
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::removedFromDocument()
|
||
|
{
|
||
|
if (m_timeContainer) {
|
||
|
m_timeContainer->unschedule(this);
|
||
|
m_timeContainer = 0;
|
||
|
}
|
||
|
// Calling disconnectConditions() may kill us if there are syncbase conditions.
|
||
|
// OK, but we don't want to die inside the call.
|
||
|
RefPtr<SVGSMILElement> keepAlive(this);
|
||
|
disconnectConditions();
|
||
|
SVGElement::removedFromDocument();
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::finishParsingChildren()
|
||
|
{
|
||
|
SVGElement::finishParsingChildren();
|
||
|
|
||
|
// "If no attribute is present, the default begin value (an offset-value of 0) must be evaluated."
|
||
|
if (!hasAttribute(SVGNames::beginAttr))
|
||
|
m_beginTimes.append(0);
|
||
|
|
||
|
if (m_isWaitingForFirstInterval) {
|
||
|
resolveFirstInterval();
|
||
|
reschedule();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::parseOffsetValue(const String& data)
|
||
|
{
|
||
|
bool ok;
|
||
|
double result = 0;
|
||
|
String parse = data.stripWhiteSpace();
|
||
|
if (parse.endsWith("h"))
|
||
|
result = parse.left(parse.length() - 1).toDouble(&ok) * 60 * 60;
|
||
|
else if (parse.endsWith("min"))
|
||
|
result = parse.left(parse.length() - 3).toDouble(&ok) * 60;
|
||
|
else if (parse.endsWith("ms"))
|
||
|
result = parse.left(parse.length() - 2).toDouble(&ok) / 1000;
|
||
|
else if (parse.endsWith("s"))
|
||
|
result = parse.left(parse.length() - 1).toDouble(&ok);
|
||
|
else
|
||
|
result = parse.toDouble(&ok);
|
||
|
if (!ok)
|
||
|
return SMILTime::unresolved();
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::parseClockValue(const String& data)
|
||
|
{
|
||
|
if (data.isNull())
|
||
|
return SMILTime::unresolved();
|
||
|
|
||
|
String parse = data.stripWhiteSpace();
|
||
|
|
||
|
static const AtomicString indefiniteValue("indefinite");
|
||
|
if (parse == indefiniteValue)
|
||
|
return SMILTime::indefinite();
|
||
|
|
||
|
double result = 0;
|
||
|
bool ok;
|
||
|
int doublePointOne = parse.find(':');
|
||
|
int doublePointTwo = parse.find(':', doublePointOne + 1);
|
||
|
if (doublePointOne == 2 && doublePointTwo == 5 && parse.length() >= 8) {
|
||
|
result += parse.substring(0, 2).toUIntStrict(&ok) * 60 * 60;
|
||
|
if (!ok)
|
||
|
return SMILTime::unresolved();
|
||
|
result += parse.substring(3, 2).toUIntStrict(&ok) * 60;
|
||
|
if (!ok)
|
||
|
return SMILTime::unresolved();
|
||
|
result += parse.substring(6).toDouble(&ok);
|
||
|
} else if (doublePointOne == 2 && doublePointTwo == -1 && parse.length() >= 5) {
|
||
|
result += parse.substring(0, 2).toUIntStrict(&ok) * 60;
|
||
|
if (!ok)
|
||
|
return SMILTime::unresolved();
|
||
|
result += parse.substring(3).toDouble(&ok);
|
||
|
} else
|
||
|
return parseOffsetValue(parse);
|
||
|
|
||
|
if (!ok)
|
||
|
return SMILTime::unresolved();
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static void sortTimeList(Vector<SMILTime>& timeList)
|
||
|
{
|
||
|
std::sort(timeList.begin(), timeList.end());
|
||
|
}
|
||
|
|
||
|
bool SVGSMILElement::parseCondition(const String& value, BeginOrEnd beginOrEnd)
|
||
|
{
|
||
|
String parseString = value.stripWhiteSpace();
|
||
|
|
||
|
double sign = 1.;
|
||
|
bool ok;
|
||
|
int pos = parseString.find('+');
|
||
|
if (pos == -1) {
|
||
|
pos = parseString.find('-');
|
||
|
if (pos != -1)
|
||
|
sign = -1.;
|
||
|
}
|
||
|
String conditionString;
|
||
|
SMILTime offset = 0;
|
||
|
if (pos == -1)
|
||
|
conditionString = parseString;
|
||
|
else {
|
||
|
conditionString = parseString.left(pos).stripWhiteSpace();
|
||
|
String offsetString = parseString.substring(pos + 1).stripWhiteSpace();
|
||
|
offset = parseOffsetValue(offsetString);
|
||
|
if (offset.isUnresolved())
|
||
|
return false;
|
||
|
offset = offset * sign;
|
||
|
}
|
||
|
if (conditionString.isEmpty())
|
||
|
return false;
|
||
|
pos = conditionString.find('.');
|
||
|
|
||
|
String baseID;
|
||
|
String nameString;
|
||
|
if (pos == -1)
|
||
|
nameString = conditionString;
|
||
|
else {
|
||
|
baseID = conditionString.left(pos);
|
||
|
nameString = conditionString.substring(pos + 1);
|
||
|
}
|
||
|
if (nameString.isEmpty())
|
||
|
return false;
|
||
|
|
||
|
Condition::Type type;
|
||
|
int repeats = -1;
|
||
|
if (nameString.startsWith("repeat(") && nameString.endsWith(")")) {
|
||
|
// FIXME: For repeat events we just need to add the data carrying TimeEvent class and
|
||
|
// fire the events at appropriate times.
|
||
|
repeats = nameString.substring(7, nameString.length() - 8).toUIntStrict(&ok);
|
||
|
if (!ok)
|
||
|
return false;
|
||
|
nameString = "repeat";
|
||
|
type = Condition::EventBase;
|
||
|
} else if (nameString == "begin" || nameString == "end") {
|
||
|
if (baseID.isEmpty())
|
||
|
return false;
|
||
|
type = Condition::Syncbase;
|
||
|
} else if (nameString.startsWith("accesskey(")) {
|
||
|
// FIXME: accesskey() support.
|
||
|
type = Condition::AccessKey;
|
||
|
} else
|
||
|
type = Condition::EventBase;
|
||
|
|
||
|
m_conditions.append(Condition(type, beginOrEnd, baseID, nameString, offset, repeats));
|
||
|
|
||
|
if (type == Condition::EventBase && beginOrEnd == End)
|
||
|
m_hasEndEventConditions = true;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool SVGSMILElement::isSMILElement(Node* node)
|
||
|
{
|
||
|
if (!node)
|
||
|
return false;
|
||
|
return node->hasTagName(SVGNames::setTag) || node->hasTagName(SVGNames::animateTag) || node->hasTagName(SVGNames::animateMotionTag)
|
||
|
|| node->hasTagName(SVGNames::animateTransformTag) || node->hasTagName(SVGNames::animateColorTag);
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::parseBeginOrEnd(const String& parseString, BeginOrEnd beginOrEnd)
|
||
|
{
|
||
|
Vector<SMILTime>& timeList = beginOrEnd == Begin ? m_beginTimes : m_endTimes;
|
||
|
if (beginOrEnd == End)
|
||
|
m_hasEndEventConditions = false;
|
||
|
HashSet<double> existing;
|
||
|
for (unsigned n = 0; n < timeList.size(); ++n)
|
||
|
existing.add(timeList[n].value());
|
||
|
Vector<String> splitString;
|
||
|
parseString.split(';', splitString);
|
||
|
for (unsigned n = 0; n < splitString.size(); ++n) {
|
||
|
SMILTime value = parseClockValue(splitString[n]);
|
||
|
if (value.isUnresolved())
|
||
|
parseCondition(splitString[n], beginOrEnd);
|
||
|
else if (!existing.contains(value.value()))
|
||
|
timeList.append(value);
|
||
|
}
|
||
|
sortTimeList(timeList);
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::parseMappedAttribute(MappedAttribute* attr)
|
||
|
{
|
||
|
if (attr->name() == SVGNames::beginAttr) {
|
||
|
if (!m_conditions.isEmpty()) {
|
||
|
disconnectConditions();
|
||
|
m_conditions.clear();
|
||
|
parseBeginOrEnd(getAttribute(SVGNames::endAttr), End);
|
||
|
}
|
||
|
parseBeginOrEnd(attr->value().string(), Begin);
|
||
|
if (inDocument())
|
||
|
connectConditions();
|
||
|
} else if (attr->name() == SVGNames::endAttr) {
|
||
|
if (!m_conditions.isEmpty()) {
|
||
|
disconnectConditions();
|
||
|
m_conditions.clear();
|
||
|
parseBeginOrEnd(getAttribute(SVGNames::beginAttr), Begin);
|
||
|
}
|
||
|
parseBeginOrEnd(attr->value().string(), End);
|
||
|
if (inDocument())
|
||
|
connectConditions();
|
||
|
} else
|
||
|
SVGElement::parseMappedAttribute(attr);
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::attributeChanged(Attribute* attr, bool preserveDecls)
|
||
|
{
|
||
|
SVGElement::attributeChanged(attr, preserveDecls);
|
||
|
|
||
|
const QualifiedName& attrName = attr->name();
|
||
|
if (attrName == SVGNames::durAttr)
|
||
|
m_cachedDur = invalidCachedTime;
|
||
|
else if (attrName == SVGNames::repeatDurAttr)
|
||
|
m_cachedRepeatDur = invalidCachedTime;
|
||
|
else if (attrName == SVGNames::repeatCountAttr)
|
||
|
m_cachedRepeatCount = invalidCachedTime;
|
||
|
else if (attrName == SVGNames::minAttr)
|
||
|
m_cachedMin = invalidCachedTime;
|
||
|
else if (attrName == SVGNames::maxAttr)
|
||
|
m_cachedMax = invalidCachedTime;
|
||
|
|
||
|
if (inDocument()) {
|
||
|
if (attrName == SVGNames::beginAttr)
|
||
|
beginListChanged();
|
||
|
else if (attrName == SVGNames::endAttr)
|
||
|
endListChanged();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::connectConditions()
|
||
|
{
|
||
|
if (m_conditionsConnected)
|
||
|
disconnectConditions();
|
||
|
m_conditionsConnected = true;
|
||
|
for (unsigned n = 0; n < m_conditions.size(); ++n) {
|
||
|
Condition& condition = m_conditions[n];
|
||
|
if (condition.m_type == Condition::EventBase) {
|
||
|
ASSERT(!condition.m_syncbase);
|
||
|
Element* eventBase = condition.m_baseID.isEmpty() ? targetElement() : document()->getElementById(condition.m_baseID);
|
||
|
if (!eventBase)
|
||
|
continue;
|
||
|
ASSERT(!condition.m_eventListener);
|
||
|
condition.m_eventListener = new ConditionEventListener(this, eventBase, &condition);
|
||
|
} else if (condition.m_type == Condition::Syncbase) {
|
||
|
ASSERT(!condition.m_baseID.isEmpty());
|
||
|
condition.m_syncbase = document()->getElementById(condition.m_baseID);
|
||
|
if (!isSMILElement(condition.m_syncbase.get())) {
|
||
|
condition.m_syncbase = 0;
|
||
|
continue;
|
||
|
}
|
||
|
SVGSMILElement* syncbase = static_cast<SVGSMILElement*>(condition.m_syncbase.get());
|
||
|
syncbase->addTimeDependent(this);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::disconnectConditions()
|
||
|
{
|
||
|
if (!m_conditionsConnected)
|
||
|
return;
|
||
|
m_conditionsConnected = false;
|
||
|
for (unsigned n = 0; n < m_conditions.size(); ++n) {
|
||
|
Condition& condition = m_conditions[n];
|
||
|
if (condition.m_type == Condition::EventBase) {
|
||
|
ASSERT(!condition.m_syncbase);
|
||
|
if (condition.m_eventListener) {
|
||
|
condition.m_eventListener->unregister();
|
||
|
condition.m_eventListener = 0;
|
||
|
}
|
||
|
} else if (condition.m_type == Condition::Syncbase) {
|
||
|
if (condition.m_syncbase) {
|
||
|
ASSERT(isSMILElement(condition.m_syncbase.get()));
|
||
|
static_cast<SVGSMILElement*>(condition.m_syncbase.get())->removeTimeDependent(this);
|
||
|
}
|
||
|
}
|
||
|
condition.m_syncbase = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::reschedule()
|
||
|
{
|
||
|
if (m_timeContainer)
|
||
|
m_timeContainer->schedule(this);
|
||
|
}
|
||
|
|
||
|
SVGElement* SVGSMILElement::targetElement() const
|
||
|
{
|
||
|
String href = xlinkHref();
|
||
|
Node* target = href.isEmpty() ? parentNode() : document()->getElementById(SVGURIReference::getTarget(href));
|
||
|
if (target && target->isSVGElement())
|
||
|
return static_cast<SVGElement*>(target);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
String SVGSMILElement::attributeName() const
|
||
|
{
|
||
|
return getAttribute(SVGNames::attributeNameAttr).string().stripWhiteSpace();
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::elapsed() const
|
||
|
{
|
||
|
return m_timeContainer ? m_timeContainer->elapsed() : 0;
|
||
|
}
|
||
|
|
||
|
bool SVGSMILElement::isInactive() const
|
||
|
{
|
||
|
return m_activeState == Inactive;
|
||
|
}
|
||
|
|
||
|
bool SVGSMILElement::isFrozen() const
|
||
|
{
|
||
|
return m_activeState == Frozen;
|
||
|
}
|
||
|
|
||
|
SVGSMILElement::Restart SVGSMILElement::restart() const
|
||
|
{
|
||
|
static const AtomicString never("never");
|
||
|
static const AtomicString whenNotActive("whenNotActive");
|
||
|
const AtomicString& value = getAttribute(SVGNames::restartAttr);
|
||
|
if (value == never)
|
||
|
return RestartNever;
|
||
|
if (value == whenNotActive)
|
||
|
return RestartWhenNotActive;
|
||
|
return RestartAlways;
|
||
|
}
|
||
|
|
||
|
SVGSMILElement::FillMode SVGSMILElement::fill() const
|
||
|
{
|
||
|
static const AtomicString freeze("freeze");
|
||
|
const AtomicString& value = getAttribute(SVGNames::fillAttr);
|
||
|
return value == freeze ? FillFreeze : FillRemove;
|
||
|
}
|
||
|
|
||
|
String SVGSMILElement::xlinkHref() const
|
||
|
{
|
||
|
return getAttribute(XLinkNames::hrefAttr);
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::dur() const
|
||
|
{
|
||
|
if (m_cachedDur != invalidCachedTime)
|
||
|
return m_cachedDur;
|
||
|
const AtomicString& value = getAttribute(SVGNames::durAttr);
|
||
|
SMILTime clockValue = parseClockValue(value);
|
||
|
return m_cachedDur = clockValue <= 0 ? SMILTime::unresolved() : clockValue;
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::repeatDur() const
|
||
|
{
|
||
|
if (m_cachedRepeatDur != invalidCachedTime)
|
||
|
return m_cachedRepeatDur;
|
||
|
const AtomicString& value = getAttribute(SVGNames::repeatDurAttr);
|
||
|
SMILTime clockValue = parseClockValue(value);
|
||
|
return m_cachedRepeatDur = clockValue < 0 ? SMILTime::unresolved() : clockValue;
|
||
|
}
|
||
|
|
||
|
// So a count is not really a time but let just all pretend we did not notice.
|
||
|
SMILTime SVGSMILElement::repeatCount() const
|
||
|
{
|
||
|
if (m_cachedRepeatCount != invalidCachedTime)
|
||
|
return m_cachedRepeatCount;
|
||
|
const AtomicString& value = getAttribute(SVGNames::repeatCountAttr);
|
||
|
if (value.isNull())
|
||
|
return SMILTime::unresolved();
|
||
|
|
||
|
static const AtomicString indefiniteValue("indefinite");
|
||
|
if (value == indefiniteValue)
|
||
|
return SMILTime::indefinite();
|
||
|
bool ok;
|
||
|
double result = value.string().toDouble(&ok);
|
||
|
return m_cachedRepeatCount = ok && result > 0 ? result : SMILTime::unresolved();
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::maxValue() const
|
||
|
{
|
||
|
if (m_cachedMax != invalidCachedTime)
|
||
|
return m_cachedMax;
|
||
|
const AtomicString& value = getAttribute(SVGNames::maxAttr);
|
||
|
SMILTime result = parseClockValue(value);
|
||
|
return m_cachedMax = (result.isUnresolved() || result < 0) ? SMILTime::indefinite() : result;
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::minValue() const
|
||
|
{
|
||
|
if (m_cachedMin != invalidCachedTime)
|
||
|
return m_cachedMin;
|
||
|
const AtomicString& value = getAttribute(SVGNames::minAttr);
|
||
|
SMILTime result = parseClockValue(value);
|
||
|
return m_cachedMin = (result.isUnresolved() || result < 0) ? 0 : result;
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::simpleDuration() const
|
||
|
{
|
||
|
return min(dur(), SMILTime::indefinite());
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::addBeginTime(SMILTime time)
|
||
|
{
|
||
|
m_beginTimes.append(time);
|
||
|
sortTimeList(m_beginTimes);
|
||
|
beginListChanged();
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::addEndTime(SMILTime time)
|
||
|
{
|
||
|
m_endTimes.append(time);
|
||
|
sortTimeList(m_endTimes);
|
||
|
endListChanged();
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::findInstanceTime(BeginOrEnd beginOrEnd, SMILTime minimumTime, bool equalsMinimumOK) const
|
||
|
{
|
||
|
// FIXME: This searches from the beginning which is inefficient. The list is usually not long
|
||
|
// (one entry in common cases) but you can construct a case where it does grow.
|
||
|
const Vector<SMILTime>& list = beginOrEnd == Begin ? m_beginTimes : m_endTimes;
|
||
|
for (unsigned n = 0; n < list.size(); ++n) {
|
||
|
SMILTime time = list[n];
|
||
|
ASSERT(!time.isUnresolved());
|
||
|
if (time.isIndefinite() && beginOrEnd == Begin) {
|
||
|
// "The special value "indefinite" does not yield an instance time in the begin list."
|
||
|
continue;
|
||
|
}
|
||
|
if (equalsMinimumOK) {
|
||
|
if (time >= minimumTime)
|
||
|
return time;
|
||
|
} else if (time > minimumTime)
|
||
|
return time;
|
||
|
}
|
||
|
return SMILTime::unresolved();
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::repeatingDuration() const
|
||
|
{
|
||
|
// Computing the active duration
|
||
|
// http://www.w3.org/TR/SMIL2/smil-timing.html#Timing-ComputingActiveDur
|
||
|
SMILTime repeatCount = this->repeatCount();
|
||
|
SMILTime repeatDur = this->repeatDur();
|
||
|
SMILTime simpleDuration = this->simpleDuration();
|
||
|
if (simpleDuration == 0 || (repeatDur.isUnresolved() && repeatCount.isUnresolved()))
|
||
|
return simpleDuration;
|
||
|
SMILTime repeatCountDuration = simpleDuration * repeatCount;
|
||
|
return min(repeatCountDuration, min(repeatDur, SMILTime::indefinite()));
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::resolveActiveEnd(SMILTime resolvedBegin, SMILTime resolvedEnd) const
|
||
|
{
|
||
|
// Computing the active duration
|
||
|
// http://www.w3.org/TR/SMIL2/smil-timing.html#Timing-ComputingActiveDur
|
||
|
SMILTime preliminaryActiveDuration;
|
||
|
if (!resolvedEnd.isUnresolved() && dur().isUnresolved() && repeatDur().isUnresolved() && repeatCount().isUnresolved())
|
||
|
preliminaryActiveDuration = resolvedEnd - resolvedBegin;
|
||
|
else if (!resolvedEnd.isFinite())
|
||
|
preliminaryActiveDuration = repeatingDuration();
|
||
|
else
|
||
|
preliminaryActiveDuration = min(repeatingDuration(), resolvedEnd - resolvedBegin);
|
||
|
|
||
|
SMILTime minValue = this->minValue();
|
||
|
SMILTime maxValue = this->maxValue();
|
||
|
if (minValue > maxValue) {
|
||
|
// Ignore both.
|
||
|
// http://www.w3.org/TR/2001/REC-smil-animation-20010904/#MinMax
|
||
|
minValue = 0;
|
||
|
maxValue = SMILTime::indefinite();
|
||
|
}
|
||
|
return resolvedBegin + min(maxValue, max(minValue, preliminaryActiveDuration));
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::resolveInterval(bool first, SMILTime& beginResult, SMILTime& endResult) const
|
||
|
{
|
||
|
// See the pseudocode in
|
||
|
// http://www.w3.org/TR/2001/REC-smil-animation-20010904/#Timing-BeginEnd-LifeCycle
|
||
|
SMILTime beginAfter = first ? -numeric_limits<double>::infinity() : m_intervalEnd;
|
||
|
SMILTime lastIntervalTempEnd = numeric_limits<double>::infinity();
|
||
|
while (true) {
|
||
|
SMILTime tempBegin = findInstanceTime(Begin, beginAfter, true);
|
||
|
if (tempBegin.isUnresolved())
|
||
|
break;
|
||
|
SMILTime tempEnd;
|
||
|
if (m_endTimes.isEmpty())
|
||
|
tempEnd = resolveActiveEnd(tempBegin, SMILTime::indefinite());
|
||
|
else {
|
||
|
tempEnd = findInstanceTime(End, tempBegin, true);
|
||
|
if ((first && tempBegin == tempEnd && tempEnd == lastIntervalTempEnd) || (!first && tempEnd == m_intervalEnd))
|
||
|
tempEnd = findInstanceTime(End, tempBegin, false);
|
||
|
if (tempEnd.isUnresolved()) {
|
||
|
if (!m_endTimes.isEmpty() && !m_hasEndEventConditions)
|
||
|
break;
|
||
|
}
|
||
|
tempEnd = resolveActiveEnd(tempBegin, tempEnd);
|
||
|
}
|
||
|
if (tempEnd > 0 || !first) {
|
||
|
beginResult = tempBegin;
|
||
|
endResult = tempEnd;
|
||
|
return;
|
||
|
} else if (restart() == RestartNever)
|
||
|
break;
|
||
|
else
|
||
|
beginAfter = tempEnd;
|
||
|
lastIntervalTempEnd = tempEnd;
|
||
|
}
|
||
|
beginResult = SMILTime::unresolved();
|
||
|
endResult = SMILTime::unresolved();
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::resolveFirstInterval()
|
||
|
{
|
||
|
SMILTime begin;
|
||
|
SMILTime end;
|
||
|
resolveInterval(true, begin, end);
|
||
|
ASSERT(!begin.isIndefinite());
|
||
|
|
||
|
if (!begin.isUnresolved() && (begin != m_intervalBegin || end != m_intervalEnd)) {
|
||
|
bool wasUnresolved = m_intervalBegin.isUnresolved();
|
||
|
m_intervalBegin = begin;
|
||
|
m_intervalEnd = end;
|
||
|
notifyDependentsIntervalChanged(wasUnresolved ? NewInterval : ExistingInterval);
|
||
|
m_nextProgressTime = min(m_nextProgressTime, m_intervalBegin);
|
||
|
reschedule();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::resolveNextInterval()
|
||
|
{
|
||
|
SMILTime begin;
|
||
|
SMILTime end;
|
||
|
resolveInterval(false, begin, end);
|
||
|
ASSERT(!begin.isIndefinite());
|
||
|
|
||
|
if (!begin.isUnresolved() && begin != m_intervalBegin) {
|
||
|
m_intervalBegin = begin;
|
||
|
m_intervalEnd = end;
|
||
|
notifyDependentsIntervalChanged(NewInterval);
|
||
|
m_nextProgressTime = min(m_nextProgressTime, m_intervalBegin);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::nextProgressTime() const
|
||
|
{
|
||
|
return m_nextProgressTime;
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::beginListChanged()
|
||
|
{
|
||
|
SMILTime elapsed = this->elapsed();
|
||
|
if (m_isWaitingForFirstInterval)
|
||
|
resolveFirstInterval();
|
||
|
else if (elapsed < m_intervalBegin) {
|
||
|
SMILTime newBegin = findInstanceTime(Begin, elapsed, false);
|
||
|
if (newBegin < m_intervalBegin) {
|
||
|
// Begin time changed, re-resolve the interval.
|
||
|
SMILTime oldBegin = m_intervalBegin;
|
||
|
m_intervalBegin = elapsed;
|
||
|
resolveInterval(false, m_intervalBegin, m_intervalEnd);
|
||
|
ASSERT(!m_intervalBegin.isUnresolved());
|
||
|
if (m_intervalBegin != oldBegin)
|
||
|
notifyDependentsIntervalChanged(ExistingInterval);
|
||
|
}
|
||
|
}
|
||
|
m_nextProgressTime = elapsed;
|
||
|
reschedule();
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::endListChanged()
|
||
|
{
|
||
|
SMILTime elapsed = this->elapsed();
|
||
|
if (m_isWaitingForFirstInterval)
|
||
|
resolveFirstInterval();
|
||
|
else if (elapsed < m_intervalEnd && m_intervalBegin.isFinite()) {
|
||
|
SMILTime newEnd = findInstanceTime(End, m_intervalBegin, false);
|
||
|
if (newEnd < m_intervalEnd) {
|
||
|
newEnd = resolveActiveEnd(m_intervalBegin, newEnd);
|
||
|
if (newEnd != m_intervalEnd) {
|
||
|
m_intervalEnd = newEnd;
|
||
|
notifyDependentsIntervalChanged(ExistingInterval);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
m_nextProgressTime = elapsed;
|
||
|
reschedule();
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::checkRestart(SMILTime elapsed)
|
||
|
{
|
||
|
ASSERT(!m_isWaitingForFirstInterval);
|
||
|
ASSERT(elapsed >= m_intervalBegin);
|
||
|
|
||
|
Restart restart = this->restart();
|
||
|
if (restart == RestartNever)
|
||
|
return;
|
||
|
|
||
|
if (elapsed < m_intervalEnd) {
|
||
|
if (restart != RestartAlways)
|
||
|
return;
|
||
|
SMILTime nextBegin = findInstanceTime(Begin, m_intervalBegin, false);
|
||
|
if (nextBegin < m_intervalEnd) {
|
||
|
m_intervalEnd = nextBegin;
|
||
|
notifyDependentsIntervalChanged(ExistingInterval);
|
||
|
}
|
||
|
}
|
||
|
if (elapsed >= m_intervalEnd)
|
||
|
resolveNextInterval();
|
||
|
}
|
||
|
|
||
|
float SVGSMILElement::calculateAnimationPercentAndRepeat(SMILTime elapsed, unsigned& repeat) const
|
||
|
{
|
||
|
SMILTime simpleDuration = this->simpleDuration();
|
||
|
repeat = 0;
|
||
|
if (simpleDuration.isIndefinite()) {
|
||
|
repeat = 0;
|
||
|
return 0.f;
|
||
|
}
|
||
|
if (simpleDuration == 0) {
|
||
|
repeat = 0;
|
||
|
return 1.f;
|
||
|
}
|
||
|
ASSERT(m_intervalBegin.isFinite());
|
||
|
ASSERT(simpleDuration.isFinite());
|
||
|
SMILTime activeTime = elapsed - m_intervalBegin;
|
||
|
SMILTime repeatingDuration = this->repeatingDuration();
|
||
|
if (elapsed >= m_intervalEnd || activeTime > repeatingDuration) {
|
||
|
repeat = static_cast<unsigned>(repeatingDuration.value() / simpleDuration.value());
|
||
|
if (fmod(repeatingDuration.value(), simpleDuration.value() == 0.))
|
||
|
repeat--;
|
||
|
return 1.f;
|
||
|
}
|
||
|
repeat = static_cast<unsigned>(activeTime.value() / simpleDuration.value());
|
||
|
SMILTime simpleTime = fmod(activeTime.value(), simpleDuration.value());
|
||
|
return narrowPrecisionToFloat(simpleTime.value() / simpleDuration.value());
|
||
|
}
|
||
|
|
||
|
SMILTime SVGSMILElement::calculateNextProgressTime(SMILTime elapsed) const
|
||
|
{
|
||
|
if (m_activeState == Active) {
|
||
|
// If duration is indefinite the value does not actually change over time. Same is true for <set>.
|
||
|
SMILTime simpleDuration = this->simpleDuration();
|
||
|
if (simpleDuration.isIndefinite() || hasTagName(SVGNames::setTag)) {
|
||
|
SMILTime repeatCount = this->repeatCount();
|
||
|
SMILTime repeatingDurationEnd = m_intervalBegin + repeatingDuration();
|
||
|
// We are supposed to do freeze semantics when repeating ends, even if the element is still active.
|
||
|
// Take care that we get a timer callback at that point.
|
||
|
if (elapsed < repeatingDurationEnd && repeatingDurationEnd < m_intervalEnd && repeatingDurationEnd.isFinite())
|
||
|
return repeatingDurationEnd;
|
||
|
return m_intervalEnd;
|
||
|
}
|
||
|
return elapsed + 0.025;
|
||
|
}
|
||
|
return m_intervalBegin >= elapsed ? m_intervalBegin : SMILTime::unresolved();
|
||
|
}
|
||
|
|
||
|
SVGSMILElement::ActiveState SVGSMILElement::determineActiveState(SMILTime elapsed) const
|
||
|
{
|
||
|
if (elapsed >= m_intervalBegin && elapsed < m_intervalEnd)
|
||
|
return Active;
|
||
|
|
||
|
if (m_activeState == Active)
|
||
|
return fill() == FillFreeze ? Frozen : Inactive;
|
||
|
|
||
|
return m_activeState;
|
||
|
}
|
||
|
|
||
|
bool SVGSMILElement::isContributing(SMILTime elapsed) const
|
||
|
{
|
||
|
// Animation does not contribute during the active time if it is past its repeating duration and has fill=remove.
|
||
|
return (m_activeState == Active && (fill() == FillFreeze || elapsed <= m_intervalBegin + repeatingDuration())) || m_activeState == Frozen;
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::progress(SMILTime elapsed, SVGSMILElement* resultElement)
|
||
|
{
|
||
|
ASSERT(m_timeContainer);
|
||
|
ASSERT(m_isWaitingForFirstInterval || m_intervalBegin.isFinite());
|
||
|
|
||
|
if (!m_conditionsConnected)
|
||
|
connectConditions();
|
||
|
|
||
|
if (!m_intervalBegin.isFinite()) {
|
||
|
ASSERT(m_activeState == Inactive);
|
||
|
m_nextProgressTime = SMILTime::unresolved();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (elapsed < m_intervalBegin) {
|
||
|
ASSERT(m_activeState != Active);
|
||
|
if (m_activeState == Frozen && resultElement)
|
||
|
updateAnimation(m_lastPercent, m_lastRepeat, resultElement);
|
||
|
m_nextProgressTime = m_intervalBegin;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_previousIntervalBegin = m_intervalBegin;
|
||
|
|
||
|
if (m_activeState == Inactive) {
|
||
|
m_isWaitingForFirstInterval = false;
|
||
|
m_activeState = Active;
|
||
|
startedActiveInterval();
|
||
|
}
|
||
|
|
||
|
unsigned repeat;
|
||
|
float percent = calculateAnimationPercentAndRepeat(elapsed, repeat);
|
||
|
|
||
|
checkRestart(elapsed);
|
||
|
|
||
|
ActiveState oldActiveState = m_activeState;
|
||
|
m_activeState = determineActiveState(elapsed);
|
||
|
|
||
|
if (isContributing(elapsed)) {
|
||
|
if (resultElement)
|
||
|
updateAnimation(percent, repeat, resultElement);
|
||
|
m_lastPercent = percent;
|
||
|
m_lastRepeat = repeat;
|
||
|
}
|
||
|
|
||
|
if (oldActiveState == Active && m_activeState != Active)
|
||
|
endedActiveInterval();
|
||
|
|
||
|
m_nextProgressTime = calculateNextProgressTime(elapsed);
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::notifyDependentsIntervalChanged(NewOrExistingInterval newOrExisting)
|
||
|
{
|
||
|
ASSERT(m_intervalBegin.isFinite());
|
||
|
static HashSet<SVGSMILElement*> loopBreaker;
|
||
|
if (loopBreaker.contains(this))
|
||
|
return;
|
||
|
loopBreaker.add(this);
|
||
|
|
||
|
TimeDependentSet::iterator end = m_timeDependents.end();
|
||
|
for (TimeDependentSet::iterator it = m_timeDependents.begin(); it != end; ++it) {
|
||
|
SVGSMILElement* dependent = *it;
|
||
|
dependent->createInstanceTimesFromSyncbase(this, newOrExisting);
|
||
|
}
|
||
|
|
||
|
loopBreaker.remove(this);
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::createInstanceTimesFromSyncbase(SVGSMILElement* syncbase, NewOrExistingInterval newOrExisting)
|
||
|
{
|
||
|
// FIXME: To be really correct, this should handle updating exising interval by changing
|
||
|
// the associated times instead of creating new ones.
|
||
|
for (unsigned n = 0; n < m_conditions.size(); ++n) {
|
||
|
Condition& condition = m_conditions[n];
|
||
|
if (condition.m_type == Condition::Syncbase && condition.m_syncbase == syncbase) {
|
||
|
ASSERT(condition.m_name == "begin" || condition.m_name == "end");
|
||
|
// No nested time containers in SVG, no need for crazy time space conversions. Phew!
|
||
|
SMILTime time = 0;
|
||
|
if (condition.m_name == "begin")
|
||
|
time = syncbase->m_intervalBegin + condition.m_offset;
|
||
|
else
|
||
|
time = syncbase->m_intervalEnd + condition.m_offset;
|
||
|
ASSERT(time.isFinite());
|
||
|
if (condition.m_beginOrEnd == Begin)
|
||
|
addBeginTime(time);
|
||
|
else
|
||
|
addEndTime(time);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::addTimeDependent(SVGSMILElement* animation)
|
||
|
{
|
||
|
m_timeDependents.add(animation);
|
||
|
if (m_intervalBegin.isFinite())
|
||
|
animation->createInstanceTimesFromSyncbase(this, NewInterval);
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::removeTimeDependent(SVGSMILElement* animation)
|
||
|
{
|
||
|
m_timeDependents.remove(animation);
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::handleConditionEvent(Event* event, Condition* condition)
|
||
|
{
|
||
|
if (condition->m_beginOrEnd == Begin)
|
||
|
addBeginTime(elapsed() + condition->m_offset);
|
||
|
else
|
||
|
addEndTime(elapsed() + condition->m_offset);
|
||
|
}
|
||
|
|
||
|
void SVGSMILElement::beginByLinkActivation()
|
||
|
{
|
||
|
addBeginTime(elapsed());
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|