1 /* 2 * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann (at) kde.org> 3 * Copyright (C) 2004, 2005, 2006 Rob Buis <buis (at) 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_animatedAttributeType(AnimatedString) 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 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 116 bool SVGAnimateElement::hasValidAttributeType() const 117 { 118 SVGElement* targetElement = this->targetElement(); 119 if (!targetElement) 120 return false; 121 122 return determineAnimatedAttributeType(targetElement) != AnimatedUnknown; 123 } 124 125 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 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 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 333 static bool attributeValueIsCurrentColor(const String& value) 334 { 335 DEFINE_STATIC_LOCAL(const AtomicString, currentColor, ("currentColor")); 336 return value == currentColor; 337 } 338 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 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 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 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 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