KHtml

SVGAnimationElement.cpp
1 /*
2  Copyright (C) 2004, 2005 Nikolas Zimmermann <[email protected]>
3  2004, 2005, 2006, 2007 Rob Buis <[email protected]>
4  Copyright (C) 2007 Eric Seidel <[email protected]>
5  Copyright (C) 2008 Apple Inc. All rights reserved.
6 
7  This file is part of the KDE project
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 #if ENABLE(SVG_ANIMATION)
26 #include "SVGAnimationElement.h"
27 
28 #include "CSSComputedStyleDeclaration.h"
29 #include "CSSParser.h"
30 #include "CSSPropertyNames.h"
31 #include "Document.h"
32 #include "Event.h"
33 #include "EventListener.h"
34 #include "FloatConversion.h"
35 #include "HTMLNames.h"
36 #include "SVGElementInstance.h"
37 #include "SVGNames.h"
38 #include "SVGURIReference.h"
39 #include "SVGUseElement.h"
40 #include "XLinkNames.h"
41 #include <math.h>
42 
43 using namespace std;
44 
45 namespace WebCore
46 {
47 
48 SVGAnimationElement::SVGAnimationElement(const QualifiedName &tagName, Document *doc)
49  : SVGSMILElement(tagName, doc)
50  , SVGTests()
51  , SVGExternalResourcesRequired()
52  , m_animationValid(false)
53 {
54 }
55 
56 SVGAnimationElement::~SVGAnimationElement()
57 {
58 }
59 
60 static void parseKeyTimes(const String &parse, Vector<float> &result, bool verifyOrder)
61 {
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  }
72  if (verifyOrder) {
73  if (!n) {
74  if (time != 0) {
75  goto fail;
76  }
77  } else if (time < result.last()) {
78  goto fail;
79  }
80  }
81  result.append(time);
82  }
83  return;
84 fail:
85  result.clear();
86 }
87 
88 static void parseKeySplines(const String &parse, Vector<UnitBezier> &result)
89 {
90  result.clear();
91  Vector<String> parseList;
92  parse.split(';', parseList);
93  for (unsigned n = 0; n < parseList.size(); ++n) {
94  Vector<String> parseSpline;
95  parseList[n].split(',', parseSpline);
96  // The spec says the sepator is a space, all tests use commas. Weird.
97  if (parseSpline.size() == 1) {
98  parseList[n].split(' ', parseSpline);
99  }
100  if (parseSpline.size() != 4) {
101  goto fail;
102  }
103  double curveValues[4];
104  for (unsigned i = 0; i < 4; ++i) {
105  String parseNumber = parseSpline[i];
106  bool ok;
107  curveValues[i] = parseNumber.toDouble(&ok);
108  if (!ok || curveValues[i] < 0.0 || curveValues[i] > 1.0) {
109  goto fail;
110  }
111  }
112  result.append(UnitBezier(curveValues[0], curveValues[1], curveValues[2], curveValues[3]));
113  }
114  return;
115 fail:
116  result.clear();
117 }
118 
119 void SVGAnimationElement::parseMappedAttribute(MappedAttribute *attr)
120 {
121  if (attr->name() == SVGNames::valuesAttr) {
122  attr->value().string().split(';', m_values);
123  } else if (attr->name() == SVGNames::keyTimesAttr) {
124  parseKeyTimes(attr->value(), m_keyTimes, true);
125  } else if (attr->name() == SVGNames::keyPointsAttr && hasTagName(SVGNames::animateMotionTag)) {
126  // This is specified to be an animateMotion attribute only but it is simpler to put it here
127  // where the other timing calculatations are.
128  parseKeyTimes(attr->value(), m_keyPoints, false);
129  } else if (attr->name() == SVGNames::keySplinesAttr) {
130  parseKeySplines(attr->value(), m_keySplines);
131  } else {
132  if (SVGTests::parseMappedAttribute(attr)) {
133  return;
134  }
135  if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) {
136  return;
137  }
138  SVGSMILElement::parseMappedAttribute(attr);
139  }
140 }
141 
142 void SVGAnimationElement::attributeChanged(Attribute *attr, bool preserveDecls)
143 {
144  // Assumptions may not hold after an attribute change.
145  m_animationValid = false;
146  SVGSMILElement::attributeChanged(attr, preserveDecls);
147 }
148 
149 float SVGAnimationElement::getStartTime() const
150 {
151  return narrowPrecisionToFloat(intervalBegin().value());
152 }
153 
154 float SVGAnimationElement::getCurrentTime() const
155 {
156  return narrowPrecisionToFloat(elapsed().value());
157 }
158 
159 float SVGAnimationElement::getSimpleDuration(ExceptionCode &) const
160 {
161  return narrowPrecisionToFloat(simpleDuration().value());
162 }
163 
164 bool SVGAnimationElement::beginElement(ExceptionCode &ec)
165 {
166  return beginElementAt(0, ec);
167 }
168 
169 bool SVGAnimationElement::beginElementAt(float offset, ExceptionCode &ec)
170 {
171  addBeginTime(elapsed() + offset);
172  return true;
173 }
174 
175 bool SVGAnimationElement::endElement(ExceptionCode &ec)
176 {
177  return endElementAt(0, ec);
178 }
179 
180 bool SVGAnimationElement::endElementAt(float offset, ExceptionCode &ec)
181 {
182  if (offset < 0) {
183  return false;
184  }
185 
186  addEndTime(elapsed() + offset);
187  return true;
188 }
189 
190 SVGAnimationElement::AnimationMode SVGAnimationElement::animationMode() const
191 {
192  // https://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
193  if (hasTagName(SVGNames::setTag)) {
194  return ToAnimation;
195  }
196  if (!animationPath().isEmpty()) {
197  return PathAnimation;
198  }
199  if (hasAttribute(SVGNames::valuesAttr)) {
200  return ValuesAnimation;
201  }
202  if (!toValue().isEmpty()) {
203  return fromValue().isEmpty() ? ToAnimation : FromToAnimation;
204  }
205  if (!byValue().isEmpty()) {
206  return fromValue().isEmpty() ? ByAnimation : FromByAnimation;
207  }
208  return NoAnimation;
209 }
210 
211 SVGAnimationElement::CalcMode SVGAnimationElement::calcMode() const
212 {
213  static const AtomicString discrete("discrete");
214  static const AtomicString linear("linear");
215  static const AtomicString paced("paced");
216  static const AtomicString spline("spline");
217  const AtomicString &value = getAttribute(SVGNames::calcModeAttr);
218  if (value == discrete) {
219  return CalcModeDiscrete;
220  }
221  if (value == linear) {
222  return CalcModeLinear;
223  }
224  if (value == paced) {
225  return CalcModePaced;
226  }
227  if (value == spline) {
228  return CalcModeSpline;
229  }
230  return hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear;
231 }
232 
233 SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const
234 {
235  static const AtomicString css("CSS");
236  static const AtomicString xml("XML");
237  const AtomicString &value = getAttribute(SVGNames::attributeTypeAttr);
238  if (value == css) {
239  return AttributeTypeCSS;
240  }
241  if (value == xml) {
242  return AttributeTypeXML;
243  }
244  return AttributeTypeAuto;
245 }
246 
247 String SVGAnimationElement::toValue() const
248 {
249  return getAttribute(SVGNames::toAttr);
250 }
251 
252 String SVGAnimationElement::byValue() const
253 {
254  return getAttribute(SVGNames::byAttr);
255 }
256 
257 String SVGAnimationElement::fromValue() const
258 {
259  return getAttribute(SVGNames::fromAttr);
260 }
261 
262 bool SVGAnimationElement::isAdditive() const
263 {
264  static const AtomicString sum("sum");
265  const AtomicString &value = getAttribute(SVGNames::additiveAttr);
266  return value == sum || animationMode() == ByAnimation;
267 }
268 
269 bool SVGAnimationElement::isAccumulated() const
270 {
271  static const AtomicString sum("sum");
272  const AtomicString &value = getAttribute(SVGNames::accumulateAttr);
273  return value == sum && animationMode() != ToAnimation;
274 }
275 
276 bool SVGAnimationElement::hasValidTarget() const
277 {
278  return targetElement();
279 }
280 
281 bool SVGAnimationElement::attributeIsCSS(const String &attributeName)
282 {
283  // FIXME: We should have a map of all SVG properties and their attribute types so we
284  // could validate animations better. The spec is very vague about this.
285  unsigned id = cssPropertyID(attributeName);
286  // SVG range
287  if (id >= CSSPropertyClipPath && id <= CSSPropertyWritingMode) {
288  return true;
289  }
290  // Regular CSS properties also in SVG
291  return id == CSSPropertyColor || id == CSSPropertyDisplay || id == CSSPropertyOpacity
292  || (id >= CSSPropertyFont && id <= CSSPropertyFontWeight)
293  || id == CSSPropertyOverflow || id == CSSPropertyVisibility;
294 }
295 
296 bool SVGAnimationElement::targetAttributeIsCSS() const
297 {
298  AttributeType type = attributeType();
299  if (type == AttributeTypeCSS) {
300  return true;
301  }
302  if (type == AttributeTypeXML) {
303  return false;
304  }
305  return attributeIsCSS(attributeName());
306 }
307 
308 void SVGAnimationElement::setTargetAttributeAnimatedValue(const String &value)
309 {
310  if (!hasValidTarget()) {
311  return;
312  }
313  SVGElement *target = targetElement();
314  String attributeName = this->attributeName();
315  if (!target || attributeName.isEmpty() || value.isNull()) {
316  return;
317  }
318 
319  // We don't want the instance tree to get rebuild. Instances are updated in the loop below.
320  if (target->isStyled()) {
321  static_cast<SVGStyledElement *>(target)->setInstanceUpdatesBlocked(true);
322  }
323 
324  ExceptionCode ec;
325  bool isCSS = targetAttributeIsCSS();
326  if (isCSS) {
327  // FIXME: This should set the override style, not the inline style.
328  // Sadly override styles are not yet implemented.
329  target->style()->setProperty(attributeName, value, "", ec);
330  } else {
331  // FIXME: This should set the 'presentation' value, not the actual
332  // attribute value. Whatever that means in practice.
333  target->setAttribute(attributeName, value, ec);
334  }
335 
336  if (target->isStyled()) {
337  static_cast<SVGStyledElement *>(target)->setInstanceUpdatesBlocked(false);
338  }
339 
340  // If the target element is used in an <use> instance tree, update that as well.
341  HashSet<SVGElementInstance *> *instances = document()->accessSVGExtensions()->instancesForElement(target);
342  if (!instances) {
343  return;
344  }
345  HashSet<SVGElementInstance *>::iterator end = instances->end();
346  for (HashSet<SVGElementInstance *>::iterator it = instances->begin(); it != end; ++it) {
347  SVGElement *shadowTreeElement = (*it)->shadowTreeElement();
348  ASSERT(shadowTreeElement);
349  if (isCSS) {
350  shadowTreeElement->style()->setProperty(attributeName, value, "", ec);
351  } else {
352  shadowTreeElement->setAttribute(attributeName, value, ec);
353  }
354  (*it)->correspondingUseElement()->setChanged();
355  }
356 }
357 
358 void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
359 {
360  ASSERT(calcMode() == CalcModePaced);
361  ASSERT(animationMode() == ValuesAnimation);
362 
363  unsigned valuesCount = m_values.size();
364  ASSERT(valuesCount > 1);
365  Vector<float> keyTimesForPaced;
366  float totalDistance = 0;
367  keyTimesForPaced.append(0);
368  for (unsigned n = 0; n < valuesCount - 1; ++n) {
369  // Distance in any units
370  float distance = calculateDistance(m_values[n], m_values[n + 1]);
371  if (distance < 0) {
372  return;
373  }
374  totalDistance += distance;
375  keyTimesForPaced.append(distance);
376  }
377  if (!totalDistance) {
378  return;
379  }
380 
381  // Normalize.
382  for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n) {
383  keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance;
384  }
385  keyTimesForPaced[keyTimesForPaced.size() - 1] = 1.f;
386 
387  // Use key times calculated based on pacing instead of the user provided ones.
388  m_keyTimes.swap(keyTimesForPaced);
389 }
390 
391 static inline double solveEpsilon(double duration)
392 {
393  return 1. / (200. * duration);
394 }
395 
396 float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
397 {
398  ASSERT(calcMode() == CalcModeSpline);
399  ASSERT(splineIndex < m_keySplines.size());
400  UnitBezier bezier = m_keySplines[splineIndex];
401  SMILTime duration = simpleDuration();
402  if (!duration.isFinite()) {
403  duration = 100.0;
404  }
405  return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value())));
406 }
407 
408 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
409 {
410  ASSERT(!m_keyPoints.isEmpty());
411  ASSERT(calcMode() != CalcModePaced);
412  unsigned keyTimesCount = m_keyTimes.size();
413  ASSERT(keyTimesCount > 1);
414  ASSERT(m_keyPoints.size() == keyTimesCount);
415 
416  unsigned index;
417  for (index = 1; index < keyTimesCount; ++index) {
418  if (m_keyTimes[index] >= percent) {
419  break;
420  }
421  }
422  --index;
423 
424  float fromPercent = m_keyTimes[index];
425  float toPercent = m_keyTimes[index + 1];
426  float fromKeyPoint = m_keyPoints[index];
427  float toKeyPoint = m_keyPoints[index + 1];
428 
429  if (calcMode() == CalcModeDiscrete) {
430  return percent == 1.0f ? toKeyPoint : fromKeyPoint;
431  }
432 
433  float keyPointPercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent);
434 
435  if (calcMode() == CalcModeSpline) {
436  ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
437  keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
438  }
439  return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
440 }
441 
442 void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float &effectivePercent, String &from, String &to) const
443 {
444  ASSERT(!m_keyPoints.isEmpty());
445  ASSERT(m_keyPoints.size() == m_keyTimes.size());
446  ASSERT(calcMode() != CalcModePaced);
447  effectivePercent = calculatePercentFromKeyPoints(percent);
448  unsigned index = effectivePercent == 1.0f ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1));
449  from = m_values[index];
450  to = m_values[index + 1];
451 }
452 
453 void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float &effectivePercent, String &from, String &to) const
454 {
455  unsigned valuesCount = m_values.size();
456  ASSERT(m_animationValid);
457  ASSERT(valuesCount > 1);
458 
459  CalcMode calcMode = this->calcMode();
460  if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced) {
461  return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
462  }
463 
464  unsigned keyTimesCount = m_keyTimes.size();
465  ASSERT(!keyTimesCount || valuesCount == keyTimesCount);
466  ASSERT(!keyTimesCount || (keyTimesCount > 1 && m_keyTimes[0] == 0));
467 
468  unsigned index;
469  for (index = 1; index < keyTimesCount; ++index) {
470  if (m_keyTimes[index] >= percent) {
471  break;
472  }
473  }
474  --index;
475 
476  if (calcMode == CalcModeDiscrete) {
477  if (!keyTimesCount) {
478  index = percent == 1.0f ? valuesCount - 1 : static_cast<unsigned>(percent * valuesCount);
479  }
480  from = m_values[index];
481  to = m_values[index];
482  effectivePercent = 0.0f;
483  return;
484  }
485 
486  float fromPercent;
487  float toPercent;
488  if (keyTimesCount) {
489  fromPercent = m_keyTimes[index];
490  toPercent = m_keyTimes[index + 1];
491  } else {
492  index = static_cast<unsigned>(percent * (valuesCount - 1));
493  fromPercent = static_cast<float>(index) / (valuesCount - 1);
494  toPercent = static_cast<float>(index + 1) / (valuesCount - 1);
495  }
496 
497  if (index == valuesCount - 1) {
498  --index;
499  }
500  from = m_values[index];
501  to = m_values[index + 1];
502  ASSERT(toPercent > fromPercent);
503  effectivePercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent);
504 
505  if (calcMode == CalcModeSpline) {
506  ASSERT(m_keySplines.size() == m_values.size() - 1);
507  effectivePercent = calculatePercentForSpline(effectivePercent, index);
508  }
509 }
510 
511 void SVGAnimationElement::startedActiveInterval()
512 {
513  m_animationValid = false;
514 
515  if (!hasValidTarget()) {
516  return;
517  }
518 
519  AnimationMode animationMode = this->animationMode();
520  if (animationMode == NoAnimation) {
521  return;
522  }
523  if (animationMode == FromToAnimation) {
524  m_animationValid = calculateFromAndToValues(fromValue(), toValue());
525  } else if (animationMode == ToAnimation) {
526  // For to-animations the from value is the current accumulated value from lower priority animations.
527  // The value is not static and is determined during the animation.
528  m_animationValid = calculateFromAndToValues(String(), toValue());
529  } else if (animationMode == FromByAnimation) {
530  m_animationValid = calculateFromAndByValues(fromValue(), byValue());
531  } else if (animationMode == ByAnimation) {
532  m_animationValid = calculateFromAndByValues(String(), byValue());
533  } else if (animationMode == ValuesAnimation) {
534  CalcMode calcMode = this->calcMode();
535  m_animationValid = m_values.size() > 1
536  && (calcMode == CalcModePaced || !hasAttribute(SVGNames::keyTimesAttr) || hasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
537  && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1.0)
538  && (calcMode != CalcModeSpline || (m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1) || m_keySplines.size() == m_keyPoints.size() - 1))
539  && (!hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
540  if (calcMode == CalcModePaced && m_animationValid) {
541  calculateKeyTimesForCalcModePaced();
542  }
543  } else if (animationMode == PathAnimation) {
544  m_animationValid = calcMode() == CalcModePaced || !hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
545  }
546 }
547 
548 void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement *resultElement)
549 {
550  if (!m_animationValid) {
551  return;
552  }
553 
554  float effectivePercent;
555  if (animationMode() == ValuesAnimation) {
556  String from;
557  String to;
558  currentValuesForValuesAnimation(percent, effectivePercent, from, to);
559  if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo) {
560  m_animationValid = calculateFromAndToValues(from, to);
561  if (!m_animationValid) {
562  return;
563  }
564  m_lastValuesAnimationFrom = from;
565  m_lastValuesAnimationTo = to;
566  }
567  } else if (!m_keyPoints.isEmpty() && calcMode() != CalcModePaced) {
568  effectivePercent = calculatePercentFromKeyPoints(percent);
569  } else {
570  effectivePercent = percent;
571  }
572 
573  calculateAnimatedValue(effectivePercent, repeat, resultElement);
574 }
575 
576 void SVGAnimationElement::endedActiveInterval()
577 {
578 }
579 
580 }
581 
582 #endif // ENABLE(SVG_ANIMATION)
583 
Type type(const QSqlDatabase &db)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
int distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2)
const QList< QKeySequence > & end()
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 25 2021 22:48:22 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.