2 Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
3 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.
7 This file is part of the KDE project
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.
26 #if ENABLE(SVG_ANIMATION)
27 #include "SVGAnimationElement.h"
29 #include "CSSComputedStyleDeclaration.h"
30 #include "CSSParser.h"
31 #include "CSSPropertyNames.h"
34 #include "EventListener.h"
35 #include "FloatConversion.h"
36 #include "HTMLNames.h"
37 #include "SVGElementInstance.h"
39 #include "SVGURIReference.h"
40 #include "SVGUseElement.h"
41 #include "XLinkNames.h"
48 SVGAnimationElement::SVGAnimationElement(const QualifiedName
& tagName
, Document
* doc
)
49 : SVGSMILElement(tagName
, doc
)
51 , SVGExternalResourcesRequired()
52 , m_animationValid(false)
56 SVGAnimationElement::~SVGAnimationElement()
60 static void parseKeyTimes(const String
& parse
, Vector
<float>& result
, bool verifyOrder
)
63 Vector
<String
> parseList
;
64 parse
.split(';', parseList
);
65 for (unsigned n
= 0; n
< parseList
.size(); ++n
) {
66 String timeString
= parseList
[n
];
68 float time
= timeString
.toFloat(&ok
);
69 if (!ok
|| time
< 0 || time
> 1.f
)
75 } else if (time
< result
.last())
85 static void parseKeySplines(const String
& parse
, Vector
<UnitBezier
>& result
)
88 Vector
<String
> parseList
;
89 parse
.split(';', parseList
);
90 for (unsigned n
= 0; n
< parseList
.size(); ++n
) {
91 Vector
<String
> parseSpline
;
92 parseList
[n
].split(',', parseSpline
);
93 // The spec says the sepator is a space, all tests use commas. Weird.
94 if (parseSpline
.size() == 1)
95 parseList
[n
].split(' ', parseSpline
);
96 if (parseSpline
.size() != 4)
98 double curveValues
[4];
99 for (unsigned i
= 0; i
< 4; ++i
) {
100 String parseNumber
= parseSpline
[i
];
102 curveValues
[i
] = parseNumber
.toDouble(&ok
);
103 if (!ok
|| curveValues
[i
] < 0.0 || curveValues
[i
] > 1.0)
106 result
.append(UnitBezier(curveValues
[0], curveValues
[1], curveValues
[2], curveValues
[3]));
113 void SVGAnimationElement::parseMappedAttribute(MappedAttribute
* attr
)
115 if (attr
->name() == SVGNames::valuesAttr
)
116 attr
->value().string().split(';', m_values
);
117 else if (attr
->name() == SVGNames::keyTimesAttr
)
118 parseKeyTimes(attr
->value(), m_keyTimes
, true);
119 else if (attr
->name() == SVGNames::keyPointsAttr
&& hasTagName(SVGNames::animateMotionTag
)) {
120 // This is specified to be an animateMotion attribute only but it is simpler to put it here
121 // where the other timing calculatations are.
122 parseKeyTimes(attr
->value(), m_keyPoints
, false);
123 } else if (attr
->name() == SVGNames::keySplinesAttr
)
124 parseKeySplines(attr
->value(), m_keySplines
);
126 if (SVGTests::parseMappedAttribute(attr
))
128 if (SVGExternalResourcesRequired::parseMappedAttribute(attr
))
130 SVGSMILElement::parseMappedAttribute(attr
);
134 void SVGAnimationElement::attributeChanged(Attribute
* attr
, bool preserveDecls
)
136 // Assumptions may not hold after an attribute change.
137 m_animationValid
= false;
138 SVGSMILElement::attributeChanged(attr
, preserveDecls
);
141 float SVGAnimationElement::getStartTime() const
143 return narrowPrecisionToFloat(intervalBegin().value());
146 float SVGAnimationElement::getCurrentTime() const
148 return narrowPrecisionToFloat(elapsed().value());
151 float SVGAnimationElement::getSimpleDuration(ExceptionCode
&) const
153 return narrowPrecisionToFloat(simpleDuration().value());
156 bool SVGAnimationElement::beginElement(ExceptionCode
& ec
)
158 return beginElementAt(0, ec
);
161 bool SVGAnimationElement::beginElementAt(float offset
, ExceptionCode
& ec
)
163 addBeginTime(elapsed() + offset
);
167 bool SVGAnimationElement::endElement(ExceptionCode
& ec
)
169 return endElementAt(0, ec
);
172 bool SVGAnimationElement::endElementAt(float offset
, ExceptionCode
& ec
)
177 addEndTime(elapsed() + offset
);
181 SVGAnimationElement::AnimationMode
SVGAnimationElement::animationMode() const
183 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
184 if (hasTagName(SVGNames::setTag
))
186 if (!animationPath().isEmpty())
187 return PathAnimation
;
188 if (hasAttribute(SVGNames::valuesAttr
))
189 return ValuesAnimation
;
190 if (!toValue().isEmpty())
191 return fromValue().isEmpty() ? ToAnimation
: FromToAnimation
;
192 if (!byValue().isEmpty())
193 return fromValue().isEmpty() ? ByAnimation
: FromByAnimation
;
197 SVGAnimationElement::CalcMode
SVGAnimationElement::calcMode() const
199 static const AtomicString
discrete("discrete");
200 static const AtomicString
linear("linear");
201 static const AtomicString
paced("paced");
202 static const AtomicString
spline("spline");
203 const AtomicString
& value
= getAttribute(SVGNames::calcModeAttr
);
204 if (value
== discrete
)
205 return CalcModeDiscrete
;
207 return CalcModeLinear
;
209 return CalcModePaced
;
211 return CalcModeSpline
;
212 return hasTagName(SVGNames::animateMotionTag
) ? CalcModePaced
: CalcModeLinear
;
215 SVGAnimationElement::AttributeType
SVGAnimationElement::attributeType() const
217 static const AtomicString
css("CSS");
218 static const AtomicString
xml("XML");
219 const AtomicString
& value
= getAttribute(SVGNames::attributeTypeAttr
);
221 return AttributeTypeCSS
;
223 return AttributeTypeXML
;
224 return AttributeTypeAuto
;
227 String
SVGAnimationElement::toValue() const
229 return getAttribute(SVGNames::toAttr
);
232 String
SVGAnimationElement::byValue() const
234 return getAttribute(SVGNames::byAttr
);
237 String
SVGAnimationElement::fromValue() const
239 return getAttribute(SVGNames::fromAttr
);
242 bool SVGAnimationElement::isAdditive() const
244 static const AtomicString
sum("sum");
245 const AtomicString
& value
= getAttribute(SVGNames::additiveAttr
);
246 return value
== sum
|| animationMode() == ByAnimation
;
249 bool SVGAnimationElement::isAccumulated() const
251 static const AtomicString
sum("sum");
252 const AtomicString
& value
= getAttribute(SVGNames::accumulateAttr
);
253 return value
== sum
&& animationMode() != ToAnimation
;
256 bool SVGAnimationElement::hasValidTarget() const
258 return targetElement();
261 bool SVGAnimationElement::attributeIsCSS(const String
& attributeName
)
263 // FIXME: We should have a map of all SVG properties and their attribute types so we
264 // could validate animations better. The spec is very vague about this.
265 unsigned id
= cssPropertyID(attributeName
);
267 if (id
>= CSSPropertyClipPath
&& id
<= CSSPropertyWritingMode
)
269 // Regular CSS properties also in SVG
270 return id
== CSSPropertyColor
|| id
== CSSPropertyDisplay
|| id
== CSSPropertyOpacity
271 || (id
>= CSSPropertyFont
&& id
<= CSSPropertyFontWeight
)
272 || id
== CSSPropertyOverflow
|| id
== CSSPropertyVisibility
;
275 bool SVGAnimationElement::targetAttributeIsCSS() const
277 AttributeType type
= attributeType();
278 if (type
== AttributeTypeCSS
)
280 if (type
== AttributeTypeXML
)
282 return attributeIsCSS(attributeName());
285 void SVGAnimationElement::setTargetAttributeAnimatedValue(const String
& value
)
287 if (!hasValidTarget())
289 SVGElement
* target
= targetElement();
290 String attributeName
= this->attributeName();
291 if (!target
|| attributeName
.isEmpty() || value
.isNull())
294 // We don't want the instance tree to get rebuild. Instances are updated in the loop below.
295 if (target
->isStyled())
296 static_cast<SVGStyledElement
*>(target
)->setInstanceUpdatesBlocked(true);
299 bool isCSS
= targetAttributeIsCSS();
301 // FIXME: This should set the override style, not the inline style.
302 // Sadly override styles are not yet implemented.
303 target
->style()->setProperty(attributeName
, value
, "", ec
);
305 // FIXME: This should set the 'presentation' value, not the actual
306 // attribute value. Whatever that means in practice.
307 target
->setAttribute(attributeName
, value
, ec
);
310 if (target
->isStyled())
311 static_cast<SVGStyledElement
*>(target
)->setInstanceUpdatesBlocked(false);
313 // If the target element is used in an <use> instance tree, update that as well.
314 HashSet
<SVGElementInstance
*>* instances
= document()->accessSVGExtensions()->instancesForElement(target
);
317 HashSet
<SVGElementInstance
*>::iterator end
= instances
->end();
318 for (HashSet
<SVGElementInstance
*>::iterator it
= instances
->begin(); it
!= end
; ++it
) {
319 SVGElement
* shadowTreeElement
= (*it
)->shadowTreeElement();
320 ASSERT(shadowTreeElement
);
322 shadowTreeElement
->style()->setProperty(attributeName
, value
, "", ec
);
324 shadowTreeElement
->setAttribute(attributeName
, value
, ec
);
325 (*it
)->correspondingUseElement()->setChanged();
329 void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
331 ASSERT(calcMode() == CalcModePaced
);
332 ASSERT(animationMode() == ValuesAnimation
);
334 unsigned valuesCount
= m_values
.size();
335 ASSERT(valuesCount
> 1);
336 Vector
<float> keyTimesForPaced
;
337 float totalDistance
= 0;
338 keyTimesForPaced
.append(0);
339 for (unsigned n
= 0; n
< valuesCount
- 1; ++n
) {
340 // Distance in any units
341 float distance
= calculateDistance(m_values
[n
], m_values
[n
+ 1]);
344 totalDistance
+= distance
;
345 keyTimesForPaced
.append(distance
);
351 for (unsigned n
= 1; n
< keyTimesForPaced
.size() - 1; ++n
)
352 keyTimesForPaced
[n
] = keyTimesForPaced
[n
- 1] + keyTimesForPaced
[n
] / totalDistance
;
353 keyTimesForPaced
[keyTimesForPaced
.size() - 1] = 1.f
;
355 // Use key times calculated based on pacing instead of the user provided ones.
356 m_keyTimes
.swap(keyTimesForPaced
);
359 static inline double solveEpsilon(double duration
) { return 1. / (200. * duration
); }
361 float SVGAnimationElement::calculatePercentForSpline(float percent
, unsigned splineIndex
) const
363 ASSERT(calcMode() == CalcModeSpline
);
364 ASSERT(splineIndex
< m_keySplines
.size());
365 UnitBezier bezier
= m_keySplines
[splineIndex
];
366 SMILTime duration
= simpleDuration();
367 if (!duration
.isFinite())
369 return narrowPrecisionToFloat(bezier
.solve(percent
, solveEpsilon(duration
.value())));
372 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent
) const
374 ASSERT(!m_keyPoints
.isEmpty());
375 ASSERT(calcMode() != CalcModePaced
);
376 unsigned keyTimesCount
= m_keyTimes
.size();
377 ASSERT(keyTimesCount
> 1);
378 ASSERT(m_keyPoints
.size() == keyTimesCount
);
381 for (index
= 1; index
< keyTimesCount
; ++index
) {
382 if (m_keyTimes
[index
] >= percent
)
387 float fromPercent
= m_keyTimes
[index
];
388 float toPercent
= m_keyTimes
[index
+ 1];
389 float fromKeyPoint
= m_keyPoints
[index
];
390 float toKeyPoint
= m_keyPoints
[index
+ 1];
392 if (calcMode() == CalcModeDiscrete
)
393 return percent
== 1.0f
? toKeyPoint
: fromKeyPoint
;
395 float keyPointPercent
= percent
== 1.0f
? 1.0f
: (percent
- fromPercent
) / (toPercent
- fromPercent
);
397 if (calcMode() == CalcModeSpline
) {
398 ASSERT(m_keySplines
.size() == m_keyPoints
.size() - 1);
399 keyPointPercent
= calculatePercentForSpline(keyPointPercent
, index
);
401 return (toKeyPoint
- fromKeyPoint
) * keyPointPercent
+ fromKeyPoint
;
404 void SVGAnimationElement::currentValuesFromKeyPoints(float percent
, float& effectivePercent
, String
& from
, String
& to
) const
406 ASSERT(!m_keyPoints
.isEmpty());
407 ASSERT(m_keyPoints
.size() == m_keyTimes
.size());
408 ASSERT(calcMode() != CalcModePaced
);
409 effectivePercent
= calculatePercentFromKeyPoints(percent
);
410 unsigned index
= effectivePercent
== 1.0f
? m_values
.size() - 2 : static_cast<unsigned>(effectivePercent
* (m_values
.size() - 1));
411 from
= m_values
[index
];
412 to
= m_values
[index
+ 1];
415 void SVGAnimationElement::currentValuesForValuesAnimation(float percent
, float& effectivePercent
, String
& from
, String
& to
) const
417 unsigned valuesCount
= m_values
.size();
418 ASSERT(m_animationValid
);
419 ASSERT(valuesCount
> 1);
421 CalcMode calcMode
= this->calcMode();
422 if (!m_keyPoints
.isEmpty() && calcMode
!= CalcModePaced
)
423 return currentValuesFromKeyPoints(percent
, effectivePercent
, from
, to
);
425 unsigned keyTimesCount
= m_keyTimes
.size();
426 ASSERT(!keyTimesCount
|| valuesCount
== keyTimesCount
);
427 ASSERT(!keyTimesCount
|| (keyTimesCount
> 1 && m_keyTimes
[0] == 0));
430 for (index
= 1; index
< keyTimesCount
; ++index
) {
431 if (m_keyTimes
[index
] >= percent
)
436 if (calcMode
== CalcModeDiscrete
) {
438 index
= percent
== 1.0f
? valuesCount
- 1 : static_cast<unsigned>(percent
* valuesCount
);
439 from
= m_values
[index
];
440 to
= m_values
[index
];
441 effectivePercent
= 0.0f
;
448 fromPercent
= m_keyTimes
[index
];
449 toPercent
= m_keyTimes
[index
+ 1];
451 index
= static_cast<unsigned>(percent
* (valuesCount
- 1));
452 fromPercent
= static_cast<float>(index
) / (valuesCount
- 1);
453 toPercent
= static_cast<float>(index
+ 1) / (valuesCount
- 1);
456 if (index
== valuesCount
- 1)
458 from
= m_values
[index
];
459 to
= m_values
[index
+ 1];
460 ASSERT(toPercent
> fromPercent
);
461 effectivePercent
= percent
== 1.0f
? 1.0f
: (percent
- fromPercent
) / (toPercent
- fromPercent
);
463 if (calcMode
== CalcModeSpline
) {
464 ASSERT(m_keySplines
.size() == m_values
.size() - 1);
465 effectivePercent
= calculatePercentForSpline(effectivePercent
, index
);
469 void SVGAnimationElement::startedActiveInterval()
471 m_animationValid
= false;
473 if (!hasValidTarget())
476 AnimationMode animationMode
= this->animationMode();
477 if (animationMode
== NoAnimation
)
479 if (animationMode
== FromToAnimation
)
480 m_animationValid
= calculateFromAndToValues(fromValue(), toValue());
481 else if (animationMode
== ToAnimation
) {
482 // For to-animations the from value is the current accumulated value from lower priority animations.
483 // The value is not static and is determined during the animation.
484 m_animationValid
= calculateFromAndToValues(String(), toValue());
485 } else if (animationMode
== FromByAnimation
)
486 m_animationValid
= calculateFromAndByValues(fromValue(), byValue());
487 else if (animationMode
== ByAnimation
)
488 m_animationValid
= calculateFromAndByValues(String(), byValue());
489 else if (animationMode
== ValuesAnimation
) {
490 CalcMode calcMode
= this->calcMode();
491 m_animationValid
= m_values
.size() > 1
492 && (calcMode
== CalcModePaced
|| !hasAttribute(SVGNames::keyTimesAttr
) || hasAttribute(SVGNames::keyPointsAttr
) || (m_values
.size() == m_keyTimes
.size()))
493 && (calcMode
== CalcModeDiscrete
|| !m_keyTimes
.size() || m_keyTimes
.last() == 1.0)
494 && (calcMode
!= CalcModeSpline
|| (m_keySplines
.size() && (m_keySplines
.size() == m_values
.size() - 1) || m_keySplines
.size() == m_keyPoints
.size() - 1))
495 && (!hasAttribute(SVGNames::keyPointsAttr
) || (m_keyTimes
.size() > 1 && m_keyTimes
.size() == m_keyPoints
.size()));
496 if (calcMode
== CalcModePaced
&& m_animationValid
)
497 calculateKeyTimesForCalcModePaced();
498 } else if (animationMode
== PathAnimation
)
499 m_animationValid
= calcMode() == CalcModePaced
|| !hasAttribute(SVGNames::keyPointsAttr
) || (m_keyTimes
.size() > 1 && m_keyTimes
.size() == m_keyPoints
.size());
502 void SVGAnimationElement::updateAnimation(float percent
, unsigned repeat
, SVGSMILElement
* resultElement
)
504 if (!m_animationValid
)
507 float effectivePercent
;
508 if (animationMode() == ValuesAnimation
) {
511 currentValuesForValuesAnimation(percent
, effectivePercent
, from
, to
);
512 if (from
!= m_lastValuesAnimationFrom
|| to
!= m_lastValuesAnimationTo
) {
513 m_animationValid
= calculateFromAndToValues(from
, to
);
514 if (!m_animationValid
)
516 m_lastValuesAnimationFrom
= from
;
517 m_lastValuesAnimationTo
= to
;
519 } else if (!m_keyPoints
.isEmpty() && calcMode() != CalcModePaced
)
520 effectivePercent
= calculatePercentFromKeyPoints(percent
);
522 effectivePercent
= percent
;
524 calculateAnimatedValue(effectivePercent
, repeat
, resultElement
);
527 void SVGAnimationElement::endedActiveInterval()
534 #endif // ENABLE(SVG_ANIMATION)