Reviewed by Mitz.
[WebKit-https.git] / WebCore / ksvg2 / svg / SVGAnimationElement.cpp
1 /*
2     Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
3                   2004, 2005, 2006 Rob Buis <buis@kde.org>
4
5     This file is part of the KDE project
6
7     This library is free software; you can redistribute it and/or
8     modify it under the terms of the GNU Library General Public
9     License as published by the Free Software Foundation; either
10     version 2 of the License, or (at your option) any later version.
11
12     This library is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15     Library General Public License for more details.
16
17     You should have received a copy of the GNU Library General Public License
18     along with this library; see the file COPYING.LIB.  If not, write to
19     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20     Boston, MA 02111-1307, USA.
21 */
22
23 #include "config.h"
24 #ifdef SVG_SUPPORT
25 #include "SVGAnimationElement.h"
26
27 #include "CSSPropertyNames.h"
28 #include "Document.h"
29 #include "KSVGTimeScheduler.h"
30 #include "PlatformString.h"
31 #include "SVGHelper.h"
32 #include "SVGSVGElement.h"
33 #include "SVGStyledElement.h"
34 #include "SVGURIReference.h"
35 #include "XLinkNames.h"
36 #include "ksvgcssproperties.h"
37 #include <float.h>
38 #include <math.h>
39 #include <wtf/Vector.h>
40
41 using namespace std;
42
43 namespace WebCore {
44
45 SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* doc)
46     : SVGElement(tagName, doc)
47     , SVGTests()
48     , SVGExternalResourcesRequired()
49     , m_targetElement(0)
50     , m_connected(false)
51     , m_currentTime(0.0)
52     , m_simpleDuration(0.0)
53     , m_fill(FILL_REMOVE)
54     , m_restart(RESTART_ALWAYS)
55     , m_calcMode(CALCMODE_LINEAR)
56     , m_additive(ADDITIVE_REPLACE)
57     , m_accumulate(ACCUMULATE_NONE)
58     , m_attributeType(ATTRIBUTETYPE_AUTO)
59     , m_max(0.0)
60     , m_min(0.0)
61     , m_end(0.0)
62     , m_begin(0.0)
63     , m_repeations(0)
64     , m_repeatCount(0)
65 {
66
67 }
68
69 SVGAnimationElement::~SVGAnimationElement()
70 {
71 }
72
73 SVGElement* SVGAnimationElement::targetElement() const
74 {
75     if (!m_targetElement) {
76         if (!m_href.isEmpty()) {
77             Element* element = ownerDocument()->getElementById(SVGURIReference::getTarget(m_href));
78             m_targetElement = svg_dynamic_cast(element);
79         } else if (parentNode()) {
80             Node* target = parentNode();
81             while (target) {
82                 if (target->nodeType() != ELEMENT_NODE)
83                     target = target->parentNode();
84                 else
85                     break;
86             }
87             m_targetElement = svg_dynamic_cast(target);
88         }
89     }
90                         
91     return m_targetElement;
92 }
93
94 double SVGAnimationElement::getEndTime() const
95 {
96     return m_end;
97 }
98
99 double SVGAnimationElement::getStartTime() const
100 {
101     return m_begin;
102 }
103
104 double SVGAnimationElement::getCurrentTime() const
105 {
106     return m_currentTime;
107 }
108
109 double SVGAnimationElement::getSimpleDuration(ExceptionCode&) const
110 {
111     return m_simpleDuration;
112 }
113
114 void SVGAnimationElement::parseMappedAttribute(MappedAttribute* attr)
115 {
116     const String& value = attr->value();
117     if (attr->name().matches(XLinkNames::hrefAttr))
118         m_href = value;
119     else if (attr->name() == SVGNames::attributeNameAttr)
120         m_attributeName = value;
121     else if (attr->name() == SVGNames::attributeTypeAttr)
122     {
123         if (value == "CSS")
124             m_attributeType = ATTRIBUTETYPE_CSS;
125         else if (value == "XML")
126             m_attributeType = ATTRIBUTETYPE_XML;
127         else if (value == "auto")
128             m_attributeType = ATTRIBUTETYPE_AUTO;
129     }
130     else if (attr->name() == SVGNames::beginAttr || attr->name() == SVGNames::endAttr)
131     {
132         // Create list
133         RefPtr<SVGStringList> temp = new SVGStringList();
134
135         // Feed data into list
136         SVGHelper::parseSeparatedList(temp.get(), value, ';');
137
138         ExceptionCode ec = 0;
139
140         // Parse data
141         for (unsigned int i = 0; i < temp->numberOfItems(); i++) {
142             String current = temp->getItem(i, ec);
143
144             if (current.startsWith("accessKey")) {
145                 // Register keyDownEventListener for the character
146                 String character = current.substring(current.length() - 2, 1);
147                 // FIXME: Not implemented! Supposed to register accessKey character
148             } else if (current.startsWith("wallclock")) {
149                 int firstBrace = current.find('(');
150                 int secondBrace = current.find(')');
151
152                 String wallclockValue = current.substring(firstBrace + 1, secondBrace - firstBrace - 2);
153                 // FIXME: Not implemented! Supposed to use wallClock value
154             } else if (current.contains('.')) {
155                 int dotPosition = current.find('.');
156
157                 String element = current.substring(0, dotPosition);
158                 String clockValue;
159                 if (current.contains("begin"))
160                     clockValue = current.substring(dotPosition + 6);
161                 else if (current.contains("end"))
162                     clockValue = current.substring(dotPosition + 4);
163                 else if (current.contains("repeat"))
164                     clockValue = current.substring(dotPosition + 7);
165                 else // DOM2 Event Reference
166                 {
167                     int plusMinusPosition = -1;
168
169                     if (current.contains('+'))
170                         plusMinusPosition = current.find('+');
171                     else if (current.contains('-'))
172                         plusMinusPosition = current.find('-');
173
174                     String event = current.substring(dotPosition + 1, plusMinusPosition - dotPosition - 1);
175                     clockValue = current.substring(dotPosition + event.length() + 1);
176                     // FIXME: supposed to use DOM Event
177                 }
178             } else {
179                 if (attr->name() == SVGNames::beginAttr) {
180                     m_begin = parseClockValue(current);
181                     if (!isIndefinite(m_begin))
182                         m_begin *= 1000.0;
183                     // FIXME: supposed to set begin time
184                 } else {
185                     m_end = parseClockValue(current);
186                     if (!isIndefinite(m_end))
187                         m_end *= 1000.0;
188                     // FIXME: supposed to set end time
189                 }
190             }
191         }
192     }
193     else if (attr->name() == SVGNames::durAttr)
194     {
195         m_simpleDuration = parseClockValue(value);
196         if (!isIndefinite(m_simpleDuration))
197             m_simpleDuration *= 1000.0;
198     }
199     else if (attr->name() == SVGNames::minAttr)
200     {
201         m_min = parseClockValue(value);
202         if (!isIndefinite(m_min))
203             m_min *= 1000.0;
204     }
205     else if (attr->name() == SVGNames::maxAttr)
206     {
207         m_max = parseClockValue(value);
208         if (!isIndefinite(m_max))
209             m_max *= 1000.0;
210     }
211     else if (attr->name() == SVGNames::restartAttr)
212     {
213         if (value == "whenNotActive")
214             m_restart = RESTART_WHENNOTACTIVE;
215         else if (value == "never")
216             m_restart = RESTART_NEVER;
217         else if (value == "always")
218             m_restart = RESTART_ALWAYS;
219     }
220     else if (attr->name() == SVGNames::repeatCountAttr)
221     {
222         if (value == "indefinite")
223             m_repeatCount = DBL_MAX;
224         else
225             m_repeatCount = value.toDouble();
226     }
227     else if (attr->name() == SVGNames::repeatDurAttr)
228         m_repeatDur = value;
229     else if (attr->name() == SVGNames::fillAttr)
230     {
231         if (value == "freeze")
232             m_fill = FILL_FREEZE;
233         else if (value == "remove")
234             m_fill = FILL_REMOVE;
235     }
236     else if (attr->name() == SVGNames::calcModeAttr)
237     {
238         if (value == "discrete")
239             m_calcMode = CALCMODE_DISCRETE;
240         else if (value == "linear")
241             m_calcMode = CALCMODE_LINEAR;
242         else if (value == "spline")
243             m_calcMode = CALCMODE_SPLINE;
244         else if (value == "paced")
245             m_calcMode = CALCMODE_PACED;
246     }
247     else if (attr->name() == SVGNames::valuesAttr)
248     {
249         m_values = new SVGStringList();
250         SVGHelper::parseSeparatedList(m_values.get(), value, ';');
251     }
252     else if (attr->name() == SVGNames::keyTimesAttr)
253     {
254         m_keyTimes = new SVGStringList();
255         SVGHelper::parseSeparatedList(m_keyTimes.get(), value, ';');
256     }
257     else if (attr->name() == SVGNames::keySplinesAttr)
258     {
259         m_keySplines = new SVGStringList();
260         SVGHelper::parseSeparatedList(m_keySplines.get(), value, ';');
261     }
262     else if (attr->name() == SVGNames::fromAttr)
263         m_from = value;
264     else if (attr->name() == SVGNames::toAttr)
265         m_to = value;
266     else if (attr->name() == SVGNames::byAttr)
267         m_by = value;
268     else if (attr->name() == SVGNames::additiveAttr)
269     {
270         if (value == "sum")
271             m_additive = ADDITIVE_SUM;
272         else if (value == "replace")
273             m_additive = ADDITIVE_REPLACE;
274     }
275     else if (attr->name() == SVGNames::accumulateAttr)
276     {
277         if (value == "sum")
278             m_accumulate = ACCUMULATE_SUM;
279         else if (value == "none")
280             m_accumulate = ACCUMULATE_NONE;
281     }
282     else
283     {
284         if (SVGTests::parseMappedAttribute(attr)) return;
285         if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) return;
286         
287         SVGElement::parseMappedAttribute(attr);
288     }
289 }
290
291 double SVGAnimationElement::parseClockValue(const String& data) const
292 {
293     DeprecatedString parse = data.deprecatedString().stripWhiteSpace();
294     
295     if (parse == "indefinite") // Saves some time...
296         return DBL_MAX;
297
298     double result;
299
300     int doublePointOne = parse.find(':');
301     int doublePointTwo = parse.find(':', doublePointOne + 1);
302
303     if (doublePointOne != -1 && doublePointTwo != -1) // Spec: "Full clock values"
304     {
305         unsigned int hours = parse.mid(0, 2).toUInt();
306         unsigned int minutes = parse.mid(3, 2).toUInt();
307         unsigned int seconds = parse.mid(6, 2).toUInt();
308         unsigned int milliseconds = 0;
309
310         result = (3600 * hours) + (60 * minutes) + seconds;
311
312         if (parse.find('.') != -1)
313         {
314             DeprecatedString temp = parse.mid(9, 2);
315             milliseconds = temp.toUInt();
316             result += (milliseconds * (1 / pow(10.0, int(temp.length()))));
317         }
318     }
319     else if (doublePointOne != -1 && doublePointTwo == -1) // Spec: "Partial clock values"
320     {
321         unsigned int minutes = parse.mid(0, 2).toUInt();
322         unsigned int seconds = parse.mid(3, 2).toUInt();
323         unsigned int milliseconds = 0;
324
325         result = (60 * minutes) + seconds;
326
327         if (parse.find('.') != -1)
328         {
329             DeprecatedString temp = parse.mid(6, 2);
330             milliseconds = temp.toUInt();
331             result += (milliseconds * (1 / pow(10.0, int(temp.length()))));
332         }
333     }
334     else // Spec: "Timecount values"
335     {
336         int dotPosition = parse.find('.');
337
338         if (parse.endsWith("h"))
339         {
340             if (dotPosition == -1)
341                 result = parse.mid(0, parse.length() - 1).toUInt() * 3600;
342             else
343             {
344                 result = parse.mid(0, dotPosition).toUInt() * 3600;
345                 DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 2);
346                 result += (3600.0 * temp.toUInt()) * (1 / pow(10.0, int(temp.length())));
347             }
348         }
349         else if (parse.endsWith("min"))
350         {
351             if (dotPosition == -1)
352                 result = parse.mid(0, parse.length() - 3).toUInt() * 60;
353             else
354             {
355                 result = parse.mid(0, dotPosition).toUInt() * 60;
356                 DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 4);
357                 result += (60.0 * temp.toUInt()) * (1 / pow(10.0, int(temp.length())));
358             }
359         }
360         else if (parse.endsWith("ms"))
361         {
362             if (dotPosition == -1)
363                 result = parse.mid(0, parse.length() - 2).toUInt() / 1000.0;
364             else
365             {
366                 result = parse.mid(0, dotPosition).toUInt() / 1000.0;
367                 DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 3);
368                 result += (temp.toUInt() / 1000.0) * (1 / pow(10.0, int(temp.length())));
369             }
370         }
371         else if (parse.endsWith("s"))
372         {
373             if (dotPosition == -1)
374                 result = parse.mid(0, parse.length() - 1).toUInt();
375             else
376             {
377                 result = parse.mid(0, dotPosition).toUInt();
378                 DeprecatedString temp = parse.mid(dotPosition + 1, parse.length() - dotPosition - 2);
379                 result += temp.toUInt() * (1 / pow(10.0, int(temp.length())));
380             }
381         }
382         else
383             result = parse.toDouble();
384     }
385
386     return result;
387 }
388
389 void SVGAnimationElement::closeRenderer()
390 {
391     ownerSVGElement()->timeScheduler()->addTimer(this, lround(getStartTime()));
392     SVGElement::closeRenderer();
393 }
394
395 String SVGAnimationElement::targetAttribute() const
396 {
397     if (!targetElement())
398         return String();
399     
400     SVGElement* target = targetElement();
401     SVGStyledElement* styled = 0;
402     if (target && target->isStyled())
403         styled = static_cast<SVGStyledElement*>(target);
404     
405     String ret;
406
407     EAttributeType attributeType = static_cast<EAttributeType>(m_attributeType);
408     if (attributeType == ATTRIBUTETYPE_AUTO) {
409         attributeType = ATTRIBUTETYPE_XML;
410
411         // Spec: The implementation should match the attributeName to an attribute
412         // for the target element. The implementation must first search through the
413         // list of CSS properties for a matching property name, and if none is found,
414         // search the default XML namespace for the element.
415         if (styled && styled->style()) {
416             if (styled->style()->getPropertyCSSValue(m_attributeName))
417                 attributeType = ATTRIBUTETYPE_CSS;
418         }
419     }
420     
421     if (attributeType == ATTRIBUTETYPE_CSS) {
422         if (styled && styled->style())
423             ret = styled->style()->getPropertyValue(m_attributeName);
424     }
425
426     if (attributeType == ATTRIBUTETYPE_XML || ret.isEmpty())
427         ret = targetElement()->getAttribute(m_attributeName);
428
429     return ret;
430 }
431
432 void SVGAnimationElement::setTargetAttribute(const String& value)
433 {
434     SVGAnimationElement::setTargetAttribute(targetElement(), m_attributeName, value, static_cast<EAttributeType>(m_attributeType));
435 }
436
437 void SVGAnimationElement::setTargetAttribute(SVGElement* target, const String& name, const String& value, EAttributeType type)
438 {
439     if (!target || name.isNull() || value.isNull())
440         return;
441     
442     SVGStyledElement* styled = (target && target->isStyled()) ? static_cast<SVGStyledElement*>(target) : 0;
443
444     EAttributeType attributeType = type;
445     if (type == ATTRIBUTETYPE_AUTO) {
446         // Spec: The implementation should match the attributeName to an attribute
447         // for the target element. The implementation must first search through the
448         // list of CSS properties for a matching property name, and if none is found,
449         // search the default XML namespace for the element.
450         if (styled && styled->style() && styled->style()->getPropertyCSSValue(name))
451             attributeType = ATTRIBUTETYPE_CSS;
452         else
453             attributeType = ATTRIBUTETYPE_XML;
454     }
455     ExceptionCode ec = 0;
456     if (attributeType == ATTRIBUTETYPE_CSS && styled && styled->style())
457         styled->style()->setProperty(name, value, "", ec);
458     else if (attributeType == ATTRIBUTETYPE_XML)
459         target->setAttribute(name, value, ec);
460 }
461
462 String SVGAnimationElement::attributeName() const
463 {
464     return m_attributeName;
465 }
466
467 bool SVGAnimationElement::connected() const
468 {
469     return m_connected;
470 }
471
472 bool SVGAnimationElement::isFrozen() const
473 {
474     return (m_fill == FILL_FREEZE);
475 }
476
477 bool SVGAnimationElement::isAdditive() const
478 {
479     return (m_additive == ADDITIVE_SUM) ||
480            (detectAnimationMode() == BY_ANIMATION);
481 }
482
483 bool SVGAnimationElement::isAccumulated() const
484 {
485     return (m_accumulate == ACCUMULATE_SUM) &&
486            (detectAnimationMode() != TO_ANIMATION);
487 }
488
489 EAnimationMode SVGAnimationElement::detectAnimationMode() const
490 {
491     if ((!m_from.isEmpty() && !m_to.isEmpty()) || (!m_to.isEmpty())) // to/from-to animation
492     {
493         if (!m_from.isEmpty()) // from-to animation
494             return FROM_TO_ANIMATION;
495         else
496             return TO_ANIMATION;
497     }
498     else if ((m_from.isEmpty() && m_to.isEmpty() && !m_by.isEmpty()) ||
499             (!m_from.isEmpty() && !m_by.isEmpty())) // by/from-by animation
500     {
501         if (!m_from.isEmpty()) // from-by animation
502             return FROM_BY_ANIMATION;
503         else
504             return BY_ANIMATION;
505     }
506     else if (m_values)
507         return VALUES_ANIMATION;
508
509     return NO_ANIMATION;
510 }
511
512 int SVGAnimationElement::calculateCurrentValueItem(double timePercentage)
513 {
514     if (!m_values)
515         return -1;
516     
517     unsigned long items = m_values->numberOfItems();
518
519     // Calculate the relative time percentages for each 'fade'.
520     Vector<double> startTimes(items);
521     startTimes[0] = 0.0;
522     for (unsigned i = 1; i < items; ++i)
523         startTimes[i] = (((2.0 * i)) / (items - 1)) / 2.0;
524
525     int itemByPercentage = -1;
526     for (unsigned i = 0; i < items - 1; ++i) {
527         if (timePercentage >= startTimes[i] && timePercentage <= startTimes[i + 1]) {
528             itemByPercentage = i;
529             break;
530         }
531     }
532
533     return itemByPercentage;
534 }
535
536 double SVGAnimationElement::calculateRelativeTimePercentage(double timePercentage, int currentItem)
537 {
538     if (currentItem == -1 || !m_values)
539         return 0.0;
540
541     unsigned long items = m_values->numberOfItems();
542
543     // Calculate the relative time percentages for each 'fade'.
544     Vector<double> startTimes(items);
545     startTimes[0] = 0.0;
546     for (unsigned i = 1; i < items; ++i)
547         startTimes[i] = (((2.0 * i)) / (items - 1)) / 2.0;
548
549     double beginTimePercentage = startTimes[currentItem];
550     double endTimePercentage = startTimes[currentItem + 1];
551
552     if ((endTimePercentage - beginTimePercentage) == 0.0)
553         return 0.0;
554
555     return ((timePercentage - beginTimePercentage) /
556             (endTimePercentage - beginTimePercentage));
557 }
558
559 double SVGAnimationElement::repeations() const
560 {
561     return m_repeations;
562 }
563
564 bool SVGAnimationElement::isIndefinite(double value) const
565 {
566     return (value == DBL_MAX);
567 }
568
569 }
570
571 // vim:ts=4:noet
572 #endif // SVG_SUPPORT
573