1 /*
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.
8 *
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.
13 *
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.
18 *
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.
23 */
24
25 #include "config.h"
26
27 #if ENABLE(SVG_ANIMATION)
28 #include "SVGAnimationElement.h"
29
30 #include "Attribute.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSParser.h"
33 #include "CSSPropertyNames.h"
34 #include "Color.h"
35 #include "Document.h"
36 #include "Event.h"
37 #include "EventListener.h"
38 #include "FloatConversion.h"
39 #include "HTMLNames.h"
40 #include "PlatformString.h"
41 #include "SVGElementInstance.h"
42 #include "SVGNames.h"
43 #include "SVGParserUtilities.h"
44 #include "SVGStyledElement.h"
45 #include "SVGURIReference.h"
46 #include "SVGUseElement.h"
47 #include "XLinkNames.h"
48 #include <wtf/StdLibExtras.h>
49
50 using namespace std;
51
52 namespace WebCore {
53
54 // Animated property definitions
DEFINE_ANIMATED_BOOLEAN(SVGAnimationElement,SVGNames::externalResourcesRequiredAttr,ExternalResourcesRequired,externalResourcesRequired)55 DEFINE_ANIMATED_BOOLEAN(SVGAnimationElement, SVGNames::externalResourcesRequiredAttr, ExternalResourcesRequired, externalResourcesRequired)
56
57 SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* document)
58 : SVGSMILElement(tagName, document)
59 , m_animationValid(false)
60 {
61 }
62
parseKeyTimes(const String & parse,Vector<float> & result,bool verifyOrder)63 static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder)
64 {
65 result.clear();
66 Vector<String> parseList;
67 parse.split(';', parseList);
68 for (unsigned n = 0; n < parseList.size(); ++n) {
69 String timeString = parseList[n];
70 bool ok;
71 float time = timeString.toFloat(&ok);
72 if (!ok || time < 0 || time > 1)
73 goto fail;
74 if (verifyOrder) {
75 if (!n) {
76 if (time)
77 goto fail;
78 } else if (time < result.last())
79 goto fail;
80 }
81 result.append(time);
82 }
83 return;
84 fail:
85 result.clear();
86 }
87
parseKeySplines(const String & parse,Vector<UnitBezier> & result)88 static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
89 {
90 result.clear();
91 if (parse.isEmpty())
92 return;
93 const UChar* cur = parse.characters();
94 const UChar* end = cur + parse.length();
95
96 skipOptionalSpaces(cur, end);
97
98 bool delimParsed = false;
99 while (cur < end) {
100 delimParsed = false;
101 float posA = 0;
102 if (!parseNumber(cur, end, posA)) {
103 result.clear();
104 return;
105 }
106
107 float posB = 0;
108 if (!parseNumber(cur, end, posB)) {
109 result.clear();
110 return;
111 }
112
113 float posC = 0;
114 if (!parseNumber(cur, end, posC)) {
115 result.clear();
116 return;
117 }
118
119 float posD = 0;
120 if (!parseNumber(cur, end, posD, false)) {
121 result.clear();
122 return;
123 }
124
125 skipOptionalSpaces(cur, end);
126
127 if (cur < end && *cur == ';') {
128 delimParsed = true;
129 cur++;
130 }
131 skipOptionalSpaces(cur, end);
132
133 result.append(UnitBezier(posA, posB, posC, posD));
134 }
135 if (!(cur == end && !delimParsed))
136 result.clear();
137 }
138
parseMappedAttribute(Attribute * attr)139 void SVGAnimationElement::parseMappedAttribute(Attribute* attr)
140 {
141 if (attr->name() == SVGNames::valuesAttr)
142 attr->value().string().split(';', m_values);
143 else if (attr->name() == SVGNames::keyTimesAttr)
144 parseKeyTimes(attr->value(), m_keyTimes, true);
145 else if (attr->name() == SVGNames::keyPointsAttr && hasTagName(SVGNames::animateMotionTag)) {
146 // This is specified to be an animateMotion attribute only but it is simpler to put it here
147 // where the other timing calculatations are.
148 parseKeyTimes(attr->value(), m_keyPoints, false);
149 } else if (attr->name() == SVGNames::keySplinesAttr)
150 parseKeySplines(attr->value(), m_keySplines);
151 else {
152 if (SVGTests::parseMappedAttribute(attr))
153 return;
154 if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
155 return;
156 SVGSMILElement::parseMappedAttribute(attr);
157 }
158 }
159
attributeChanged(Attribute * attr,bool preserveDecls)160 void SVGAnimationElement::attributeChanged(Attribute* attr, bool preserveDecls)
161 {
162 // Assumptions may not hold after an attribute change.
163 m_animationValid = false;
164 SVGSMILElement::attributeChanged(attr, preserveDecls);
165 }
166
synchronizeProperty(const QualifiedName & attrName)167 void SVGAnimationElement::synchronizeProperty(const QualifiedName& attrName)
168 {
169 SVGSMILElement::synchronizeProperty(attrName);
170
171 if (attrName == anyQName()) {
172 synchronizeExternalResourcesRequired();
173 SVGTests::synchronizeProperties(this, attrName);
174 return;
175 }
176
177 if (SVGExternalResourcesRequired::isKnownAttribute(attrName))
178 synchronizeExternalResourcesRequired();
179 else if (SVGTests::isKnownAttribute(attrName))
180 SVGTests::synchronizeProperties(this, attrName);
181 }
182
getStartTime() const183 float SVGAnimationElement::getStartTime() const
184 {
185 return narrowPrecisionToFloat(intervalBegin().value());
186 }
187
getCurrentTime() const188 float SVGAnimationElement::getCurrentTime() const
189 {
190 return narrowPrecisionToFloat(elapsed().value());
191 }
192
getSimpleDuration(ExceptionCode &) const193 float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const
194 {
195 return narrowPrecisionToFloat(simpleDuration().value());
196 }
197
beginElement()198 void SVGAnimationElement::beginElement()
199 {
200 beginElementAt(0);
201 }
202
beginElementAt(float offset)203 void SVGAnimationElement::beginElementAt(float offset)
204 {
205 addBeginTime(elapsed() + offset);
206 }
207
endElement()208 void SVGAnimationElement::endElement()
209 {
210 endElementAt(0);
211 }
212
endElementAt(float offset)213 void SVGAnimationElement::endElementAt(float offset)
214 {
215 addEndTime(elapsed() + offset);
216 }
217
animationMode() const218 SVGAnimationElement::AnimationMode SVGAnimationElement::animationMode() const
219 {
220 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
221 if (hasTagName(SVGNames::setTag))
222 return ToAnimation;
223 if (!animationPath().isEmpty())
224 return PathAnimation;
225 if (hasAttribute(SVGNames::valuesAttr))
226 return ValuesAnimation;
227 if (!toValue().isEmpty())
228 return fromValue().isEmpty() ? ToAnimation : FromToAnimation;
229 if (!byValue().isEmpty())
230 return fromValue().isEmpty() ? ByAnimation : FromByAnimation;
231 return NoAnimation;
232 }
233
calcMode() const234 SVGAnimationElement::CalcMode SVGAnimationElement::calcMode() const
235 {
236 DEFINE_STATIC_LOCAL(const AtomicString, discrete, ("discrete"));
237 DEFINE_STATIC_LOCAL(const AtomicString, linear, ("linear"));
238 DEFINE_STATIC_LOCAL(const AtomicString, paced, ("paced"));
239 DEFINE_STATIC_LOCAL(const AtomicString, spline, ("spline"));
240 const AtomicString& value = getAttribute(SVGNames::calcModeAttr);
241 if (value == discrete)
242 return CalcModeDiscrete;
243 if (value == linear)
244 return CalcModeLinear;
245 if (value == paced)
246 return CalcModePaced;
247 if (value == spline)
248 return CalcModeSpline;
249 return hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear;
250 }
251
attributeType() const252 SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const
253 {
254 DEFINE_STATIC_LOCAL(const AtomicString, css, ("CSS"));
255 DEFINE_STATIC_LOCAL(const AtomicString, xml, ("XML"));
256 const AtomicString& value = getAttribute(SVGNames::attributeTypeAttr);
257 if (value == css)
258 return AttributeTypeCSS;
259 if (value == xml)
260 return AttributeTypeXML;
261 return AttributeTypeAuto;
262 }
263
toValue() const264 String SVGAnimationElement::toValue() const
265 {
266 return getAttribute(SVGNames::toAttr);
267 }
268
byValue() const269 String SVGAnimationElement::byValue() const
270 {
271 return getAttribute(SVGNames::byAttr);
272 }
273
fromValue() const274 String SVGAnimationElement::fromValue() const
275 {
276 return getAttribute(SVGNames::fromAttr);
277 }
278
isAdditive() const279 bool SVGAnimationElement::isAdditive() const
280 {
281 DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum"));
282 const AtomicString& value = getAttribute(SVGNames::additiveAttr);
283 return value == sum || animationMode() == ByAnimation;
284 }
285
isAccumulated() const286 bool SVGAnimationElement::isAccumulated() const
287 {
288 DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum"));
289 const AtomicString& value = getAttribute(SVGNames::accumulateAttr);
290 return value == sum && animationMode() != ToAnimation;
291 }
292
isTargetAttributeCSSProperty(SVGElement * targetElement,const QualifiedName & attributeName)293 bool SVGAnimationElement::isTargetAttributeCSSProperty(SVGElement* targetElement, const QualifiedName& attributeName)
294 {
295 ASSERT(targetElement);
296 if (!targetElement->isStyled())
297 return false;
298
299 return SVGStyledElement::isAnimatableCSSProperty(attributeName);
300 }
301
setTargetAttributeAnimatedValue(const String & value)302 void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value)
303 {
304 if (!hasValidAttributeType())
305 return;
306 SVGElement* targetElement = this->targetElement();
307 QualifiedName attributeName = this->attributeName();
308 if (!targetElement || attributeName == anyQName() || value.isNull())
309 return;
310
311 // We don't want the instance tree to get rebuild. Instances are updated in the loop below.
312 if (targetElement->isStyled())
313 static_cast<SVGStyledElement*>(targetElement)->setInstanceUpdatesBlocked(true);
314
315 bool attributeIsCSSProperty = isTargetAttributeCSSProperty(targetElement, attributeName);
316 // Stop animation, if attributeType is set to CSS by the user, but the attribute itself is not a CSS property.
317 if (!attributeIsCSSProperty && attributeType() == AttributeTypeCSS)
318 return;
319
320 ExceptionCode ec;
321 if (attributeIsCSSProperty) {
322 // FIXME: This should set the override style, not the inline style.
323 // Sadly override styles are not yet implemented.
324 targetElement->style()->setProperty(attributeName.localName(), value, "", ec);
325 } else {
326 // FIXME: This should set the 'presentation' value, not the actual
327 // attribute value. Whatever that means in practice.
328 targetElement->setAttribute(attributeName, value, ec);
329 }
330
331 if (targetElement->isStyled())
332 static_cast<SVGStyledElement*>(targetElement)->setInstanceUpdatesBlocked(false);
333
334 // If the target element is used in an <use> instance tree, update that as well.
335 const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
336 const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
337 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
338 SVGElement* shadowTreeElement = (*it)->shadowTreeElement();
339 if (!shadowTreeElement)
340 continue;
341 if (attributeIsCSSProperty)
342 shadowTreeElement->style()->setProperty(attributeName.localName(), value, "", ec);
343 else
344 shadowTreeElement->setAttribute(attributeName, value, ec);
345 (*it)->correspondingUseElement()->setNeedsStyleRecalc();
346 }
347 }
348
calculateKeyTimesForCalcModePaced()349 void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
350 {
351 ASSERT(calcMode() == CalcModePaced);
352 ASSERT(animationMode() == ValuesAnimation);
353
354 unsigned valuesCount = m_values.size();
355 ASSERT(valuesCount > 1);
356 Vector<float> keyTimesForPaced;
357 float totalDistance = 0;
358 keyTimesForPaced.append(0);
359 for (unsigned n = 0; n < valuesCount - 1; ++n) {
360 // Distance in any units
361 float distance = calculateDistance(m_values[n], m_values[n + 1]);
362 if (distance < 0)
363 return;
364 totalDistance += distance;
365 keyTimesForPaced.append(distance);
366 }
367 if (!totalDistance)
368 return;
369
370 // Normalize.
371 for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n)
372 keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance;
373 keyTimesForPaced[keyTimesForPaced.size() - 1] = 1;
374
375 // Use key times calculated based on pacing instead of the user provided ones.
376 m_keyTimes.swap(keyTimesForPaced);
377 }
378
solveEpsilon(double duration)379 static inline double solveEpsilon(double duration) { return 1 / (200 * duration); }
380
calculateKeyTimesIndex(float percent) const381 unsigned SVGAnimationElement::calculateKeyTimesIndex(float percent) const
382 {
383 unsigned index;
384 unsigned keyTimesCount = m_keyTimes.size();
385 for (index = 1; index < keyTimesCount; ++index) {
386 if (m_keyTimes[index] >= percent)
387 break;
388 }
389 return --index;
390 }
391
calculatePercentForSpline(float percent,unsigned splineIndex) const392 float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
393 {
394 ASSERT(calcMode() == CalcModeSpline);
395 ASSERT(splineIndex < m_keySplines.size());
396 UnitBezier bezier = m_keySplines[splineIndex];
397 SMILTime duration = simpleDuration();
398 if (!duration.isFinite())
399 duration = 100.0;
400 return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value())));
401 }
402
calculatePercentFromKeyPoints(float percent) const403 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
404 {
405 ASSERT(!m_keyPoints.isEmpty());
406 ASSERT(calcMode() != CalcModePaced);
407 ASSERT(m_keyTimes.size() > 1);
408 ASSERT(m_keyPoints.size() == m_keyTimes.size());
409
410 unsigned index = calculateKeyTimesIndex(percent);
411 float fromPercent = m_keyTimes[index];
412 float toPercent = m_keyTimes[index + 1];
413 float fromKeyPoint = m_keyPoints[index];
414 float toKeyPoint = m_keyPoints[index + 1];
415
416 if (calcMode() == CalcModeDiscrete)
417 return percent == 1 ? toKeyPoint : fromKeyPoint;
418
419 float keyPointPercent = percent == 1 ? 1 : (percent - fromPercent) / (toPercent - fromPercent);
420
421 if (calcMode() == CalcModeSpline) {
422 ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
423 keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
424 }
425 return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
426 }
427
currentValuesFromKeyPoints(float percent,float & effectivePercent,String & from,String & to) const428 void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const
429 {
430 ASSERT(!m_keyPoints.isEmpty());
431 ASSERT(m_keyPoints.size() == m_keyTimes.size());
432 ASSERT(calcMode() != CalcModePaced);
433 effectivePercent = calculatePercentFromKeyPoints(percent);
434 unsigned index = effectivePercent == 1 ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1));
435 from = m_values[index];
436 to = m_values[index + 1];
437 }
438
currentValuesForValuesAnimation(float percent,float & effectivePercent,String & from,String & to) const439 void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to) const
440 {
441 unsigned valuesCount = m_values.size();
442 ASSERT(m_animationValid);
443 ASSERT(valuesCount > 1);
444
445 CalcMode calcMode = this->calcMode();
446 if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced)
447 return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
448
449 unsigned keyTimesCount = m_keyTimes.size();
450 ASSERT(!keyTimesCount || valuesCount == keyTimesCount);
451 ASSERT(!keyTimesCount || (keyTimesCount > 1 && !m_keyTimes[0]));
452
453 unsigned index = calculateKeyTimesIndex(percent);
454 if (calcMode == CalcModeDiscrete) {
455 if (!keyTimesCount)
456 index = percent == 1 ? valuesCount - 1 : static_cast<unsigned>(percent * valuesCount);
457 from = m_values[index];
458 to = m_values[index];
459 effectivePercent = 0;
460 return;
461 }
462
463 float fromPercent;
464 float toPercent;
465 if (keyTimesCount) {
466 fromPercent = m_keyTimes[index];
467 toPercent = m_keyTimes[index + 1];
468 } else {
469 index = static_cast<unsigned>(percent * (valuesCount - 1));
470 fromPercent = static_cast<float>(index) / (valuesCount - 1);
471 toPercent = static_cast<float>(index + 1) / (valuesCount - 1);
472 }
473
474 if (index == valuesCount - 1)
475 --index;
476 from = m_values[index];
477 to = m_values[index + 1];
478 ASSERT(toPercent > fromPercent);
479 effectivePercent = percent == 1 ? 1 : (percent - fromPercent) / (toPercent - fromPercent);
480
481 if (calcMode == CalcModeSpline) {
482 ASSERT(m_keySplines.size() == m_values.size() - 1);
483 effectivePercent = calculatePercentForSpline(effectivePercent, index);
484 }
485 }
486
startedActiveInterval()487 void SVGAnimationElement::startedActiveInterval()
488 {
489 m_animationValid = false;
490
491 if (!hasValidAttributeType())
492 return;
493
494 // These validations are appropriate for all animation modes.
495 if (hasAttribute(SVGNames::keyPointsAttr) && m_keyPoints.size() != m_keyTimes.size())
496 return;
497
498 AnimationMode animationMode = this->animationMode();
499 CalcMode calcMode = this->calcMode();
500 if (calcMode == CalcModeSpline) {
501 unsigned splinesCount = m_keySplines.size() + 1;
502 if ((hasAttribute(SVGNames::keyPointsAttr) && m_keyPoints.size() != splinesCount)
503 || (animationMode == ValuesAnimation && m_values.size() != splinesCount))
504 return;
505 }
506
507 String from = fromValue();
508 String to = toValue();
509 String by = byValue();
510 if (animationMode == NoAnimation)
511 return;
512 if (animationMode == FromToAnimation)
513 m_animationValid = calculateFromAndToValues(from, to);
514 else if (animationMode == ToAnimation) {
515 // For to-animations the from value is the current accumulated value from lower priority animations.
516 // The value is not static and is determined during the animation.
517 m_animationValid = calculateFromAndToValues(String(), to);
518 } else if (animationMode == FromByAnimation)
519 m_animationValid = calculateFromAndByValues(from, by);
520 else if (animationMode == ByAnimation)
521 m_animationValid = calculateFromAndByValues(String(), by);
522 else if (animationMode == ValuesAnimation) {
523 m_animationValid = m_values.size() > 1
524 && (calcMode == CalcModePaced || !hasAttribute(SVGNames::keyTimesAttr) || hasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
525 && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1)
526 && (calcMode != CalcModeSpline || ((m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1)) || m_keySplines.size() == m_keyPoints.size() - 1))
527 && (!hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
528 if (calcMode == CalcModePaced && m_animationValid)
529 calculateKeyTimesForCalcModePaced();
530 } else if (animationMode == PathAnimation)
531 m_animationValid = calcMode == CalcModePaced || !hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
532 }
533
updateAnimation(float percent,unsigned repeat,SVGSMILElement * resultElement)534 void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement* resultElement)
535 {
536 if (!m_animationValid)
537 return;
538
539 float effectivePercent;
540 CalcMode mode = calcMode();
541 if (animationMode() == ValuesAnimation) {
542 String from;
543 String to;
544 currentValuesForValuesAnimation(percent, effectivePercent, from, to);
545 if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo) {
546 m_animationValid = calculateFromAndToValues(from, to);
547 if (!m_animationValid)
548 return;
549 m_lastValuesAnimationFrom = from;
550 m_lastValuesAnimationTo = to;
551 }
552 } else if (!m_keyPoints.isEmpty() && mode != CalcModePaced)
553 effectivePercent = calculatePercentFromKeyPoints(percent);
554 else if (m_keyPoints.isEmpty() && mode == CalcModeSpline && m_keyTimes.size() > 1)
555 effectivePercent = calculatePercentForSpline(percent, calculateKeyTimesIndex(percent));
556 else
557 effectivePercent = percent;
558
559 calculateAnimatedValue(effectivePercent, repeat, resultElement);
560 }
561
endedActiveInterval()562 void SVGAnimationElement::endedActiveInterval()
563 {
564 }
565
566 }
567 #endif // ENABLE(SVG_ANIMATION)
568
569