• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.14.38 API Reference
  • KDE Home
  • Contact Us
 

KHTML

  • khtml
  • svg
SVGAnimationElement.cpp
Go to the documentation of this file.
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.
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#include "config.h"
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 "SVGElementInstance.h"
38#include "SVGNames.h"
39#include "SVGURIReference.h"
40#include "SVGUseElement.h"
41#include "XLinkNames.h"
42#include <math.h>
43
44using namespace std;
45
46namespace WebCore {
47
48SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* doc)
49 : SVGSMILElement(tagName, doc)
50 , SVGTests()
51 , SVGExternalResourcesRequired()
52 , m_animationValid(false)
53{
54}
55
56SVGAnimationElement::~SVGAnimationElement()
57{
58}
59
60static 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 if (verifyOrder) {
72 if (!n) {
73 if (time != 0)
74 goto fail;
75 } else if (time < result.last())
76 goto fail;
77 }
78 result.append(time);
79 }
80 return;
81fail:
82 result.clear();
83}
84
85static void parseKeySplines(const String& parse, Vector<UnitBezier>& result)
86{
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;
105 }
106 result.append(UnitBezier(curveValues[0], curveValues[1], curveValues[2], curveValues[3]));
107 }
108 return;
109fail:
110 result.clear();
111}
112
113void SVGAnimationElement::parseMappedAttribute(MappedAttribute* attr)
114{
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);
131 }
132}
133
134void SVGAnimationElement::attributeChanged(Attribute* attr, bool preserveDecls)
135{
136 // Assumptions may not hold after an attribute change.
137 m_animationValid = false;
138 SVGSMILElement::attributeChanged(attr, preserveDecls);
139}
140
141float SVGAnimationElement::getStartTime() const
142{
143 return narrowPrecisionToFloat(intervalBegin().value());
144}
145
146float SVGAnimationElement::getCurrentTime() const
147{
148 return narrowPrecisionToFloat(elapsed().value());
149}
150
151float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const
152{
153 return narrowPrecisionToFloat(simpleDuration().value());
154}
155
156bool SVGAnimationElement::beginElement(ExceptionCode& ec)
157{
158 return beginElementAt(0, ec);
159}
160
161bool SVGAnimationElement::beginElementAt(float offset, ExceptionCode& ec)
162{
163 addBeginTime(elapsed() + offset);
164 return true;
165}
166
167bool SVGAnimationElement::endElement(ExceptionCode& ec)
168{
169 return endElementAt(0, ec);
170}
171
172bool SVGAnimationElement::endElementAt(float offset, ExceptionCode& ec)
173{
174 if (offset < 0)
175 return false;
176
177 addEndTime(elapsed() + offset);
178 return true;
179}
180
181SVGAnimationElement::AnimationMode SVGAnimationElement::animationMode() const
182{
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;
195}
196
197SVGAnimationElement::CalcMode SVGAnimationElement::calcMode() const
198{
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;
213}
214
215SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const
216{
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;
225}
226
227String SVGAnimationElement::toValue() const
228{
229 return getAttribute(SVGNames::toAttr);
230}
231
232String SVGAnimationElement::byValue() const
233{
234 return getAttribute(SVGNames::byAttr);
235}
236
237String SVGAnimationElement::fromValue() const
238{
239 return getAttribute(SVGNames::fromAttr);
240}
241
242bool SVGAnimationElement::isAdditive() const
243{
244 static const AtomicString sum("sum");
245 const AtomicString& value = getAttribute(SVGNames::additiveAttr);
246 return value == sum || animationMode() == ByAnimation;
247}
248
249bool SVGAnimationElement::isAccumulated() const
250{
251 static const AtomicString sum("sum");
252 const AtomicString& value = getAttribute(SVGNames::accumulateAttr);
253 return value == sum && animationMode() != ToAnimation;
254}
255
256bool SVGAnimationElement::hasValidTarget() const
257{
258 return targetElement();
259}
260
261bool SVGAnimationElement::attributeIsCSS(const String& attributeName)
262{
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;
273}
274
275bool SVGAnimationElement::targetAttributeIsCSS() const
276{
277 AttributeType type = attributeType();
278 if (type == AttributeTypeCSS)
279 return true;
280 if (type == AttributeTypeXML)
281 return false;
282 return attributeIsCSS(attributeName());
283}
284
285void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value)
286{
287 if (!hasValidTarget())
288 return;
289 SVGElement* target = targetElement();
290 String attributeName = this->attributeName();
291 if (!target || attributeName.isEmpty() || value.isNull())
292 return;
293
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);
297
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);
308 }
309
310 if (target->isStyled())
311 static_cast<SVGStyledElement*>(target)->setInstanceUpdatesBlocked(false);
312
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();
326 }
327}
328
329void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
330{
331 ASSERT(calcMode() == CalcModePaced);
332 ASSERT(animationMode() == ValuesAnimation);
333
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);
346 }
347 if (!totalDistance)
348 return;
349
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;
354
355 // Use key times calculated based on pacing instead of the user provided ones.
356 m_keyTimes.swap(keyTimesForPaced);
357}
358
359static inline double solveEpsilon(double duration) { return 1. / (200. * duration); }
360
361float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
362{
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())));
370}
371
372float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
373{
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);
379
380 unsigned index;
381 for (index = 1; index < keyTimesCount; ++index) {
382 if (m_keyTimes[index] >= percent)
383 break;
384 }
385 --index;
386
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];
391
392 if (calcMode() == CalcModeDiscrete)
393 return percent == 1.0f ? toKeyPoint : fromKeyPoint;
394
395 float keyPointPercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent);
396
397 if (calcMode() == CalcModeSpline) {
398 ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
399 keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
400 }
401 return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
402}
403
404void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const
405{
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];
413}
414
415void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to) const
416{
417 unsigned valuesCount = m_values.size();
418 ASSERT(m_animationValid);
419 ASSERT(valuesCount > 1);
420
421 CalcMode calcMode = this->calcMode();
422 if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced)
423 return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
424
425 unsigned keyTimesCount = m_keyTimes.size();
426 ASSERT(!keyTimesCount || valuesCount == keyTimesCount);
427 ASSERT(!keyTimesCount || (keyTimesCount > 1 && m_keyTimes[0] == 0));
428
429 unsigned index;
430 for (index = 1; index < keyTimesCount; ++index) {
431 if (m_keyTimes[index] >= percent)
432 break;
433 }
434 --index;
435
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;
443 }
444
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);
454 }
455
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);
462
463 if (calcMode == CalcModeSpline) {
464 ASSERT(m_keySplines.size() == m_values.size() - 1);
465 effectivePercent = calculatePercentForSpline(effectivePercent, index);
466 }
467}
468
469void SVGAnimationElement::startedActiveInterval()
470{
471 m_animationValid = false;
472
473 if (!hasValidTarget())
474 return;
475
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());
500}
501
502void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement* resultElement)
503{
504 if (!m_animationValid)
505 return;
506
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;
518 }
519 } else if (!m_keyPoints.isEmpty() && calcMode() != CalcModePaced)
520 effectivePercent = calculatePercentFromKeyPoints(percent);
521 else
522 effectivePercent = percent;
523
524 calculateAnimatedValue(effectivePercent, repeat, resultElement);
525}
526
527void SVGAnimationElement::endedActiveInterval()
528{
529}
530
531}
532
533// vim:ts=4:noet
534#endif // ENABLE(SVG_ANIMATION)
535
FloatConversion.h
SVGAnimationElement.h
SVGElementInstance.h
SVGNames.h
SVGURIReference.h
SVGUseElement.h
XLinkNames.h
ok
KGuiItem ok()
end
const KShortcut & end()
WebCore
Definition: CSSHelper.h:7
WebCore::String
DOM::DOMString String
Definition: PlatformString.h:8
WebCore::narrowPrecisionToFloat
float narrowPrecisionToFloat(T)
khtml::ExceptionCode
unsigned short ExceptionCode
Definition: ExceptionCode.h:37
parse
QList< Action > parse(QSettings &ini)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KHTML

Skip menu "KHTML"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.14.38 API Reference

Skip menu "kdelibs-4.14.38 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal