• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SVGAnimateElement(const QualifiedName & tagName,Document * document)46 SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* document)
47     : SVGAnimationElement(tagName, document)
48     , m_animatedAttributeType(AnimatedString)
49     , m_fromNumber(0)
50     , m_toNumber(0)
51     , m_animatedNumber(numeric_limits<double>::infinity())
52     , m_animatedPathPointer(0)
53 {
54 }
55 
create(const QualifiedName & tagName,Document * document)56 PassRefPtr<SVGAnimateElement> SVGAnimateElement::create(const QualifiedName& tagName, Document* document)
57 {
58     return adoptRef(new SVGAnimateElement(tagName, document));
59 }
60 
~SVGAnimateElement()61 SVGAnimateElement::~SVGAnimateElement()
62 {
63 }
64 
parseNumberValueAndUnit(const String & in,double & value,String & unit)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 
adjustForCurrentColor(SVGElement * targetElement,Color & color)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 
adjustForInheritance(SVGElement * targetElement,const QualifiedName & attributeName,String & value)101 static inline void adjustForInheritance(SVGElement* targetElement, const QualifiedName& 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.localName()));
114 }
115 
hasValidAttributeType() const116 bool SVGAnimateElement::hasValidAttributeType() const
117 {
118     SVGElement* targetElement = this->targetElement();
119     if (!targetElement)
120         return false;
121 
122     return determineAnimatedAttributeType(targetElement) != AnimatedUnknown;
123 }
124 
determineAnimatedAttributeType(SVGElement * targetElement) const125 AnimatedAttributeType SVGAnimateElement::determineAnimatedAttributeType(SVGElement* targetElement) const
126 {
127     ASSERT(targetElement);
128 
129     AnimatedAttributeType type = targetElement->animatedPropertyTypeForAttribute(attributeName());
130     if (type == AnimatedUnknown || (hasTagName(SVGNames::animateColorTag) && type != AnimatedColor))
131         return AnimatedUnknown;
132 
133     // FIXME: We need type specific animations in the future. Many animations marked as AnimatedString today will
134     // support continuous animations.
135     switch (type) {
136     case AnimatedBoolean:
137     case AnimatedEnumeration:
138     case AnimatedLengthList:
139     case AnimatedNumberList:
140     case AnimatedNumberOptionalNumber:
141     case AnimatedPreserveAspectRatio:
142     case AnimatedRect:
143     case AnimatedString:
144         return AnimatedString;
145     case AnimatedAngle:
146     case AnimatedInteger:
147     case AnimatedLength:
148     case AnimatedNumber:
149         return AnimatedNumber;
150     case AnimatedPath:
151         return AnimatedPath;
152     case AnimatedPoints:
153         return AnimatedPoints;
154     case AnimatedColor:
155         return AnimatedColor;
156     case AnimatedUnknown:
157     case AnimatedTransformList:
158         // Animations of transform lists are not allowed for <animate> or <set>
159         // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties
160         return AnimatedUnknown;
161     }
162 
163     ASSERT_NOT_REACHED();
164     return AnimatedUnknown;
165 }
166 
calculateAnimatedValue(float percentage,unsigned repeat,SVGSMILElement * resultElement)167 void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeat, SVGSMILElement* resultElement)
168 {
169     ASSERT(percentage >= 0 && percentage <= 1);
170     ASSERT(resultElement);
171     bool isInFirstHalfOfAnimation = percentage < 0.5f;
172     AnimationMode animationMode = this->animationMode();
173     SVGElement* targetElement = 0;
174     // Avoid targetElement() call if possible. It might slow down animations.
175     if (m_fromPropertyValueType == InheritValue || m_toPropertyValueType == InheritValue
176         || m_fromPropertyValueType == CurrentColorValue || m_toPropertyValueType == CurrentColorValue) {
177         targetElement = this->targetElement();
178         if (!targetElement)
179             return;
180     }
181 
182     if (hasTagName(SVGNames::setTag))
183         percentage = 1;
184     if (!resultElement->hasTagName(SVGNames::animateTag) && !resultElement->hasTagName(SVGNames::animateColorTag)
185         && !resultElement->hasTagName(SVGNames::setTag))
186         return;
187     SVGAnimateElement* results = static_cast<SVGAnimateElement*>(resultElement);
188     // Can't accumulate over a string property.
189     if (results->m_animatedAttributeType == AnimatedString && m_animatedAttributeType != AnimatedString)
190         return;
191     if (m_animatedAttributeType == AnimatedNumber) {
192         // To animation uses contributions from the lower priority animations as the base value.
193         if (animationMode == ToAnimation)
194             m_fromNumber = results->m_animatedNumber;
195 
196         // Replace 'currentColor' / 'inherit' by their computed property values.
197         if (m_fromPropertyValueType == InheritValue) {
198             String fromNumberString;
199             adjustForInheritance(targetElement, attributeName(), fromNumberString);
200             if (!parseNumberValueAndUnit(fromNumberString, m_fromNumber, m_numberUnit))
201                 return;
202         }
203         if (m_toPropertyValueType == InheritValue) {
204             String toNumberString;
205             adjustForInheritance(targetElement, attributeName(), toNumberString);
206             if (!parseNumberValueAndUnit(toNumberString, m_toNumber, m_numberUnit))
207                 return;
208         }
209 
210         double number;
211         if (calcMode() == CalcModeDiscrete)
212             number = isInFirstHalfOfAnimation ? m_fromNumber : m_toNumber;
213         else
214             number = (m_toNumber - m_fromNumber) * percentage + m_fromNumber;
215 
216         // FIXME: This is not correct for values animation.
217         if (isAccumulated() && repeat)
218             number += m_toNumber * repeat;
219         if (isAdditive() && animationMode != ToAnimation)
220             results->m_animatedNumber += number;
221         else
222             results->m_animatedNumber = number;
223         return;
224     }
225     if (m_animatedAttributeType == AnimatedColor) {
226         if (animationMode == ToAnimation)
227             m_fromColor = results->m_animatedColor;
228 
229         // Replace 'currentColor' / 'inherit' by their computed property values.
230         if (m_fromPropertyValueType == CurrentColorValue)
231             adjustForCurrentColor(targetElement, m_fromColor);
232         else if (m_fromPropertyValueType == InheritValue) {
233             String fromColorString;
234             adjustForInheritance(targetElement, attributeName(), fromColorString);
235             m_fromColor = SVGColor::colorFromRGBColorString(fromColorString);
236         }
237         if (m_toPropertyValueType == CurrentColorValue)
238             adjustForCurrentColor(targetElement, m_toColor);
239         else if (m_toPropertyValueType == InheritValue) {
240             String toColorString;
241             adjustForInheritance(targetElement, attributeName(), toColorString);
242             m_toColor = SVGColor::colorFromRGBColorString(toColorString);
243         }
244 
245         Color color;
246         if (calcMode() == CalcModeDiscrete)
247             color = isInFirstHalfOfAnimation ? m_fromColor : m_toColor;
248         else
249             color = ColorDistance(m_fromColor, m_toColor).scaledDistance(percentage).addToColorAndClamp(m_fromColor);
250 
251         // FIXME: Accumulate colors.
252         if (isAdditive() && animationMode != ToAnimation)
253             results->m_animatedColor = ColorDistance::addColorsAndClamp(results->m_animatedColor, color);
254         else
255             results->m_animatedColor = color;
256         return;
257     }
258     if (m_animatedAttributeType == AnimatedPath) {
259         if (animationMode == ToAnimation) {
260             ASSERT(results->m_animatedPathPointer);
261             m_fromPath = results->m_animatedPathPointer->copy();
262         }
263         if (!percentage) {
264             ASSERT(m_fromPath);
265             ASSERT(percentage >= 0);
266             results->m_animatedPathPointer = m_fromPath.get();
267         } else if (percentage == 1) {
268             ASSERT(m_toPath);
269             results->m_animatedPathPointer = m_toPath.get();
270         } else {
271             if (m_fromPath && m_toPath) {
272                 SVGPathParserFactory* factory = SVGPathParserFactory::self();
273                 if (!factory->buildAnimatedSVGPathByteStream(m_fromPath.get(), m_toPath.get(), results->m_animatedPath, percentage)) {
274                     results->m_animatedPath.clear();
275                     results->m_animatedPathPointer = 0;
276                 } else
277                     results->m_animatedPathPointer = results->m_animatedPath.get();
278             } else
279                 results->m_animatedPathPointer = 0;
280             // Fall back to discrete animation if the paths are not compatible
281             if (!results->m_animatedPathPointer) {
282                 ASSERT(m_fromPath);
283                 ASSERT(m_toPath);
284                 ASSERT(!results->m_animatedPath);
285                 results->m_animatedPathPointer = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1)
286                     ? m_toPath.get() : m_fromPath.get();
287             }
288         }
289         return;
290     }
291     if (m_animatedAttributeType == AnimatedPoints) {
292         if (!percentage)
293             results->m_animatedPoints = m_fromPoints;
294         else if (percentage == 1)
295             results->m_animatedPoints = m_toPoints;
296         else {
297             if (!m_fromPoints.isEmpty() && !m_toPoints.isEmpty())
298                 SVGPointList::createAnimated(m_fromPoints, m_toPoints, results->m_animatedPoints, percentage);
299             else
300                 results->m_animatedPoints.clear();
301             // Fall back to discrete animation if the points are not compatible
302             if (results->m_animatedPoints.isEmpty())
303                 results->m_animatedPoints = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1)
304                     ? m_toPoints : m_fromPoints;
305         }
306         return;
307     }
308     ASSERT(animationMode == FromToAnimation || animationMode == ToAnimation || animationMode == ValuesAnimation);
309     // Replace 'currentColor' / 'inherit' by their computed property values.
310     if (m_fromPropertyValueType == InheritValue)
311         adjustForInheritance(targetElement, attributeName(), m_fromString);
312     if (m_toPropertyValueType == InheritValue)
313         adjustForInheritance(targetElement, attributeName(), m_toString);
314 
315     if ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1)
316         results->m_animatedString = m_toString;
317     else
318         results->m_animatedString = m_fromString;
319     // Higher priority replace animation overrides any additive results so far.
320     results->m_animatedAttributeType = AnimatedString;
321 }
322 
inheritsFromProperty(SVGElement * targetElement,const QualifiedName & attributeName,const String & value)323 static bool inheritsFromProperty(SVGElement* targetElement, const QualifiedName& attributeName, const String& value)
324 {
325     ASSERT(targetElement);
326     DEFINE_STATIC_LOCAL(const AtomicString, inherit, ("inherit"));
327 
328     if (value.isEmpty() || value != inherit || !targetElement->isStyled())
329         return false;
330     return SVGStyledElement::isAnimatableCSSProperty(attributeName);
331 }
332 
attributeValueIsCurrentColor(const String & value)333 static bool attributeValueIsCurrentColor(const String& value)
334 {
335     DEFINE_STATIC_LOCAL(const AtomicString, currentColor, ("currentColor"));
336     return value == currentColor;
337 }
338 
calculateFromAndToValues(const String & fromString,const String & toString)339 bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
340 {
341     SVGElement* targetElement = this->targetElement();
342     if (!targetElement)
343         return false;
344     m_fromPropertyValueType = inheritsFromProperty(targetElement, attributeName(), fromString) ? InheritValue : RegularPropertyValue;
345     m_toPropertyValueType = inheritsFromProperty(targetElement, attributeName(), toString) ? InheritValue : RegularPropertyValue;
346 
347     // FIXME: Needs more solid way determine target attribute type.
348     m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
349     if (m_animatedAttributeType == AnimatedColor) {
350         bool fromIsCurrentColor = attributeValueIsCurrentColor(fromString);
351         bool toIsCurrentColor = attributeValueIsCurrentColor(toString);
352         if (fromIsCurrentColor)
353             m_fromPropertyValueType = CurrentColorValue;
354         else
355             m_fromColor = SVGColor::colorFromRGBColorString(fromString);
356         if (toIsCurrentColor)
357             m_toPropertyValueType = CurrentColorValue;
358         else
359             m_toColor = SVGColor::colorFromRGBColorString(toString);
360         bool fromIsValid = m_fromColor.isValid() || fromIsCurrentColor || m_fromPropertyValueType == InheritValue;
361         bool toIsValid = m_toColor.isValid() || toIsCurrentColor || m_toPropertyValueType == InheritValue;
362         if ((fromIsValid && toIsValid) || (toIsValid && animationMode() == ToAnimation))
363             return true;
364     } else if (m_animatedAttributeType == AnimatedNumber) {
365         m_numberUnit = String();
366         if (parseNumberValueAndUnit(toString, m_toNumber, m_numberUnit)) {
367             // For to-animations the from number is calculated later
368             if (animationMode() == ToAnimation || parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
369                 return true;
370         }
371     } else if (m_animatedAttributeType == AnimatedPath) {
372         SVGPathParserFactory* factory = SVGPathParserFactory::self();
373         if (factory->buildSVGPathByteStreamFromString(toString, m_toPath, UnalteredParsing)) {
374             // For to-animations the from number is calculated later
375             if (animationMode() == ToAnimation || factory->buildSVGPathByteStreamFromString(fromString, m_fromPath, UnalteredParsing))
376                 return true;
377         }
378         m_fromPath.clear();
379         m_toPath.clear();
380     } else if (m_animatedAttributeType == AnimatedPoints) {
381         m_fromPoints.clear();
382         if (pointsListFromSVGData(m_fromPoints, fromString)) {
383             m_toPoints.clear();
384             if (pointsListFromSVGData(m_toPoints, toString))
385                 return true;
386         }
387     }
388     m_fromString = fromString;
389     m_toString = toString;
390     m_animatedAttributeType = AnimatedString;
391     return true;
392 }
393 
calculateFromAndByValues(const String & fromString,const String & byString)394 bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
395 {
396     SVGElement* targetElement = this->targetElement();
397     if (!targetElement)
398         return false;
399     m_fromPropertyValueType = inheritsFromProperty(targetElement, attributeName(), fromString) ? InheritValue : RegularPropertyValue;
400     m_toPropertyValueType = inheritsFromProperty(targetElement, attributeName(), byString) ? InheritValue : RegularPropertyValue;
401 
402     ASSERT(!hasTagName(SVGNames::setTag));
403     m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
404     if (m_animatedAttributeType == AnimatedColor) {
405         bool fromIsCurrentColor = attributeValueIsCurrentColor(fromString);
406         bool byIsCurrentColor = attributeValueIsCurrentColor(byString);
407         if (fromIsCurrentColor)
408             m_fromPropertyValueType = CurrentColorValue;
409         else
410             m_fromColor = SVGColor::colorFromRGBColorString(fromString);
411         if (byIsCurrentColor)
412             m_toPropertyValueType = CurrentColorValue;
413         else
414             m_toColor = SVGColor::colorFromRGBColorString(byString);
415 
416         if ((!m_fromColor.isValid() && !fromIsCurrentColor)
417             || (!m_toColor.isValid() && !byIsCurrentColor))
418             return false;
419     } else {
420         m_numberUnit = String();
421         m_fromNumber = 0;
422         if (!fromString.isEmpty() && !parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
423             return false;
424         if (!parseNumberValueAndUnit(byString, m_toNumber, m_numberUnit))
425             return false;
426         m_toNumber += m_fromNumber;
427     }
428     return true;
429 }
430 
resetToBaseValue(const String & baseString)431 void SVGAnimateElement::resetToBaseValue(const String& baseString)
432 {
433     SVGElement* targetElement = this->targetElement();
434     ASSERT(targetElement);
435     m_animatedString = baseString;
436     AnimatedAttributeType lastType = m_animatedAttributeType;
437     m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
438     if (m_animatedAttributeType == AnimatedColor) {
439         m_animatedColor = baseString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(baseString);
440         if (isContributing(elapsed())) {
441             m_animatedAttributeType = lastType;
442             return;
443         }
444     } else if (m_animatedAttributeType == AnimatedNumber) {
445         if (baseString.isEmpty()) {
446             m_animatedNumber = 0;
447             m_numberUnit = String();
448             return;
449         }
450         if (parseNumberValueAndUnit(baseString, m_animatedNumber, m_numberUnit))
451             return;
452     } else if (m_animatedAttributeType == AnimatedPath) {
453         m_animatedPath.clear();
454         SVGPathParserFactory* factory = SVGPathParserFactory::self();
455         factory->buildSVGPathByteStreamFromString(baseString, m_animatedPath, UnalteredParsing);
456         m_animatedPathPointer = m_animatedPath.get();
457         return;
458     } else if (m_animatedAttributeType == AnimatedPoints) {
459         m_animatedPoints.clear();
460         return;
461     }
462     m_animatedAttributeType = AnimatedString;
463 }
464 
applyResultsToTarget()465 void SVGAnimateElement::applyResultsToTarget()
466 {
467     String valueToApply;
468     if (m_animatedAttributeType == AnimatedColor)
469         valueToApply = m_animatedColor.serialized();
470     else if (m_animatedAttributeType == AnimatedNumber)
471         valueToApply = String::number(m_animatedNumber) + m_numberUnit;
472     else if (m_animatedAttributeType == AnimatedPath) {
473         if (!m_animatedPathPointer || m_animatedPathPointer->isEmpty())
474             valueToApply = m_animatedString;
475         else {
476             // We need to keep going to string and back because we are currently only able to paint
477             // "processed" paths where complex shapes are replaced with simpler ones. Path
478             // morphing needs to be done with unprocessed paths.
479             // FIXME: This could be optimized if paths were not processed at parse time.
480             SVGPathParserFactory* factory = SVGPathParserFactory::self();
481             factory->buildStringFromByteStream(m_animatedPathPointer, valueToApply, UnalteredParsing);
482         }
483     } else if (m_animatedAttributeType == AnimatedPoints)
484         valueToApply = m_animatedPoints.isEmpty() ? m_animatedString : m_animatedPoints.valueAsString();
485     else
486         valueToApply = m_animatedString;
487 
488     setTargetAttributeAnimatedValue(valueToApply);
489 }
490 
calculateDistance(const String & fromString,const String & toString)491 float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
492 {
493     SVGElement* targetElement = this->targetElement();
494     if (!targetElement)
495         return -1;
496     m_animatedAttributeType = determineAnimatedAttributeType(targetElement);
497     if (m_animatedAttributeType == AnimatedNumber) {
498         double from;
499         double to;
500         String unit;
501         if (!parseNumberValueAndUnit(fromString, from, unit))
502             return -1;
503         if (!parseNumberValueAndUnit(toString, to, unit))
504             return -1;
505         return narrowPrecisionToFloat(fabs(to - from));
506     }
507     if (m_animatedAttributeType == AnimatedColor) {
508         Color from = SVGColor::colorFromRGBColorString(fromString);
509         if (!from.isValid())
510             return -1;
511         Color to = SVGColor::colorFromRGBColorString(toString);
512         if (!to.isValid())
513             return -1;
514         return ColorDistance(from, to).distance();
515     }
516     return -1;
517 }
518 
519 }
520 #endif // ENABLE(SVG)
521