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