don't discard iframe children.
[kdelibs.git] / khtml / svg / SVGAnimationElement.cpp
blob0fdb7042aa901dd4b1e8de9b4a04eca8361ef5f5
1 /*
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.
25 #include "config.h"
26 #if ENABLE(SVG_ANIMATION)
27 #include "SVGAnimationElement.h"
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 "SVGElementInstance.h"
38 #include "SVGNames.h"
39 #include "SVGURIReference.h"
40 #include "SVGUseElement.h"
41 #include "XLinkNames.h"
42 #include <math.h>
44 using namespace std;
46 namespace WebCore {
48 SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* doc)
49 : SVGSMILElement(tagName, doc)
50 , SVGTests()
51 , SVGExternalResourcesRequired()
52 , m_animationValid(false)
56 SVGAnimationElement::~SVGAnimationElement()
60 static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder)
62 result.clear();
63 Vector<String> parseList;
64 parse.split(';', parseList);
65 for (unsigned n = 0; n < parseList.size(); ++n) {
66 String timeString = parseList[n];
67 bool ok;
68 float time = timeString.toFloat(&ok);
69 if (!ok || time < 0 || time > 1.f)
70 goto fail;
71 if (verifyOrder) {
72 if (!n) {
73 if (time != 0)
74 goto fail;
75 } else if (time < result.last())
76 goto fail;
78 result.append(time);
80 return;
81 fail:
82 result.clear();
85 static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
87 result.clear();
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)
97 goto fail;
98 double curveValues[4];
99 for (unsigned i = 0; i < 4; ++i) {
100 String parseNumber = parseSpline[i];
101 bool ok;
102 curveValues[i] = parseNumber.toDouble(&ok);
103 if (!ok || curveValues[i] < 0.0 || curveValues[i] > 1.0)
104 goto fail;
106 result.append(UnitBezier(curveValues[0], curveValues[1], curveValues[2], curveValues[3]));
108 return;
109 fail:
110 result.clear();
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);
125 else {
126 if (SVGTests::parseMappedAttribute(attr))
127 return;
128 if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
129 return;
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);
164 return true;
167 bool SVGAnimationElement::endElement(ExceptionCode& ec)
169 return endElementAt(0, ec);
172 bool SVGAnimationElement::endElementAt(float offset, ExceptionCode& ec)
174 if (offset < 0)
175 return false;
177 addEndTime(elapsed() + offset);
178 return true;
181 SVGAnimationElement::AnimationMode SVGAnimationElement::animationMode() const
183 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
184 if (hasTagName(SVGNames::setTag))
185 return ToAnimation;
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;
194 return NoAnimation;
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;
206 if (value == linear)
207 return CalcModeLinear;
208 if (value == paced)
209 return CalcModePaced;
210 if (value == spline)
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);
220 if (value == css)
221 return AttributeTypeCSS;
222 if (value == xml)
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);
266 // SVG range
267 if (id >= CSSPropertyClipPath && id <= CSSPropertyWritingMode)
268 return true;
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)
279 return true;
280 if (type == AttributeTypeXML)
281 return false;
282 return attributeIsCSS(attributeName());
285 void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value)
287 if (!hasValidTarget())
288 return;
289 SVGElement* target = targetElement();
290 String attributeName = this->attributeName();
291 if (!target || attributeName.isEmpty() || value.isNull())
292 return;
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);
298 ExceptionCode ec;
299 bool isCSS = targetAttributeIsCSS();
300 if (isCSS) {
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);
304 } else {
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);
315 if (!instances)
316 return;
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);
321 if (isCSS)
322 shadowTreeElement->style()->setProperty(attributeName, value, "", ec);
323 else
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]);
342 if (distance < 0)
343 return;
344 totalDistance += distance;
345 keyTimesForPaced.append(distance);
347 if (!totalDistance)
348 return;
350 // Normalize.
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())
368 duration = 100.0;
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);
380 unsigned index;
381 for (index = 1; index < keyTimesCount; ++index) {
382 if (m_keyTimes[index] >= percent)
383 break;
385 --index;
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));
429 unsigned index;
430 for (index = 1; index < keyTimesCount; ++index) {
431 if (m_keyTimes[index] >= percent)
432 break;
434 --index;
436 if (calcMode == CalcModeDiscrete) {
437 if (!keyTimesCount)
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;
442 return;
445 float fromPercent;
446 float toPercent;
447 if (keyTimesCount) {
448 fromPercent = m_keyTimes[index];
449 toPercent = m_keyTimes[index + 1];
450 } else {
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)
457 --index;
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())
474 return;
476 AnimationMode animationMode = this->animationMode();
477 if (animationMode == NoAnimation)
478 return;
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)
505 return;
507 float effectivePercent;
508 if (animationMode() == ValuesAnimation) {
509 String from;
510 String to;
511 currentValuesForValuesAnimation(percent, effectivePercent, from, to);
512 if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo ) {
513 m_animationValid = calculateFromAndToValues(from, to);
514 if (!m_animationValid)
515 return;
516 m_lastValuesAnimationFrom = from;
517 m_lastValuesAnimationTo = to;
519 } else if (!m_keyPoints.isEmpty() && calcMode() != CalcModePaced)
520 effectivePercent = calculatePercentFromKeyPoints(percent);
521 else
522 effectivePercent = percent;
524 calculateAnimatedValue(effectivePercent, repeat, resultElement);
527 void SVGAnimationElement::endedActiveInterval()
533 // vim:ts=4:noet
534 #endif // ENABLE(SVG_ANIMATION)