1 /*
2 Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@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.
6 Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au>
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22 */
23
24 #include "config.h"
25
26 #if ENABLE(SVG_ANIMATION)
27 #include "SVGAnimationElement.h"
28
29 #include "CSSComputedStyleDeclaration.h"
30 #include "CSSParser.h"
31 #include "CSSPropertyNames.h"
32 #include "Document.h"
33 #include "Event.h"
34 #include "EventListener.h"
35 #include "FloatConversion.h"
36 #include "HTMLNames.h"
37 #include "MappedAttribute.h"
38 #include "SVGElementInstance.h"
39 #include "SVGNames.h"
40 #include "SVGURIReference.h"
41 #include "SVGUseElement.h"
42 #include "XLinkNames.h"
43 #include <math.h>
44 #include <wtf/StdLibExtras.h>
45
46 using namespace std;
47
48 namespace WebCore {
49
SVGAnimationElement(const QualifiedName & tagName,Document * doc)50 SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* doc)
51 : SVGSMILElement(tagName, doc)
52 , SVGTests()
53 , SVGExternalResourcesRequired()
54 , m_animationValid(false)
55 {
56 }
57
~SVGAnimationElement()58 SVGAnimationElement::~SVGAnimationElement()
59 {
60 }
61
parseKeyTimes(const String & parse,Vector<float> & result,bool verifyOrder)62 static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder)
63 {
64 result.clear();
65 Vector<String> parseList;
66 parse.split(';', parseList);
67 for (unsigned n = 0; n < parseList.size(); ++n) {
68 String timeString = parseList[n];
69 bool ok;
70 float time = timeString.toFloat(&ok);
71 if (!ok || time < 0 || time > 1.f)
72 goto fail;
73 if (verifyOrder) {
74 if (!n) {
75 if (time != 0)
76 goto fail;
77 } else if (time < result.last())
78 goto fail;
79 }
80 result.append(time);
81 }
82 return;
83 fail:
84 result.clear();
85 }
86
parseKeySplines(const String & parse,Vector<UnitBezier> & result)87 static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
88 {
89 result.clear();
90 Vector<String> parseList;
91 parse.split(';', parseList);
92 for (unsigned n = 0; n < parseList.size(); ++n) {
93 Vector<String> parseSpline;
94 parseList[n].split(',', parseSpline);
95 // The spec says the sepator is a space, all tests use commas. Weird.
96 if (parseSpline.size() == 1)
97 parseList[n].split(' ', parseSpline);
98 if (parseSpline.size() != 4)
99 goto fail;
100 double curveValues[4];
101 for (unsigned i = 0; i < 4; ++i) {
102 String parseNumber = parseSpline[i];
103 bool ok;
104 curveValues[i] = parseNumber.toDouble(&ok);
105 if (!ok || curveValues[i] < 0.0 || curveValues[i] > 1.0)
106 goto fail;
107 }
108 result.append(UnitBezier(curveValues[0], curveValues[1], curveValues[2], curveValues[3]));
109 }
110 return;
111 fail:
112 result.clear();
113 }
114
parseMappedAttribute(MappedAttribute * attr)115 void SVGAnimationElement::parseMappedAttribute(MappedAttribute* attr)
116 {
117 if (attr->name() == SVGNames::valuesAttr)
118 attr->value().string().split(';', m_values);
119 else if (attr->name() == SVGNames::keyTimesAttr)
120 parseKeyTimes(attr->value(), m_keyTimes, true);
121 else if (attr->name() == SVGNames::keyPointsAttr && hasTagName(SVGNames::animateMotionTag)) {
122 // This is specified to be an animateMotion attribute only but it is simpler to put it here
123 // where the other timing calculatations are.
124 parseKeyTimes(attr->value(), m_keyPoints, false);
125 } else if (attr->name() == SVGNames::keySplinesAttr)
126 parseKeySplines(attr->value(), m_keySplines);
127 else {
128 if (SVGTests::parseMappedAttribute(attr))
129 return;
130 if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
131 return;
132 SVGSMILElement::parseMappedAttribute(attr);
133 }
134 }
135
attributeChanged(Attribute * attr,bool preserveDecls)136 void SVGAnimationElement::attributeChanged(Attribute* attr, bool preserveDecls)
137 {
138 // Assumptions may not hold after an attribute change.
139 m_animationValid = false;
140 SVGSMILElement::attributeChanged(attr, preserveDecls);
141 }
142
synchronizeProperty(const QualifiedName & attrName)143 void SVGAnimationElement::synchronizeProperty(const QualifiedName& attrName)
144 {
145 SVGSMILElement::synchronizeProperty(attrName);
146
147 if (attrName == anyQName() || SVGExternalResourcesRequired::isKnownAttribute(attrName))
148 synchronizeExternalResourcesRequired();
149 }
150
getStartTime() const151 float SVGAnimationElement::getStartTime() const
152 {
153 return narrowPrecisionToFloat(intervalBegin().value());
154 }
155
getCurrentTime() const156 float SVGAnimationElement::getCurrentTime() const
157 {
158 return narrowPrecisionToFloat(elapsed().value());
159 }
160
getSimpleDuration(ExceptionCode &) const161 float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const
162 {
163 return narrowPrecisionToFloat(simpleDuration().value());
164 }
165
beginElement()166 void SVGAnimationElement::beginElement()
167 {
168 beginElementAt(0);
169 }
170
beginElementAt(float offset)171 void SVGAnimationElement::beginElementAt(float offset)
172 {
173 addBeginTime(elapsed() + offset);
174 }
175
endElement()176 void SVGAnimationElement::endElement()
177 {
178 endElementAt(0);
179 }
180
endElementAt(float offset)181 void SVGAnimationElement::endElementAt(float offset)
182 {
183 addEndTime(elapsed() + offset);
184 }
185
animationMode() const186 SVGAnimationElement::AnimationMode SVGAnimationElement::animationMode() const
187 {
188 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
189 if (hasTagName(SVGNames::setTag))
190 return ToAnimation;
191 if (!animationPath().isEmpty())
192 return PathAnimation;
193 if (hasAttribute(SVGNames::valuesAttr))
194 return ValuesAnimation;
195 if (!toValue().isEmpty())
196 return fromValue().isEmpty() ? ToAnimation : FromToAnimation;
197 if (!byValue().isEmpty())
198 return fromValue().isEmpty() ? ByAnimation : FromByAnimation;
199 return NoAnimation;
200 }
201
calcMode() const202 SVGAnimationElement::CalcMode SVGAnimationElement::calcMode() const
203 {
204 DEFINE_STATIC_LOCAL(const AtomicString, discrete, ("discrete"));
205 DEFINE_STATIC_LOCAL(const AtomicString, linear, ("linear"));
206 DEFINE_STATIC_LOCAL(const AtomicString, paced, ("paced"));
207 DEFINE_STATIC_LOCAL(const AtomicString, spline, ("spline"));
208 const AtomicString& value = getAttribute(SVGNames::calcModeAttr);
209 if (value == discrete)
210 return CalcModeDiscrete;
211 if (value == linear)
212 return CalcModeLinear;
213 if (value == paced)
214 return CalcModePaced;
215 if (value == spline)
216 return CalcModeSpline;
217 return hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear;
218 }
219
attributeType() const220 SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const
221 {
222 DEFINE_STATIC_LOCAL(const AtomicString, css, ("CSS"));
223 DEFINE_STATIC_LOCAL(const AtomicString, xml, ("XML"));
224 const AtomicString& value = getAttribute(SVGNames::attributeTypeAttr);
225 if (value == css)
226 return AttributeTypeCSS;
227 if (value == xml)
228 return AttributeTypeXML;
229 return AttributeTypeAuto;
230 }
231
toValue() const232 String SVGAnimationElement::toValue() const
233 {
234 return getAttribute(SVGNames::toAttr);
235 }
236
byValue() const237 String SVGAnimationElement::byValue() const
238 {
239 return getAttribute(SVGNames::byAttr);
240 }
241
fromValue() const242 String SVGAnimationElement::fromValue() const
243 {
244 return getAttribute(SVGNames::fromAttr);
245 }
246
isAdditive() const247 bool SVGAnimationElement::isAdditive() const
248 {
249 DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum"));
250 const AtomicString& value = getAttribute(SVGNames::additiveAttr);
251 return value == sum || animationMode() == ByAnimation;
252 }
253
isAccumulated() const254 bool SVGAnimationElement::isAccumulated() const
255 {
256 DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum"));
257 const AtomicString& value = getAttribute(SVGNames::accumulateAttr);
258 return value == sum && animationMode() != ToAnimation;
259 }
260
hasValidTarget() const261 bool SVGAnimationElement::hasValidTarget() const
262 {
263 return targetElement();
264 }
265
attributeIsCSS(const String & attributeName)266 bool SVGAnimationElement::attributeIsCSS(const String& attributeName)
267 {
268 // FIXME: We should have a map of all SVG properties and their attribute types so we
269 // could validate animations better. The spec is very vague about this.
270 unsigned id = cssPropertyID(attributeName);
271 // SVG range
272 if (id >= CSSPropertyClipPath && id <= CSSPropertyWritingMode)
273 return true;
274 // Regular CSS properties also in SVG
275 return id == CSSPropertyColor || id == CSSPropertyDisplay || id == CSSPropertyOpacity
276 || (id >= CSSPropertyFont && id <= CSSPropertyFontWeight)
277 || id == CSSPropertyOverflow || id == CSSPropertyVisibility;
278 }
279
targetAttributeIsCSS() const280 bool SVGAnimationElement::targetAttributeIsCSS() const
281 {
282 AttributeType type = attributeType();
283 if (type == AttributeTypeCSS)
284 return true;
285 if (type == AttributeTypeXML)
286 return false;
287 return attributeIsCSS(attributeName());
288 }
289
setTargetAttributeAnimatedValue(const String & value)290 void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value)
291 {
292 if (!hasValidTarget())
293 return;
294 SVGElement* target = targetElement();
295 String attributeName = this->attributeName();
296 if (!target || attributeName.isEmpty() || value.isNull())
297 return;
298
299 // We don't want the instance tree to get rebuild. Instances are updated in the loop below.
300 if (target->isStyled())
301 static_cast<SVGStyledElement*>(target)->setInstanceUpdatesBlocked(true);
302
303 ExceptionCode ec;
304 bool isCSS = targetAttributeIsCSS();
305 if (isCSS) {
306 // FIXME: This should set the override style, not the inline style.
307 // Sadly override styles are not yet implemented.
308 target->style()->setProperty(attributeName, value, "", ec);
309 } else {
310 // FIXME: This should set the 'presentation' value, not the actual
311 // attribute value. Whatever that means in practice.
312 target->setAttribute(attributeName, value, ec);
313 }
314
315 if (target->isStyled())
316 static_cast<SVGStyledElement*>(target)->setInstanceUpdatesBlocked(false);
317
318 // If the target element is used in an <use> instance tree, update that as well.
319 const HashSet<SVGElementInstance*>& instances = target->instancesForElement();
320 const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
321 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
322 SVGElement* shadowTreeElement = (*it)->shadowTreeElement();
323 ASSERT(shadowTreeElement);
324 if (isCSS)
325 shadowTreeElement->style()->setProperty(attributeName, value, "", ec);
326 else
327 shadowTreeElement->setAttribute(attributeName, value, ec);
328 (*it)->correspondingUseElement()->setNeedsStyleRecalc();
329 }
330 }
331
calculateKeyTimesForCalcModePaced()332 void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
333 {
334 ASSERT(calcMode() == CalcModePaced);
335 ASSERT(animationMode() == ValuesAnimation);
336
337 unsigned valuesCount = m_values.size();
338 ASSERT(valuesCount > 1);
339 Vector<float> keyTimesForPaced;
340 float totalDistance = 0;
341 keyTimesForPaced.append(0);
342 for (unsigned n = 0; n < valuesCount - 1; ++n) {
343 // Distance in any units
344 float distance = calculateDistance(m_values[n], m_values[n + 1]);
345 if (distance < 0)
346 return;
347 totalDistance += distance;
348 keyTimesForPaced.append(distance);
349 }
350 if (!totalDistance)
351 return;
352
353 // Normalize.
354 for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n)
355 keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance;
356 keyTimesForPaced[keyTimesForPaced.size() - 1] = 1.f;
357
358 // Use key times calculated based on pacing instead of the user provided ones.
359 m_keyTimes.swap(keyTimesForPaced);
360 }
361
solveEpsilon(double duration)362 static inline double solveEpsilon(double duration) { return 1. / (200. * duration); }
363
calculatePercentForSpline(float percent,unsigned splineIndex) const364 float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
365 {
366 ASSERT(calcMode() == CalcModeSpline);
367 ASSERT(splineIndex < m_keySplines.size());
368 UnitBezier bezier = m_keySplines[splineIndex];
369 SMILTime duration = simpleDuration();
370 if (!duration.isFinite())
371 duration = 100.0;
372 return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value())));
373 }
374
calculatePercentFromKeyPoints(float percent) const375 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
376 {
377 ASSERT(!m_keyPoints.isEmpty());
378 ASSERT(calcMode() != CalcModePaced);
379 unsigned keyTimesCount = m_keyTimes.size();
380 ASSERT(keyTimesCount > 1);
381 ASSERT(m_keyPoints.size() == keyTimesCount);
382
383 unsigned index;
384 for (index = 1; index < keyTimesCount; ++index) {
385 if (m_keyTimes[index] >= percent)
386 break;
387 }
388 --index;
389
390 float fromPercent = m_keyTimes[index];
391 float toPercent = m_keyTimes[index + 1];
392 float fromKeyPoint = m_keyPoints[index];
393 float toKeyPoint = m_keyPoints[index + 1];
394
395 if (calcMode() == CalcModeDiscrete)
396 return percent == 1.0f ? toKeyPoint : fromKeyPoint;
397
398 float keyPointPercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent);
399
400 if (calcMode() == CalcModeSpline) {
401 ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
402 keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
403 }
404 return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
405 }
406
currentValuesFromKeyPoints(float percent,float & effectivePercent,String & from,String & to) const407 void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const
408 {
409 ASSERT(!m_keyPoints.isEmpty());
410 ASSERT(m_keyPoints.size() == m_keyTimes.size());
411 ASSERT(calcMode() != CalcModePaced);
412 effectivePercent = calculatePercentFromKeyPoints(percent);
413 unsigned index = effectivePercent == 1.0f ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1));
414 from = m_values[index];
415 to = m_values[index + 1];
416 }
417
currentValuesForValuesAnimation(float percent,float & effectivePercent,String & from,String & to) const418 void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to) const
419 {
420 unsigned valuesCount = m_values.size();
421 ASSERT(m_animationValid);
422 ASSERT(valuesCount > 1);
423
424 CalcMode calcMode = this->calcMode();
425 if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced)
426 return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
427
428 unsigned keyTimesCount = m_keyTimes.size();
429 ASSERT(!keyTimesCount || valuesCount == keyTimesCount);
430 ASSERT(!keyTimesCount || (keyTimesCount > 1 && m_keyTimes[0] == 0));
431
432 unsigned index;
433 for (index = 1; index < keyTimesCount; ++index) {
434 if (m_keyTimes[index] >= percent)
435 break;
436 }
437 --index;
438
439 if (calcMode == CalcModeDiscrete) {
440 if (!keyTimesCount)
441 index = percent == 1.0f ? valuesCount - 1 : static_cast<unsigned>(percent * valuesCount);
442 from = m_values[index];
443 to = m_values[index];
444 effectivePercent = 0.0f;
445 return;
446 }
447
448 float fromPercent;
449 float toPercent;
450 if (keyTimesCount) {
451 fromPercent = m_keyTimes[index];
452 toPercent = m_keyTimes[index + 1];
453 } else {
454 index = static_cast<unsigned>(percent * (valuesCount - 1));
455 fromPercent = static_cast<float>(index) / (valuesCount - 1);
456 toPercent = static_cast<float>(index + 1) / (valuesCount - 1);
457 }
458
459 if (index == valuesCount - 1)
460 --index;
461 from = m_values[index];
462 to = m_values[index + 1];
463 ASSERT(toPercent > fromPercent);
464 effectivePercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent);
465
466 if (calcMode == CalcModeSpline) {
467 ASSERT(m_keySplines.size() == m_values.size() - 1);
468 effectivePercent = calculatePercentForSpline(effectivePercent, index);
469 }
470 }
471
startedActiveInterval()472 void SVGAnimationElement::startedActiveInterval()
473 {
474 m_animationValid = false;
475
476 if (!hasValidTarget())
477 return;
478
479 AnimationMode animationMode = this->animationMode();
480 if (animationMode == NoAnimation)
481 return;
482 if (animationMode == FromToAnimation)
483 m_animationValid = calculateFromAndToValues(fromValue(), toValue());
484 else if (animationMode == ToAnimation) {
485 // For to-animations the from value is the current accumulated value from lower priority animations.
486 // The value is not static and is determined during the animation.
487 m_animationValid = calculateFromAndToValues(String(), toValue());
488 } else if (animationMode == FromByAnimation)
489 m_animationValid = calculateFromAndByValues(fromValue(), byValue());
490 else if (animationMode == ByAnimation)
491 m_animationValid = calculateFromAndByValues(String(), byValue());
492 else if (animationMode == ValuesAnimation) {
493 CalcMode calcMode = this->calcMode();
494 m_animationValid = m_values.size() > 1
495 && (calcMode == CalcModePaced || !hasAttribute(SVGNames::keyTimesAttr) || hasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
496 && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1.0)
497 && (calcMode != CalcModeSpline || ((m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1)) || m_keySplines.size() == m_keyPoints.size() - 1))
498 && (!hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
499 if (calcMode == CalcModePaced && m_animationValid)
500 calculateKeyTimesForCalcModePaced();
501 } else if (animationMode == PathAnimation)
502 m_animationValid = calcMode() == CalcModePaced || !hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
503 }
504
updateAnimation(float percent,unsigned repeat,SVGSMILElement * resultElement)505 void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement* resultElement)
506 {
507 if (!m_animationValid)
508 return;
509
510 float effectivePercent;
511 if (animationMode() == ValuesAnimation) {
512 String from;
513 String to;
514 currentValuesForValuesAnimation(percent, effectivePercent, from, to);
515 if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo ) {
516 m_animationValid = calculateFromAndToValues(from, to);
517 if (!m_animationValid)
518 return;
519 m_lastValuesAnimationFrom = from;
520 m_lastValuesAnimationTo = to;
521 }
522 } else if (!m_keyPoints.isEmpty() && calcMode() != CalcModePaced)
523 effectivePercent = calculatePercentFromKeyPoints(percent);
524 else
525 effectivePercent = percent;
526
527 calculateAnimatedValue(effectivePercent, repeat, resultElement);
528 }
529
endedActiveInterval()530 void SVGAnimationElement::endedActiveInterval()
531 {
532 }
533
534 }
535
536 // vim:ts=4:noet
537 #endif // ENABLE(SVG_ANIMATION)
538
539