2011-02-15 Dirk Schulze <krit@webkit.org>
[WebKit-https.git] / Source / WebCore / svg / SVGAnimateElement.cpp
1 /*
2  * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org>
3  * Copyright (C) 2004, 2005, 2006 Rob Buis <buis@kde.org>
4  * Copyright (C) 2008 Apple Inc. All rights reserved.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #include "config.h"
23
24 #if ENABLE(SVG) && ENABLE(SVG_ANIMATION)
25 #include "SVGAnimateElement.h"
26
27 #include "CSSComputedStyleDeclaration.h"
28 #include "CSSParser.h"
29 #include "CSSPropertyNames.h"
30 #include "ColorDistance.h"
31 #include "FloatConversion.h"
32 #include "QualifiedName.h"
33 #include "RenderObject.h"
34 #include "SVGColor.h"
35 #include "SVGNames.h"
36 #include "SVGParserUtilities.h"
37 #include "SVGPathParserFactory.h"
38 #include "SVGPathSegList.h"
39 #include "SVGPointList.h"
40 #include "SVGStyledElement.h"
41
42 using namespace std;
43
44 namespace WebCore {
45
46 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* document)
47     : SVGAnimationElement(tagName, document)
48     , m_propertyType(StringProperty)
49     , m_fromNumber(0)
50     , m_toNumber(0)
51     , m_animatedNumber(numeric_limits<double>::infinity())
52     , m_animatedPathPointer(0)
53 {
54 }
55
56 PassRefPtr<SVGAnimateElement> SVGAnimateElement::create(const QualifiedName& tagName, Document* document)
57 {
58     return adoptRef(new SVGAnimateElement(tagName, document));
59 }
60
61 SVGAnimateElement::~SVGAnimateElement()
62 {
63 }
64
65 static bool parseNumberValueAndUnit(const String& in, double& value, String& unit)
66 {
67     // FIXME: These are from top of my head, figure out all property types that can be animated as numbers.
68     unsigned unitLength = 0;
69     String parse = in.stripWhiteSpace();
70     if (parse.endsWith("%"))
71         unitLength = 1;
72     else if (parse.endsWith("px") || parse.endsWith("pt") || parse.endsWith("em"))
73         unitLength = 2;
74     else if (parse.endsWith("deg") || parse.endsWith("rad"))
75         unitLength = 3;
76     else if (parse.endsWith("grad"))
77         unitLength = 4;
78     String newUnit = parse.right(unitLength);
79     String number = parse.left(parse.length() - unitLength);
80     if ((!unit.isEmpty() && newUnit != unit) || number.isEmpty())
81         return false;
82     UChar last = number[number.length() - 1];
83     if (last < '0' || last > '9')
84         return false;
85     unit = newUnit;
86     bool ok;
87     value = number.toDouble(&ok);
88     return ok;
89 }
90
91 static inline void adjustForCurrentColor(SVGElement* targetElement, Color& color)
92 {
93     ASSERT(targetElement);
94     
95     if (RenderObject* targetRenderer = targetElement->renderer())
96         color = targetRenderer->style()->visitedDependentColor(CSSPropertyColor);
97     else
98         color = Color();
99 }
100
101 static inline void adjustForInheritance(SVGElement* targetElement, const String& attributeName, String& value)
102 {
103     // FIXME: At the moment the computed style gets returned as a String and needs to get parsed again.
104     // In the future we might want to work with the value type directly to avoid the String parsing.
105     ASSERT(targetElement);
106
107     Element* parent = targetElement->parentElement();
108     if (!parent || !parent->isSVGElement())
109         return;
110
111     SVGElement* svgParent = static_cast<SVGElement*>(parent);
112     if (svgParent->isStyled())
113         value = computedStyle(svgParent)->getPropertyValue(cssPropertyID(attributeName));
114 }
115
116 SVGAnimateElement::PropertyType SVGAnimateElement::determinePropertyType(const String& attribute) const
117 {
118     // FIXME: We should not allow animation of attribute types other than AnimatedColor for <animateColor>.
119     if (hasTagName(SVGNames::animateColorTag))
120         return ColorProperty;
121
122     // FIXME: Now that we have a full property table we need a more granular type specific animation.
123     AnimatedAttributeType type = targetElement()->animatedPropertyTypeForAttribute(QualifiedName(nullAtom, attribute, nullAtom));
124     if (type == AnimatedColor)
125         return ColorProperty;
126     if (type == AnimatedPath)
127         return PathProperty;
128     if (type == AnimatedPoints)
129         return PointsProperty;
130     return NumberProperty;
131 }
132
133 void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeat, SVGSMILElement* resultElement)
134 {
135     ASSERT(percentage >= 0 && percentage <= 1);
136     ASSERT(resultElement);
137     bool isInFirstHalfOfAnimation = percentage < 0.5f;
138     AnimationMode animationMode = this->animationMode();
139     SVGElement* targetElement = 0;
140     // Avoid targetElement() call if possible. It might slow down animations.
141     if (m_fromPropertyValueType == InheritValue || m_toPropertyValueType == InheritValue
142         || m_fromPropertyValueType == CurrentColorValue || m_toPropertyValueType == CurrentColorValue) {
143         targetElement = this->targetElement();
144         if (!targetElement)
145             return;
146     }
147     
148     if (hasTagName(SVGNames::setTag))
149         percentage = 1;
150     if (!resultElement->hasTagName(SVGNames::animateTag) && !resultElement->hasTagName(SVGNames::animateColorTag) 
151         && !resultElement->hasTagName(SVGNames::setTag))
152         return;
153     SVGAnimateElement* results = static_cast<SVGAnimateElement*>(resultElement);
154     // Can't accumulate over a string property.
155     if (results->m_propertyType == StringProperty && m_propertyType != StringProperty)
156         return;
157     if (m_propertyType == NumberProperty) {
158         // To animation uses contributions from the lower priority animations as the base value.
159         if (animationMode == ToAnimation)
160             m_fromNumber = results->m_animatedNumber;
161         
162         // Replace 'currentColor' / 'inherit' by their computed property values.
163         if (m_fromPropertyValueType == InheritValue) {
164             String fromNumberString;
165             adjustForInheritance(targetElement, attributeName(), fromNumberString);
166             if (!parseNumberValueAndUnit(fromNumberString, m_fromNumber, m_numberUnit))
167                 return;
168         }
169         if (m_toPropertyValueType == InheritValue) {
170             String toNumberString;
171             adjustForInheritance(targetElement, attributeName(), toNumberString);
172             if (!parseNumberValueAndUnit(toNumberString, m_toNumber, m_numberUnit))
173                 return;
174         }
175
176         double number;
177         if (calcMode() == CalcModeDiscrete)
178             number = isInFirstHalfOfAnimation ? m_fromNumber : m_toNumber;
179         else
180             number = (m_toNumber - m_fromNumber) * percentage + m_fromNumber;
181
182         // FIXME: This is not correct for values animation.
183         if (isAccumulated() && repeat)
184             number += m_toNumber * repeat;
185         if (isAdditive() && animationMode != ToAnimation)
186             results->m_animatedNumber += number;
187         else 
188             results->m_animatedNumber = number;
189         return;
190     } 
191     if (m_propertyType == ColorProperty) {
192         if (animationMode == ToAnimation)
193             m_fromColor = results->m_animatedColor;
194
195         // Replace 'currentColor' / 'inherit' by their computed property values.
196         if (m_fromPropertyValueType == CurrentColorValue)
197             adjustForCurrentColor(targetElement, m_fromColor);
198         else if (m_fromPropertyValueType == InheritValue) {
199             String fromColorString;
200             adjustForInheritance(targetElement, attributeName(), fromColorString);
201             m_fromColor = SVGColor::colorFromRGBColorString(fromColorString);
202         }
203         if (m_toPropertyValueType == CurrentColorValue)
204             adjustForCurrentColor(targetElement, m_toColor);
205         else if (m_toPropertyValueType == InheritValue) {
206             String toColorString;
207             adjustForInheritance(targetElement, attributeName(), toColorString);
208             m_toColor = SVGColor::colorFromRGBColorString(toColorString);
209         }
210
211         Color color;
212         if (calcMode() == CalcModeDiscrete)
213             color = isInFirstHalfOfAnimation ? m_fromColor : m_toColor;
214         else
215             color = ColorDistance(m_fromColor, m_toColor).scaledDistance(percentage).addToColorAndClamp(m_fromColor);
216
217         // FIXME: Accumulate colors.
218         if (isAdditive() && animationMode != ToAnimation)
219             results->m_animatedColor = ColorDistance::addColorsAndClamp(results->m_animatedColor, color);
220         else
221             results->m_animatedColor = color;
222         return;
223     }
224     if (m_propertyType == PathProperty) {
225         if (animationMode == ToAnimation) {
226             ASSERT(results->m_animatedPathPointer);
227             m_fromPath = results->m_animatedPathPointer->copy();
228         }
229         if (!percentage) {
230             ASSERT(m_fromPath);
231             ASSERT(percentage >= 0);
232             results->m_animatedPathPointer = m_fromPath.get();
233         } else if (percentage == 1.f) {
234             ASSERT(m_toPath);
235             results->m_animatedPathPointer = m_toPath.get();
236         } else {
237             if (m_fromPath && m_toPath) {
238                 SVGPathParserFactory* factory = SVGPathParserFactory::self();
239                 if (!factory->buildAnimatedSVGPathByteStream(m_fromPath.get(), m_toPath.get(), results->m_animatedPath, percentage)) {
240                     results->m_animatedPath.clear();
241                     results->m_animatedPathPointer = 0;
242                 } else
243                     results->m_animatedPathPointer = results->m_animatedPath.get();
244             } else
245                 results->m_animatedPathPointer = 0;
246             // Fall back to discrete animation if the paths are not compatible
247             if (!results->m_animatedPathPointer) {
248                 ASSERT(m_fromPath);
249                 ASSERT(m_toPath);
250                 ASSERT(!results->m_animatedPath);
251                 results->m_animatedPathPointer = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f) 
252                     ? m_toPath.get() : m_fromPath.get();
253             }
254         }
255         return;
256     }
257     if (m_propertyType == PointsProperty) {
258         if (!percentage)
259             results->m_animatedPoints = m_fromPoints;
260         else if (percentage == 1.f)
261             results->m_animatedPoints = m_toPoints;
262         else {
263             if (!m_fromPoints.isEmpty() && !m_toPoints.isEmpty())
264                 SVGPointList::createAnimated(m_fromPoints, m_toPoints, results->m_animatedPoints, percentage);
265             else
266                 results->m_animatedPoints.clear();
267             // Fall back to discrete animation if the points are not compatible
268             if (results->m_animatedPoints.isEmpty())
269                 results->m_animatedPoints = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f) 
270                     ? m_toPoints : m_fromPoints;
271         }
272         return;
273     }
274     ASSERT(animationMode == FromToAnimation || animationMode == ToAnimation || animationMode == ValuesAnimation);
275     // Replace 'currentColor' / 'inherit' by their computed property values.
276     if (m_fromPropertyValueType == InheritValue)
277         adjustForInheritance(targetElement, attributeName(), m_fromString);
278     if (m_toPropertyValueType == InheritValue)
279         adjustForInheritance(targetElement, attributeName(), m_toString);
280
281     if ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1)
282         results->m_animatedString = m_toString;
283     else
284         results->m_animatedString = m_fromString;
285     // Higher priority replace animation overrides any additive results so far.
286     results->m_propertyType = StringProperty;
287 }
288
289 static bool inheritsFromProperty(SVGElement* targetElement, const String& attributeName, const String& value)
290 {
291     ASSERT(targetElement);
292     DEFINE_STATIC_LOCAL(const AtomicString, inherit, ("inherit"));
293
294     if (value.isEmpty() || value != inherit || !targetElement->isStyled())
295         return false;
296     return SVGStyledElement::isAnimatableCSSProperty(QualifiedName(nullAtom, attributeName, nullAtom));
297 }
298
299 static bool attributeValueIsCurrentColor(const String& value)
300 {
301     DEFINE_STATIC_LOCAL(const AtomicString, currentColor, ("currentColor"));
302     return value == currentColor;
303 }
304
305 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
306 {
307     SVGElement* targetElement = this->targetElement();
308     if (!targetElement)
309         return false;
310     m_fromPropertyValueType = inheritsFromProperty(targetElement, attributeName(), fromString) ? InheritValue : CurrentColorValue;
311     m_toPropertyValueType = inheritsFromProperty(targetElement, attributeName(), toString) ? InheritValue : CurrentColorValue;
312
313     // FIXME: Needs more solid way determine target attribute type.
314     m_propertyType = determinePropertyType(attributeName());
315     if (m_propertyType == ColorProperty) {
316         bool fromIsCurrentColor = attributeValueIsCurrentColor(fromString);
317         bool toIsCurrentColor = attributeValueIsCurrentColor(toString);
318         if (fromIsCurrentColor)
319             m_fromPropertyValueType = CurrentColorValue;
320         else
321             m_fromColor = SVGColor::colorFromRGBColorString(fromString);
322         if (toIsCurrentColor)
323             m_toPropertyValueType = CurrentColorValue;
324         else
325             m_toColor = SVGColor::colorFromRGBColorString(toString);
326         if (((m_fromColor.isValid() || fromIsCurrentColor) && (m_toColor.isValid() || toIsCurrentColor))
327             || ((m_toColor.isValid() || toIsCurrentColor) && animationMode() == ToAnimation))
328             return true;
329     } else if (m_propertyType == NumberProperty) {
330         m_numberUnit = String();
331         if (parseNumberValueAndUnit(toString, m_toNumber, m_numberUnit)) {
332             // For to-animations the from number is calculated later
333             if (animationMode() == ToAnimation || parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
334                 return true;
335         }
336     } else if (m_propertyType == PathProperty) {
337         SVGPathParserFactory* factory = SVGPathParserFactory::self();
338         if (factory->buildSVGPathByteStreamFromString(toString, m_toPath, UnalteredParsing)) {
339             // For to-animations the from number is calculated later
340             if (animationMode() == ToAnimation || factory->buildSVGPathByteStreamFromString(fromString, m_fromPath, UnalteredParsing))
341                 return true;
342         }
343         m_fromPath.clear();
344         m_toPath.clear();
345     } else if (m_propertyType == PointsProperty) {
346         m_fromPoints.clear();
347         if (pointsListFromSVGData(m_fromPoints, fromString)) {
348             m_toPoints.clear();
349             if (pointsListFromSVGData(m_toPoints, toString))
350                 return true;
351         }
352     }
353     m_fromString = fromString;
354     m_toString = toString;
355     m_propertyType = StringProperty;
356     return true;
357 }
358
359 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
360 {
361     SVGElement* targetElement = this->targetElement();
362     if (!targetElement)
363         return false;
364     m_fromPropertyValueType = inheritsFromProperty(targetElement, attributeName(), fromString) ? InheritValue : CurrentColorValue;
365     m_toPropertyValueType = inheritsFromProperty(targetElement, attributeName(), byString) ? InheritValue : CurrentColorValue;
366
367     ASSERT(!hasTagName(SVGNames::setTag));
368     m_propertyType = determinePropertyType(attributeName());
369     if (m_propertyType == ColorProperty) {
370         bool fromIsCurrentColor = attributeValueIsCurrentColor(fromString);
371         bool byIsCurrentColor = attributeValueIsCurrentColor(byString);
372         if (fromIsCurrentColor)
373             m_fromPropertyValueType = CurrentColorValue;
374         else
375             m_fromColor = SVGColor::colorFromRGBColorString(fromString);
376         if (byIsCurrentColor)
377             m_toPropertyValueType = CurrentColorValue;
378         else
379             m_toColor = SVGColor::colorFromRGBColorString(byString);
380         
381         if ((!m_fromColor.isValid() && !fromIsCurrentColor)
382             || (!m_toColor.isValid() && !byIsCurrentColor))
383             return false;
384     } else {
385         m_numberUnit = String();
386         m_fromNumber = 0;
387         if (!fromString.isEmpty() && !parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
388             return false;
389         if (!parseNumberValueAndUnit(byString, m_toNumber, m_numberUnit))
390             return false;
391         m_toNumber += m_fromNumber;
392     }
393     return true;
394 }
395
396 void SVGAnimateElement::resetToBaseValue(const String& baseString)
397 {
398     m_animatedString = baseString;
399     PropertyType lastType = m_propertyType;
400     m_propertyType = determinePropertyType(attributeName());
401     if (m_propertyType == ColorProperty) {
402         m_animatedColor = baseString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(baseString);
403         if (isContributing(elapsed())) {
404             m_propertyType = lastType;
405             return;
406         }
407     } else if (m_propertyType == NumberProperty) {
408         if (baseString.isEmpty()) {
409             m_animatedNumber = 0;
410             m_numberUnit = String();
411             return;
412         }
413         if (parseNumberValueAndUnit(baseString, m_animatedNumber, m_numberUnit))
414             return;
415     } else if (m_propertyType == PathProperty) {
416         m_animatedPath.clear();
417         SVGPathParserFactory* factory = SVGPathParserFactory::self();
418         factory->buildSVGPathByteStreamFromString(baseString, m_animatedPath, UnalteredParsing);
419         m_animatedPathPointer = m_animatedPath.get();
420         return;
421     } else if (m_propertyType == PointsProperty) {
422         m_animatedPoints.clear();
423         return;
424     }
425     m_propertyType = StringProperty;
426 }
427     
428 void SVGAnimateElement::applyResultsToTarget()
429 {
430     String valueToApply;
431     if (m_propertyType == ColorProperty)
432         valueToApply = m_animatedColor.name();
433     else if (m_propertyType == NumberProperty)
434         valueToApply = String::number(m_animatedNumber) + m_numberUnit;
435     else if (m_propertyType == PathProperty) {
436         if (!m_animatedPathPointer || m_animatedPathPointer->isEmpty())
437             valueToApply = m_animatedString;
438         else {
439             // We need to keep going to string and back because we are currently only able to paint
440             // "processed" paths where complex shapes are replaced with simpler ones. Path 
441             // morphing needs to be done with unprocessed paths.
442             // FIXME: This could be optimized if paths were not processed at parse time.
443             SVGPathParserFactory* factory = SVGPathParserFactory::self();
444             factory->buildStringFromByteStream(m_animatedPathPointer, valueToApply, UnalteredParsing);
445         }
446     } else if (m_propertyType == PointsProperty)
447         valueToApply = m_animatedPoints.isEmpty() ? m_animatedString : m_animatedPoints.valueAsString();
448     else
449         valueToApply = m_animatedString;
450     
451     setTargetAttributeAnimatedValue(valueToApply);
452 }
453     
454 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
455 {
456     m_propertyType = determinePropertyType(attributeName());
457     if (m_propertyType == NumberProperty) {
458         double from;
459         double to;
460         String unit;
461         if (!parseNumberValueAndUnit(fromString, from, unit))
462             return -1.f;
463         if (!parseNumberValueAndUnit(toString, to, unit))
464             return -1.f;
465         return narrowPrecisionToFloat(fabs(to - from));
466     }
467     if (m_propertyType == ColorProperty) {
468         Color from = SVGColor::colorFromRGBColorString(fromString);
469         if (!from.isValid())
470             return -1.f;
471         Color to = SVGColor::colorFromRGBColorString(toString);
472         if (!to.isValid())
473             return -1.f;
474         return ColorDistance(from, to).distance();
475     }
476     return -1.f;
477 }
478    
479 }
480
481 // vim:ts=4:noet
482 #endif // ENABLE(SVG)
483