2 * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2006 Rob Buis <buis@kde.org>
4 * Copyright (C) 2008 Apple Inc. All rights reserved.
5 * Copyright (C) Research In Motion Limited 2011. All rights reserved.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
26 #include "SVGAnimateElement.h"
28 #include "CSSParser.h"
29 #include "CSSPropertyNames.h"
30 #include "QualifiedName.h"
31 #include "RenderObject.h"
32 #include "SVGAnimatorFactory.h"
34 #include "SVGStyledElement.h"
38 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* document)
39 : SVGAnimationElement(tagName, document)
40 , m_animatedPropertyType(AnimatedString)
42 ASSERT(hasTagName(SVGNames::animateTag) || hasTagName(SVGNames::setTag) || hasTagName(SVGNames::animateColorTag) || hasTagName(SVGNames::animateTransformTag));
45 PassRefPtr<SVGAnimateElement> SVGAnimateElement::create(const QualifiedName& tagName, Document* document)
47 return adoptRef(new SVGAnimateElement(tagName, document));
50 SVGAnimateElement::~SVGAnimateElement()
54 bool SVGAnimateElement::hasValidAttributeType()
56 SVGElement* targetElement = this->targetElement();
60 return m_animatedPropertyType != AnimatedUnknown && !hasInvalidCSSAttributeType();
63 AnimatedPropertyType SVGAnimateElement::determineAnimatedPropertyType(SVGElement* targetElement) const
65 ASSERT(targetElement);
67 Vector<AnimatedPropertyType> propertyTypes;
68 targetElement->animatedPropertyTypeForAttribute(attributeName(), propertyTypes);
69 if (propertyTypes.isEmpty())
70 return AnimatedUnknown;
72 ASSERT(propertyTypes.size() <= 2);
73 AnimatedPropertyType type = propertyTypes[0];
74 if (hasTagName(SVGNames::animateColorTag) && type != AnimatedColor)
75 return AnimatedUnknown;
77 // Animations of transform lists are not allowed for <animate> or <set>
78 // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties
79 if (type == AnimatedTransformList && !hasTagName(SVGNames::animateTransformTag))
80 return AnimatedUnknown;
82 // Fortunately there's just one special case needed here: SVGMarkerElements orientAttr, which
83 // corresponds to SVGAnimatedAngle orientAngle and SVGAnimatedEnumeration orientType. We have to
84 // figure out whose value to change here.
85 if (targetElement->hasTagName(SVGNames::markerTag) && type == AnimatedAngle) {
86 ASSERT(propertyTypes.size() == 2);
87 ASSERT(propertyTypes[0] == AnimatedAngle);
88 ASSERT(propertyTypes[1] == AnimatedEnumeration);
89 } else if (propertyTypes.size() == 2)
90 ASSERT(propertyTypes[0] == propertyTypes[1]);
95 void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement* resultElement)
97 ASSERT(resultElement);
98 SVGElement* targetElement = this->targetElement();
102 ASSERT(m_animatedPropertyType == determineAnimatedPropertyType(targetElement));
104 ASSERT(percentage >= 0 && percentage <= 1);
105 ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag));
106 ASSERT(m_animatedPropertyType != AnimatedUnknown);
108 ASSERT(m_animator->type() == m_animatedPropertyType);
110 ASSERT(m_fromType->type() == m_animatedPropertyType);
113 ASSERT(resultElement->hasTagName(SVGNames::animateTag)
114 || resultElement->hasTagName(SVGNames::animateColorTag)
115 || resultElement->hasTagName(SVGNames::animateTransformTag)
116 || resultElement->hasTagName(SVGNames::setTag));
118 SVGAnimateElement* resultAnimationElement = static_cast<SVGAnimateElement*>(resultElement);
119 ASSERT(resultAnimationElement->m_animatedType);
120 ASSERT(resultAnimationElement->m_animatedPropertyType == m_animatedPropertyType);
122 if (hasTagName(SVGNames::setTag))
125 if (calcMode() == CalcModeDiscrete)
126 percentage = percentage < 0.5 ? 0 : 1;
128 // Target element might have changed.
129 m_animator->setContextElement(targetElement);
131 // Be sure to detach list wrappers before we modfiy their underlying value. If we'd do
132 // if after calculateAnimatedValue() ran the cached pointers in the list propery tear
133 // offs would point nowhere, and we couldn't create copies of those values anymore,
134 // while detaching. This is covered by assertions, moving this down would fire them.
135 if (!m_animatedProperties.isEmpty())
136 m_animator->animValWillChange(m_animatedProperties);
138 // Values-animation accumulates using the last values entry corresponding to the end of duration time.
139 SVGAnimatedType* toAtEndOfDurationType = m_toAtEndOfDurationType ? m_toAtEndOfDurationType.get() : m_toType.get();
140 m_animator->calculateAnimatedValue(percentage, repeatCount, m_fromType.get(), m_toType.get(), toAtEndOfDurationType, resultAnimationElement->m_animatedType.get());
143 bool SVGAnimateElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
145 if (toAtEndOfDurationString.isEmpty())
147 m_toAtEndOfDurationType = ensureAnimator()->constructFromString(toAtEndOfDurationString);
151 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
153 SVGElement* targetElement = this->targetElement();
157 determinePropertyValueTypes(fromString, toString);
158 ensureAnimator()->calculateFromAndToValues(m_fromType, m_toType, fromString, toString);
159 ASSERT(m_animatedPropertyType == m_animator->type());
163 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
165 SVGElement* targetElement = this->targetElement();
169 if (animationMode() == ByAnimation && !isAdditive())
172 ASSERT(!hasTagName(SVGNames::setTag));
174 determinePropertyValueTypes(fromString, byString);
175 ensureAnimator()->calculateFromAndByValues(m_fromType, m_toType, fromString, byString);
176 ASSERT(m_animatedPropertyType == m_animator->type());
181 static inline bool propertyTypesAreConsistent(AnimatedPropertyType expectedPropertyType, const SVGElementAnimatedPropertyList& animatedTypes)
183 SVGElementAnimatedPropertyList::const_iterator end = animatedTypes.end();
184 for (SVGElementAnimatedPropertyList::const_iterator it = animatedTypes.begin(); it != end; ++it) {
185 for (size_t i = 0; i < it->properties.size(); ++i) {
186 if (expectedPropertyType != it->properties[i]->animatedPropertyType()) {
187 // This is the only allowed inconsistency. SVGAnimatedAngleAnimator handles both SVGAnimatedAngle & SVGAnimatedEnumeration for markers orient attribute.
188 if (expectedPropertyType == AnimatedAngle && it->properties[i]->animatedPropertyType() == AnimatedEnumeration)
199 void SVGAnimateElement::resetAnimatedType()
201 SVGAnimatedTypeAnimator* animator = ensureAnimator();
202 ASSERT(m_animatedPropertyType == animator->type());
204 SVGElement* targetElement = this->targetElement();
205 const QualifiedName& attributeName = this->attributeName();
206 ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName);
207 if (shouldApply == ApplyXMLAnimation) {
208 // SVG DOM animVal animation code-path.
209 m_animatedProperties = animator->findAnimatedPropertiesForAttributeName(targetElement, attributeName);
210 ASSERT(!m_animatedProperties.isEmpty());
212 ASSERT(propertyTypesAreConsistent(m_animatedPropertyType, m_animatedProperties));
214 m_animatedType = animator->startAnimValAnimation(m_animatedProperties);
216 animator->resetAnimValToBaseVal(m_animatedProperties, m_animatedType.get());
217 animator->animValDidChange(m_animatedProperties);
222 // CSS properties animation code-path.
223 ASSERT(m_animatedProperties.isEmpty());
226 if (shouldApply == ApplyCSSAnimation) {
227 ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName));
228 computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue);
232 m_animatedType = animator->constructFromString(baseValue);
234 m_animatedType->setValueAsString(attributeName, baseValue);
237 static inline void applyCSSPropertyToTarget(SVGElement* targetElement, CSSPropertyID id, const String& value)
239 ASSERT(!targetElement->m_deletionHasBegun);
241 StylePropertySet* propertySet = targetElement->ensureAnimatedSMILStyleProperties();
242 if (!propertySet->setProperty(id, value, false, 0))
245 targetElement->setNeedsStyleRecalc(SyntheticStyleChange);
248 static inline void removeCSSPropertyFromTarget(SVGElement* targetElement, CSSPropertyID id)
250 ASSERT(!targetElement->m_deletionHasBegun);
251 targetElement->ensureAnimatedSMILStyleProperties()->removeProperty(id);
252 targetElement->setNeedsStyleRecalc(SyntheticStyleChange);
255 static inline void applyCSSPropertyToTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName, const String& valueAsString)
257 ASSERT(targetElement);
258 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
261 CSSPropertyID id = cssPropertyID(attributeName.localName());
263 SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
264 applyCSSPropertyToTarget(targetElement, id, valueAsString);
266 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
267 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
268 const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
269 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
270 if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
271 applyCSSPropertyToTarget(shadowTreeElement, id, valueAsString);
275 static inline void removeCSSPropertyFromTargetAndInstances(SVGElement* targetElement, const QualifiedName& attributeName)
277 ASSERT(targetElement);
278 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
281 CSSPropertyID id = cssPropertyID(attributeName.localName());
283 SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
284 removeCSSPropertyFromTarget(targetElement, id);
286 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
287 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
288 const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
289 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
290 if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
291 removeCSSPropertyFromTarget(shadowTreeElement, id);
295 static inline void notifyTargetAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
297 ASSERT(!targetElement->m_deletionHasBegun);
298 targetElement->svgAttributeChanged(attributeName);
301 static inline void notifyTargetAndInstancesAboutAnimValChange(SVGElement* targetElement, const QualifiedName& attributeName)
303 ASSERT(targetElement);
304 if (attributeName == anyQName() || !targetElement->inDocument() || !targetElement->parentNode())
307 SVGElementInstance::InstanceUpdateBlocker blocker(targetElement);
308 notifyTargetAboutAnimValChange(targetElement, attributeName);
310 // If the target element has instances, update them as well, w/o requiring the <use> tree to be rebuilt.
311 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
312 const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
313 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
314 if (SVGElement* shadowTreeElement = (*it)->shadowTreeElement())
315 notifyTargetAboutAnimValChange(shadowTreeElement, attributeName);
319 void SVGAnimateElement::clearAnimatedType(SVGElement* targetElement)
324 if (!targetElement) {
325 m_animatedType.clear();
329 if (m_animatedProperties.isEmpty()) {
330 // CSS properties animation code-path.
331 removeCSSPropertyFromTargetAndInstances(targetElement, attributeName());
332 m_animatedType.clear();
336 // SVG DOM animVal animation code-path.
338 m_animator->stopAnimValAnimation(m_animatedProperties);
339 notifyTargetAndInstancesAboutAnimValChange(targetElement, attributeName());
342 m_animatedProperties.clear();
343 m_animatedType.clear();
346 void SVGAnimateElement::applyResultsToTarget()
348 ASSERT(m_animatedPropertyType != AnimatedTransformList || hasTagName(SVGNames::animateTransformTag));
349 ASSERT(m_animatedPropertyType != AnimatedUnknown);
352 // Early exit if our animated type got destructed by a previous endedActiveInterval().
356 if (m_animatedProperties.isEmpty()) {
357 // CSS properties animation code-path.
358 // Convert the result of the animation to a String and apply it as CSS property on the target & all instances.
359 applyCSSPropertyToTargetAndInstances(targetElement(), attributeName(), m_animatedType->valueAsString());
363 // SVG DOM animVal animation code-path.
364 // At this point the SVG DOM values are already changed, unlike for CSS.
365 // We only have to trigger update notifications here.
366 m_animator->animValDidChange(m_animatedProperties);
367 notifyTargetAndInstancesAboutAnimValChange(targetElement(), attributeName());
370 bool SVGAnimateElement::isAdditive() const
372 if (animationMode() == ByAnimation || animationMode() == FromByAnimation) {
373 // Spec: http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties.
374 switch (m_animatedPropertyType) {
375 case AnimatedBoolean:
376 case AnimatedEnumeration:
377 case AnimatedPreserveAspectRatio:
379 case AnimatedUnknown:
386 return SVGAnimationElement::isAdditive();
389 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
391 // FIXME: A return value of float is not enough to support paced animations on lists.
392 SVGElement* targetElement = this->targetElement();
396 return ensureAnimator()->calculateDistance(fromString, toString);
399 void SVGAnimateElement::setTargetElement(SVGElement* target)
401 SVGAnimationElement::setTargetElement(target);
402 resetAnimatedPropertyType();
405 void SVGAnimateElement::setAttributeName(const QualifiedName& attributeName)
407 SVGAnimationElement::setAttributeName(attributeName);
408 resetAnimatedPropertyType();
411 void SVGAnimateElement::resetAnimatedPropertyType()
413 ASSERT(!m_animatedType);
416 m_toAtEndOfDurationType.clear();
418 m_animatedPropertyType = targetElement() ? determineAnimatedPropertyType(targetElement()) : AnimatedString;
421 SVGAnimatedTypeAnimator* SVGAnimateElement::ensureAnimator()
424 m_animator = SVGAnimatorFactory::create(this, targetElement(), m_animatedPropertyType);
425 ASSERT(m_animatedPropertyType == m_animator->type());
426 return m_animator.get();
431 #endif // ENABLE(SVG)