2 * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
4 * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
5 * Copyright (C) 2008 Apple Inc. All rights reserved.
6 * Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au>
7 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
27 #if ENABLE(SVG_ANIMATION)
28 #include "SVGAnimationElement.h"
30 #include "Attribute.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSParser.h"
33 #include "CSSPropertyNames.h"
37 #include "EventListener.h"
38 #include "FloatConversion.h"
39 #include "HTMLNames.h"
40 #include "PlatformString.h"
41 #include "SVGAnimateElement.h"
42 #include "SVGElementInstance.h"
44 #include "SVGParserUtilities.h"
45 #include "SVGStyledElement.h"
46 #include "SVGURIReference.h"
47 #include "SVGUseElement.h"
48 #include "XLinkNames.h"
49 #include <wtf/StdLibExtras.h>
55 // Animated property definitions
56 DEFINE_ANIMATED_BOOLEAN(SVGAnimationElement, SVGNames::externalResourcesRequiredAttr, ExternalResourcesRequired, externalResourcesRequired)
58 BEGIN_REGISTER_ANIMATED_PROPERTIES(SVGAnimationElement)
59 REGISTER_LOCAL_ANIMATED_PROPERTY(externalResourcesRequired)
60 REGISTER_PARENT_ANIMATED_PROPERTIES(SVGTests)
61 END_REGISTER_ANIMATED_PROPERTIES
63 SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* document)
64 : SVGSMILElement(tagName, document)
65 , m_animationValid(false)
67 registerAnimatedPropertiesForSVGAnimationElement();
70 static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder)
73 Vector<String> parseList;
74 parse.split(';', parseList);
75 for (unsigned n = 0; n < parseList.size(); ++n) {
76 String timeString = parseList[n];
78 float time = timeString.toFloat(&ok);
79 if (!ok || time < 0 || time > 1)
85 } else if (time < result.last())
95 static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
100 const UChar* cur = parse.characters();
101 const UChar* end = cur + parse.length();
103 skipOptionalSVGSpaces(cur, end);
105 bool delimParsed = false;
109 if (!parseNumber(cur, end, posA)) {
115 if (!parseNumber(cur, end, posB)) {
121 if (!parseNumber(cur, end, posC)) {
127 if (!parseNumber(cur, end, posD, false)) {
132 skipOptionalSVGSpaces(cur, end);
134 if (cur < end && *cur == ';') {
138 skipOptionalSVGSpaces(cur, end);
140 result.append(UnitBezier(posA, posB, posC, posD));
142 if (!(cur == end && !delimParsed))
146 bool SVGAnimationElement::isSupportedAttribute(const QualifiedName& attrName)
148 DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, supportedAttributes, ());
149 if (supportedAttributes.isEmpty()) {
150 SVGTests::addSupportedAttributes(supportedAttributes);
151 SVGExternalResourcesRequired::addSupportedAttributes(supportedAttributes);
152 supportedAttributes.add(SVGNames::valuesAttr);
153 supportedAttributes.add(SVGNames::keyTimesAttr);
154 supportedAttributes.add(SVGNames::keyPointsAttr);
155 supportedAttributes.add(SVGNames::keySplinesAttr);
157 return supportedAttributes.contains(attrName);
160 void SVGAnimationElement::parseMappedAttribute(Attribute* attr)
162 if (!isSupportedAttribute(attr->name())) {
163 SVGSMILElement::parseMappedAttribute(attr);
167 if (attr->name() == SVGNames::valuesAttr) {
168 // Per the SMIL specification, leading and trailing white space,
169 // and white space before and after semicolon separators, is allowed and will be ignored.
170 // http://www.w3.org/TR/SVG11/animate.html#ValuesAttribute
171 attr->value().string().split(';', m_values);
172 for (unsigned i = 0; i < m_values.size(); ++i)
173 m_values[i] = m_values[i].stripWhiteSpace();
177 if (attr->name() == SVGNames::keyTimesAttr) {
178 parseKeyTimes(attr->value(), m_keyTimes, true);
182 if (attr->name() == SVGNames::keyPointsAttr) {
183 if (hasTagName(SVGNames::animateMotionTag)) {
184 // This is specified to be an animateMotion attribute only but it is simpler to put it here
185 // where the other timing calculatations are.
186 parseKeyTimes(attr->value(), m_keyPoints, false);
191 if (attr->name() == SVGNames::keySplinesAttr) {
192 parseKeySplines(attr->value(), m_keySplines);
196 if (SVGTests::parseMappedAttribute(attr))
198 if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
201 ASSERT_NOT_REACHED();
204 void SVGAnimationElement::attributeChanged(Attribute* attr, bool preserveDecls)
206 // Assumptions may not hold after an attribute change.
207 m_animationValid = false;
209 SVGSMILElement::attributeChanged(attr, preserveDecls);
212 float SVGAnimationElement::getStartTime() const
214 return narrowPrecisionToFloat(intervalBegin().value());
217 float SVGAnimationElement::getCurrentTime() const
219 return narrowPrecisionToFloat(elapsed().value());
222 float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const
224 return narrowPrecisionToFloat(simpleDuration().value());
227 void SVGAnimationElement::beginElement()
232 void SVGAnimationElement::beginElementAt(float offset)
234 SMILTime elapsed = this->elapsed();
235 addBeginTime(elapsed, elapsed + offset);
238 void SVGAnimationElement::endElement()
243 void SVGAnimationElement::endElementAt(float offset)
245 SMILTime elapsed = this->elapsed();
246 addEndTime(elapsed, elapsed + offset);
249 AnimationMode SVGAnimationElement::animationMode() const
251 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
252 if (hasTagName(SVGNames::setTag))
254 if (!animationPath().isEmpty())
255 return PathAnimation;
256 if (fastHasAttribute(SVGNames::valuesAttr))
257 return ValuesAnimation;
258 if (!toValue().isEmpty())
259 return fromValue().isEmpty() ? ToAnimation : FromToAnimation;
260 if (!byValue().isEmpty())
261 return fromValue().isEmpty() ? ByAnimation : FromByAnimation;
265 CalcMode SVGAnimationElement::calcMode() const
267 DEFINE_STATIC_LOCAL(const AtomicString, discrete, ("discrete"));
268 DEFINE_STATIC_LOCAL(const AtomicString, linear, ("linear"));
269 DEFINE_STATIC_LOCAL(const AtomicString, paced, ("paced"));
270 DEFINE_STATIC_LOCAL(const AtomicString, spline, ("spline"));
271 const AtomicString& value = fastGetAttribute(SVGNames::calcModeAttr);
272 if (value == discrete)
273 return CalcModeDiscrete;
275 return CalcModeLinear;
277 return CalcModePaced;
279 return CalcModeSpline;
280 return hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear;
283 SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const
285 DEFINE_STATIC_LOCAL(const AtomicString, css, ("CSS"));
286 DEFINE_STATIC_LOCAL(const AtomicString, xml, ("XML"));
287 const AtomicString& value = fastGetAttribute(SVGNames::attributeTypeAttr);
289 return AttributeTypeCSS;
291 return AttributeTypeXML;
292 return AttributeTypeAuto;
295 String SVGAnimationElement::toValue() const
297 return fastGetAttribute(SVGNames::toAttr);
300 String SVGAnimationElement::byValue() const
302 return fastGetAttribute(SVGNames::byAttr);
305 String SVGAnimationElement::fromValue() const
307 return fastGetAttribute(SVGNames::fromAttr);
310 bool SVGAnimationElement::isAdditive() const
312 DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum"));
313 const AtomicString& value = fastGetAttribute(SVGNames::additiveAttr);
314 return value == sum || animationMode() == ByAnimation;
317 bool SVGAnimationElement::isAccumulated() const
319 DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum"));
320 const AtomicString& value = fastGetAttribute(SVGNames::accumulateAttr);
321 return value == sum && animationMode() != ToAnimation;
324 bool SVGAnimationElement::isTargetAttributeCSSProperty(SVGElement* targetElement, const QualifiedName& attributeName)
326 ASSERT(targetElement);
327 if (!targetElement->isStyled())
330 return SVGStyledElement::isAnimatableCSSProperty(attributeName);
333 void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value)
335 if (!hasValidAttributeType())
337 SVGElement* targetElement = this->targetElement();
338 QualifiedName attributeName = this->attributeName();
339 if (!targetElement || attributeName == anyQName() || value.isNull())
342 // We don't want the instance tree to get rebuild. Instances are updated in the loop below.
343 if (targetElement->isStyled())
344 static_cast<SVGStyledElement*>(targetElement)->setInstanceUpdatesBlocked(true);
346 bool attributeIsCSSProperty = isTargetAttributeCSSProperty(targetElement, attributeName);
347 // Stop animation, if attributeType is set to CSS by the user, but the attribute itself is not a CSS property.
348 if (!attributeIsCSSProperty && attributeType() == AttributeTypeCSS)
352 if (attributeIsCSSProperty) {
353 // FIXME: This should set the override style, not the inline style.
354 // Sadly override styles are not yet implemented.
355 targetElement->style()->setProperty(attributeName.localName(), value, "", ec);
357 // FIXME: This should set the 'presentation' value, not the actual
358 // attribute value. Whatever that means in practice.
359 targetElement->setAttribute(attributeName, value, ec);
362 if (targetElement->isStyled())
363 static_cast<SVGStyledElement*>(targetElement)->setInstanceUpdatesBlocked(false);
365 // If the target element is used in an <use> instance tree, update that as well.
366 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
367 const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
368 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
369 SVGElement* shadowTreeElement = (*it)->shadowTreeElement();
370 if (!shadowTreeElement)
372 if (attributeIsCSSProperty)
373 shadowTreeElement->style()->setProperty(attributeName.localName(), value, "", ec);
375 shadowTreeElement->setAttribute(attributeName, value, ec);
376 (*it)->correspondingUseElement()->setNeedsStyleRecalc();
380 void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
382 ASSERT(calcMode() == CalcModePaced);
383 ASSERT(animationMode() == ValuesAnimation);
385 unsigned valuesCount = m_values.size();
386 ASSERT(valuesCount > 1);
387 Vector<float> keyTimesForPaced;
388 float totalDistance = 0;
389 keyTimesForPaced.append(0);
390 for (unsigned n = 0; n < valuesCount - 1; ++n) {
391 // Distance in any units
392 float distance = calculateDistance(m_values[n], m_values[n + 1]);
395 totalDistance += distance;
396 keyTimesForPaced.append(distance);
402 for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n)
403 keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance;
404 keyTimesForPaced[keyTimesForPaced.size() - 1] = 1;
406 // Use key times calculated based on pacing instead of the user provided ones.
407 m_keyTimes.swap(keyTimesForPaced);
410 static inline double solveEpsilon(double duration) { return 1 / (200 * duration); }
412 unsigned SVGAnimationElement::calculateKeyTimesIndex(float percent) const
415 unsigned keyTimesCount = m_keyTimes.size();
416 // Compare index + 1 to keyTimesCount because the last keyTimes entry is
417 // required to be 1, and percent can never exceed 1; i.e., the second last
418 // keyTimes entry defines the beginning of the final interval
419 for (index = 1; index + 1 < keyTimesCount; ++index) {
420 if (m_keyTimes[index] > percent)
426 float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
428 ASSERT(calcMode() == CalcModeSpline);
429 ASSERT(splineIndex < m_keySplines.size());
430 UnitBezier bezier = m_keySplines[splineIndex];
431 SMILTime duration = simpleDuration();
432 if (!duration.isFinite())
434 return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value())));
437 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
439 ASSERT(!m_keyPoints.isEmpty());
440 ASSERT(calcMode() != CalcModePaced);
441 ASSERT(m_keyTimes.size() > 1);
442 ASSERT(m_keyPoints.size() == m_keyTimes.size());
445 return m_keyPoints[m_keyPoints.size() - 1];
447 unsigned index = calculateKeyTimesIndex(percent);
448 float fromPercent = m_keyTimes[index];
449 float toPercent = m_keyTimes[index + 1];
450 float fromKeyPoint = m_keyPoints[index];
451 float toKeyPoint = m_keyPoints[index + 1];
453 if (calcMode() == CalcModeDiscrete)
456 float keyPointPercent = (percent - fromPercent) / (toPercent - fromPercent);
458 if (calcMode() == CalcModeSpline) {
459 ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
460 keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
462 return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
465 void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const
467 ASSERT(!m_keyPoints.isEmpty());
468 ASSERT(m_keyPoints.size() == m_keyTimes.size());
469 ASSERT(calcMode() != CalcModePaced);
470 effectivePercent = calculatePercentFromKeyPoints(percent);
471 unsigned index = effectivePercent == 1 ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1));
472 from = m_values[index];
473 to = m_values[index + 1];
476 void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to)
478 unsigned valuesCount = m_values.size();
479 ASSERT(m_animationValid);
480 ASSERT(valuesCount > 1);
483 from = m_values[valuesCount - 1];
484 to = m_values[valuesCount - 1];
485 effectivePercent = 1;
489 CalcMode calcMode = this->calcMode();
490 if (hasTagName(SVGNames::animateTag) || hasTagName(SVGNames::animateColorTag)) {
491 SVGAnimateElement* animateElement = static_cast<SVGAnimateElement*>(this);
492 AnimatedPropertyType attributeType = animateElement->determineAnimatedPropertyType(targetElement());
493 // Fall back to discrete animations for Strings.
494 if (attributeType == AnimatedBoolean
495 || attributeType == AnimatedEnumeration
496 || attributeType == AnimatedPreserveAspectRatio
497 || attributeType == AnimatedString)
498 calcMode = CalcModeDiscrete;
500 if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced)
501 return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
503 unsigned keyTimesCount = m_keyTimes.size();
504 ASSERT(!keyTimesCount || valuesCount == keyTimesCount);
505 ASSERT(!keyTimesCount || (keyTimesCount > 1 && !m_keyTimes[0]));
507 unsigned index = calculateKeyTimesIndex(percent);
508 if (calcMode == CalcModeDiscrete) {
510 index = static_cast<unsigned>(percent * valuesCount);
511 from = m_values[index];
512 to = m_values[index];
513 effectivePercent = 0;
520 fromPercent = m_keyTimes[index];
521 toPercent = m_keyTimes[index + 1];
523 index = static_cast<unsigned>(percent * (valuesCount - 1));
524 fromPercent = static_cast<float>(index) / (valuesCount - 1);
525 toPercent = static_cast<float>(index + 1) / (valuesCount - 1);
528 if (index == valuesCount - 1)
530 from = m_values[index];
531 to = m_values[index + 1];
532 ASSERT(toPercent > fromPercent);
533 effectivePercent = (percent - fromPercent) / (toPercent - fromPercent);
535 if (calcMode == CalcModeSpline) {
536 ASSERT(m_keySplines.size() == m_values.size() - 1);
537 effectivePercent = calculatePercentForSpline(effectivePercent, index);
541 void SVGAnimationElement::startedActiveInterval()
543 m_animationValid = false;
545 if (!hasValidAttributeType())
548 // These validations are appropriate for all animation modes.
549 if (fastHasAttribute(SVGNames::keyPointsAttr) && m_keyPoints.size() != m_keyTimes.size())
552 AnimationMode animationMode = this->animationMode();
553 CalcMode calcMode = this->calcMode();
554 if (calcMode == CalcModeSpline) {
555 unsigned splinesCount = m_keySplines.size() + 1;
556 if ((fastHasAttribute(SVGNames::keyPointsAttr) && m_keyPoints.size() != splinesCount)
557 || (animationMode == ValuesAnimation && m_values.size() != splinesCount)
558 || (fastHasAttribute(SVGNames::keyTimesAttr) && m_keyTimes.size() != splinesCount))
562 String from = fromValue();
563 String to = toValue();
564 String by = byValue();
565 if (animationMode == NoAnimation)
567 if (animationMode == FromToAnimation)
568 m_animationValid = calculateFromAndToValues(from, to);
569 else if (animationMode == ToAnimation) {
570 // For to-animations the from value is the current accumulated value from lower priority animations.
571 // The value is not static and is determined during the animation.
572 m_animationValid = calculateFromAndToValues(String(), to);
573 } else if (animationMode == FromByAnimation)
574 m_animationValid = calculateFromAndByValues(from, by);
575 else if (animationMode == ByAnimation)
576 m_animationValid = calculateFromAndByValues(String(), by);
577 else if (animationMode == ValuesAnimation) {
578 m_animationValid = m_values.size() > 1
579 && (calcMode == CalcModePaced || !fastHasAttribute(SVGNames::keyTimesAttr) || fastHasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
580 && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1)
581 && (calcMode != CalcModeSpline || ((m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1)) || m_keySplines.size() == m_keyPoints.size() - 1))
582 && (!fastHasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
583 if (calcMode == CalcModePaced && m_animationValid)
584 calculateKeyTimesForCalcModePaced();
585 } else if (animationMode == PathAnimation)
586 m_animationValid = calcMode == CalcModePaced || !fastHasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
589 void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement* resultElement)
591 if (!m_animationValid)
594 float effectivePercent;
595 CalcMode mode = calcMode();
596 if (animationMode() == ValuesAnimation) {
599 currentValuesForValuesAnimation(percent, effectivePercent, from, to);
600 if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo) {
601 m_animationValid = calculateFromAndToValues(from, to);
602 if (!m_animationValid)
604 m_lastValuesAnimationFrom = from;
605 m_lastValuesAnimationTo = to;
607 } else if (!m_keyPoints.isEmpty() && mode != CalcModePaced)
608 effectivePercent = calculatePercentFromKeyPoints(percent);
609 else if (m_keyPoints.isEmpty() && mode == CalcModeSpline && m_keyTimes.size() > 1)
610 effectivePercent = calculatePercentForSpline(percent, calculateKeyTimesIndex(percent));
612 effectivePercent = percent;
614 calculateAnimatedValue(effectivePercent, repeat, resultElement);
617 void SVGAnimationElement::endedActiveInterval()
622 #endif // ENABLE(SVG_ANIMATION)