• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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