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