1 /* 2 Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann (at) kde.org> 3 2004, 2005, 2006, 2007 Rob Buis <buis (at) kde.org> 4 Copyright (C) 2007 Eric Seidel <eric (at) webkit.org> 5 Copyright (C) 2008 Apple Inc. All rights reserved. 6 Copyright (C) 2009 Cameron McCormack <cam (at) mcc.id.au> 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., 51 Franklin Street, Fifth Floor, 21 Boston, MA 02110-1301, USA. 22 */ 23 24 #include "config.h" 25 26 #if ENABLE(SVG_ANIMATION) 27 #include "SVGAnimationElement.h" 28 29 #include "CSSComputedStyleDeclaration.h" 30 #include "CSSParser.h" 31 #include "CSSPropertyNames.h" 32 #include "Document.h" 33 #include "Event.h" 34 #include "EventListener.h" 35 #include "FloatConversion.h" 36 #include "HTMLNames.h" 37 #include "MappedAttribute.h" 38 #include "SVGElementInstance.h" 39 #include "SVGNames.h" 40 #include "SVGURIReference.h" 41 #include "SVGUseElement.h" 42 #include "XLinkNames.h" 43 #include <math.h> 44 #include <wtf/StdLibExtras.h> 45 46 using namespace std; 47 48 namespace WebCore { 49 50 SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* doc) 51 : SVGSMILElement(tagName, doc) 52 , SVGTests() 53 , SVGExternalResourcesRequired() 54 , m_animationValid(false) 55 { 56 } 57 58 SVGAnimationElement::~SVGAnimationElement() 59 { 60 } 61 62 static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder) 63 { 64 result.clear(); 65 Vector<String> parseList; 66 parse.split(';', parseList); 67 for (unsigned n = 0; n < parseList.size(); ++n) { 68 String timeString = parseList[n]; 69 bool ok; 70 float time = timeString.toFloat(&ok); 71 if (!ok || time < 0 || time > 1.f) 72 goto fail; 73 if (verifyOrder) { 74 if (!n) { 75 if (time != 0) 76 goto fail; 77 } else if (time < result.last()) 78 goto fail; 79 } 80 result.append(time); 81 } 82 return; 83 fail: 84 result.clear(); 85 } 86 87 static void parseKeySplines(const String& parse, Vector<UnitBezier>& result) 88 { 89 result.clear(); 90 Vector<String> parseList; 91 parse.split(';', parseList); 92 for (unsigned n = 0; n < parseList.size(); ++n) { 93 Vector<String> parseSpline; 94 parseList[n].split(',', parseSpline); 95 // The spec says the sepator is a space, all tests use commas. Weird. 96 if (parseSpline.size() == 1) 97 parseList[n].split(' ', parseSpline); 98 if (parseSpline.size() != 4) 99 goto fail; 100 double curveValues[4]; 101 for (unsigned i = 0; i < 4; ++i) { 102 String parseNumber = parseSpline[i]; 103 bool ok; 104 curveValues[i] = parseNumber.toDouble(&ok); 105 if (!ok || curveValues[i] < 0.0 || curveValues[i] > 1.0) 106 goto fail; 107 } 108 result.append(UnitBezier(curveValues[0], curveValues[1], curveValues[2], curveValues[3])); 109 } 110 return; 111 fail: 112 result.clear(); 113 } 114 115 void SVGAnimationElement::parseMappedAttribute(MappedAttribute* attr) 116 { 117 if (attr->name() == SVGNames::valuesAttr) 118 attr->value().string().split(';', m_values); 119 else if (attr->name() == SVGNames::keyTimesAttr) 120 parseKeyTimes(attr->value(), m_keyTimes, true); 121 else if (attr->name() == SVGNames::keyPointsAttr && hasTagName(SVGNames::animateMotionTag)) { 122 // This is specified to be an animateMotion attribute only but it is simpler to put it here 123 // where the other timing calculatations are. 124 parseKeyTimes(attr->value(), m_keyPoints, false); 125 } else if (attr->name() == SVGNames::keySplinesAttr) 126 parseKeySplines(attr->value(), m_keySplines); 127 else { 128 if (SVGTests::parseMappedAttribute(attr)) 129 return; 130 if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) 131 return; 132 SVGSMILElement::parseMappedAttribute(attr); 133 } 134 } 135 136 void SVGAnimationElement::attributeChanged(Attribute* attr, bool preserveDecls) 137 { 138 // Assumptions may not hold after an attribute change. 139 m_animationValid = false; 140 SVGSMILElement::attributeChanged(attr, preserveDecls); 141 } 142 143 void SVGAnimationElement::synchronizeProperty(const QualifiedName& attrName) 144 { 145 SVGSMILElement::synchronizeProperty(attrName); 146 147 if (attrName == anyQName() || SVGExternalResourcesRequired::isKnownAttribute(attrName)) 148 synchronizeExternalResourcesRequired(); 149 } 150 151 float SVGAnimationElement::getStartTime() const 152 { 153 return narrowPrecisionToFloat(intervalBegin().value()); 154 } 155 156 float SVGAnimationElement::getCurrentTime() const 157 { 158 return narrowPrecisionToFloat(elapsed().value()); 159 } 160 161 float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const 162 { 163 return narrowPrecisionToFloat(simpleDuration().value()); 164 } 165 166 void SVGAnimationElement::beginElement() 167 { 168 beginElementAt(0); 169 } 170 171 void SVGAnimationElement::beginElementAt(float offset) 172 { 173 addBeginTime(elapsed() + offset); 174 } 175 176 void SVGAnimationElement::endElement() 177 { 178 endElementAt(0); 179 } 180 181 void SVGAnimationElement::endElementAt(float offset) 182 { 183 addEndTime(elapsed() + offset); 184 } 185 186 SVGAnimationElement::AnimationMode SVGAnimationElement::animationMode() const 187 { 188 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues 189 if (hasTagName(SVGNames::setTag)) 190 return ToAnimation; 191 if (!animationPath().isEmpty()) 192 return PathAnimation; 193 if (hasAttribute(SVGNames::valuesAttr)) 194 return ValuesAnimation; 195 if (!toValue().isEmpty()) 196 return fromValue().isEmpty() ? ToAnimation : FromToAnimation; 197 if (!byValue().isEmpty()) 198 return fromValue().isEmpty() ? ByAnimation : FromByAnimation; 199 return NoAnimation; 200 } 201 202 SVGAnimationElement::CalcMode SVGAnimationElement::calcMode() const 203 { 204 DEFINE_STATIC_LOCAL(const AtomicString, discrete, ("discrete")); 205 DEFINE_STATIC_LOCAL(const AtomicString, linear, ("linear")); 206 DEFINE_STATIC_LOCAL(const AtomicString, paced, ("paced")); 207 DEFINE_STATIC_LOCAL(const AtomicString, spline, ("spline")); 208 const AtomicString& value = getAttribute(SVGNames::calcModeAttr); 209 if (value == discrete) 210 return CalcModeDiscrete; 211 if (value == linear) 212 return CalcModeLinear; 213 if (value == paced) 214 return CalcModePaced; 215 if (value == spline) 216 return CalcModeSpline; 217 return hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear; 218 } 219 220 SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const 221 { 222 DEFINE_STATIC_LOCAL(const AtomicString, css, ("CSS")); 223 DEFINE_STATIC_LOCAL(const AtomicString, xml, ("XML")); 224 const AtomicString& value = getAttribute(SVGNames::attributeTypeAttr); 225 if (value == css) 226 return AttributeTypeCSS; 227 if (value == xml) 228 return AttributeTypeXML; 229 return AttributeTypeAuto; 230 } 231 232 String SVGAnimationElement::toValue() const 233 { 234 return getAttribute(SVGNames::toAttr); 235 } 236 237 String SVGAnimationElement::byValue() const 238 { 239 return getAttribute(SVGNames::byAttr); 240 } 241 242 String SVGAnimationElement::fromValue() const 243 { 244 return getAttribute(SVGNames::fromAttr); 245 } 246 247 bool SVGAnimationElement::isAdditive() const 248 { 249 DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum")); 250 const AtomicString& value = getAttribute(SVGNames::additiveAttr); 251 return value == sum || animationMode() == ByAnimation; 252 } 253 254 bool SVGAnimationElement::isAccumulated() const 255 { 256 DEFINE_STATIC_LOCAL(const AtomicString, sum, ("sum")); 257 const AtomicString& value = getAttribute(SVGNames::accumulateAttr); 258 return value == sum && animationMode() != ToAnimation; 259 } 260 261 bool SVGAnimationElement::hasValidTarget() const 262 { 263 return targetElement(); 264 } 265 266 bool SVGAnimationElement::attributeIsCSS(const String& attributeName) 267 { 268 // FIXME: We should have a map of all SVG properties and their attribute types so we 269 // could validate animations better. The spec is very vague about this. 270 unsigned id = cssPropertyID(attributeName); 271 // SVG range 272 if (id >= CSSPropertyClipPath && id <= CSSPropertyWritingMode) 273 return true; 274 // Regular CSS properties also in SVG 275 return id == CSSPropertyColor || id == CSSPropertyDisplay || id == CSSPropertyOpacity 276 || (id >= CSSPropertyFont && id <= CSSPropertyFontWeight) 277 || id == CSSPropertyOverflow || id == CSSPropertyVisibility; 278 } 279 280 bool SVGAnimationElement::targetAttributeIsCSS() const 281 { 282 AttributeType type = attributeType(); 283 if (type == AttributeTypeCSS) 284 return true; 285 if (type == AttributeTypeXML) 286 return false; 287 return attributeIsCSS(attributeName()); 288 } 289 290 void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value) 291 { 292 if (!hasValidTarget()) 293 return; 294 SVGElement* target = targetElement(); 295 String attributeName = this->attributeName(); 296 if (!target || attributeName.isEmpty() || value.isNull()) 297 return; 298 299 // We don't want the instance tree to get rebuild. Instances are updated in the loop below. 300 if (target->isStyled()) 301 static_cast<SVGStyledElement*>(target)->setInstanceUpdatesBlocked(true); 302 303 ExceptionCode ec; 304 bool isCSS = targetAttributeIsCSS(); 305 if (isCSS) { 306 // FIXME: This should set the override style, not the inline style. 307 // Sadly override styles are not yet implemented. 308 target->style()->setProperty(attributeName, value, "", ec); 309 } else { 310 // FIXME: This should set the 'presentation' value, not the actual 311 // attribute value. Whatever that means in practice. 312 target->setAttribute(attributeName, value, ec); 313 } 314 315 if (target->isStyled()) 316 static_cast<SVGStyledElement*>(target)->setInstanceUpdatesBlocked(false); 317 318 // If the target element is used in an <use> instance tree, update that as well. 319 const HashSet<SVGElementInstance*>& instances = target->instancesForElement(); 320 const HashSet<SVGElementInstance*>::const_iterator end = instances.end(); 321 for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) { 322 SVGElement* shadowTreeElement = (*it)->shadowTreeElement(); 323 ASSERT(shadowTreeElement); 324 if (isCSS) 325 shadowTreeElement->style()->setProperty(attributeName, value, "", ec); 326 else 327 shadowTreeElement->setAttribute(attributeName, value, ec); 328 (*it)->correspondingUseElement()->setNeedsStyleRecalc(); 329 } 330 } 331 332 void SVGAnimationElement::calculateKeyTimesForCalcModePaced() 333 { 334 ASSERT(calcMode() == CalcModePaced); 335 ASSERT(animationMode() == ValuesAnimation); 336 337 unsigned valuesCount = m_values.size(); 338 ASSERT(valuesCount > 1); 339 Vector<float> keyTimesForPaced; 340 float totalDistance = 0; 341 keyTimesForPaced.append(0); 342 for (unsigned n = 0; n < valuesCount - 1; ++n) { 343 // Distance in any units 344 float distance = calculateDistance(m_values[n], m_values[n + 1]); 345 if (distance < 0) 346 return; 347 totalDistance += distance; 348 keyTimesForPaced.append(distance); 349 } 350 if (!totalDistance) 351 return; 352 353 // Normalize. 354 for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n) 355 keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance; 356 keyTimesForPaced[keyTimesForPaced.size() - 1] = 1.f; 357 358 // Use key times calculated based on pacing instead of the user provided ones. 359 m_keyTimes.swap(keyTimesForPaced); 360 } 361 362 static inline double solveEpsilon(double duration) { return 1. / (200. * duration); } 363 364 float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const 365 { 366 ASSERT(calcMode() == CalcModeSpline); 367 ASSERT(splineIndex < m_keySplines.size()); 368 UnitBezier bezier = m_keySplines[splineIndex]; 369 SMILTime duration = simpleDuration(); 370 if (!duration.isFinite()) 371 duration = 100.0; 372 return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value()))); 373 } 374 375 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const 376 { 377 ASSERT(!m_keyPoints.isEmpty()); 378 ASSERT(calcMode() != CalcModePaced); 379 unsigned keyTimesCount = m_keyTimes.size(); 380 ASSERT(keyTimesCount > 1); 381 ASSERT(m_keyPoints.size() == keyTimesCount); 382 383 unsigned index; 384 for (index = 1; index < keyTimesCount; ++index) { 385 if (m_keyTimes[index] >= percent) 386 break; 387 } 388 --index; 389 390 float fromPercent = m_keyTimes[index]; 391 float toPercent = m_keyTimes[index + 1]; 392 float fromKeyPoint = m_keyPoints[index]; 393 float toKeyPoint = m_keyPoints[index + 1]; 394 395 if (calcMode() == CalcModeDiscrete) 396 return percent == 1.0f ? toKeyPoint : fromKeyPoint; 397 398 float keyPointPercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent); 399 400 if (calcMode() == CalcModeSpline) { 401 ASSERT(m_keySplines.size() == m_keyPoints.size() - 1); 402 keyPointPercent = calculatePercentForSpline(keyPointPercent, index); 403 } 404 return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint; 405 } 406 407 void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const 408 { 409 ASSERT(!m_keyPoints.isEmpty()); 410 ASSERT(m_keyPoints.size() == m_keyTimes.size()); 411 ASSERT(calcMode() != CalcModePaced); 412 effectivePercent = calculatePercentFromKeyPoints(percent); 413 unsigned index = effectivePercent == 1.0f ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1)); 414 from = m_values[index]; 415 to = m_values[index + 1]; 416 } 417 418 void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to) const 419 { 420 unsigned valuesCount = m_values.size(); 421 ASSERT(m_animationValid); 422 ASSERT(valuesCount > 1); 423 424 CalcMode calcMode = this->calcMode(); 425 if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced) 426 return currentValuesFromKeyPoints(percent, effectivePercent, from, to); 427 428 unsigned keyTimesCount = m_keyTimes.size(); 429 ASSERT(!keyTimesCount || valuesCount == keyTimesCount); 430 ASSERT(!keyTimesCount || (keyTimesCount > 1 && m_keyTimes[0] == 0)); 431 432 unsigned index; 433 for (index = 1; index < keyTimesCount; ++index) { 434 if (m_keyTimes[index] >= percent) 435 break; 436 } 437 --index; 438 439 if (calcMode == CalcModeDiscrete) { 440 if (!keyTimesCount) 441 index = percent == 1.0f ? valuesCount - 1 : static_cast<unsigned>(percent * valuesCount); 442 from = m_values[index]; 443 to = m_values[index]; 444 effectivePercent = 0.0f; 445 return; 446 } 447 448 float fromPercent; 449 float toPercent; 450 if (keyTimesCount) { 451 fromPercent = m_keyTimes[index]; 452 toPercent = m_keyTimes[index + 1]; 453 } else { 454 index = static_cast<unsigned>(percent * (valuesCount - 1)); 455 fromPercent = static_cast<float>(index) / (valuesCount - 1); 456 toPercent = static_cast<float>(index + 1) / (valuesCount - 1); 457 } 458 459 if (index == valuesCount - 1) 460 --index; 461 from = m_values[index]; 462 to = m_values[index + 1]; 463 ASSERT(toPercent > fromPercent); 464 effectivePercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent); 465 466 if (calcMode == CalcModeSpline) { 467 ASSERT(m_keySplines.size() == m_values.size() - 1); 468 effectivePercent = calculatePercentForSpline(effectivePercent, index); 469 } 470 } 471 472 void SVGAnimationElement::startedActiveInterval() 473 { 474 m_animationValid = false; 475 476 if (!hasValidTarget()) 477 return; 478 479 AnimationMode animationMode = this->animationMode(); 480 if (animationMode == NoAnimation) 481 return; 482 if (animationMode == FromToAnimation) 483 m_animationValid = calculateFromAndToValues(fromValue(), toValue()); 484 else if (animationMode == ToAnimation) { 485 // For to-animations the from value is the current accumulated value from lower priority animations. 486 // The value is not static and is determined during the animation. 487 m_animationValid = calculateFromAndToValues(String(), toValue()); 488 } else if (animationMode == FromByAnimation) 489 m_animationValid = calculateFromAndByValues(fromValue(), byValue()); 490 else if (animationMode == ByAnimation) 491 m_animationValid = calculateFromAndByValues(String(), byValue()); 492 else if (animationMode == ValuesAnimation) { 493 CalcMode calcMode = this->calcMode(); 494 m_animationValid = m_values.size() > 1 495 && (calcMode == CalcModePaced || !hasAttribute(SVGNames::keyTimesAttr) || hasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size())) 496 && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1.0) 497 && (calcMode != CalcModeSpline || ((m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1)) || m_keySplines.size() == m_keyPoints.size() - 1)) 498 && (!hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size())); 499 if (calcMode == CalcModePaced && m_animationValid) 500 calculateKeyTimesForCalcModePaced(); 501 } else if (animationMode == PathAnimation) 502 m_animationValid = calcMode() == CalcModePaced || !hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()); 503 } 504 505 void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement* resultElement) 506 { 507 if (!m_animationValid) 508 return; 509 510 float effectivePercent; 511 if (animationMode() == ValuesAnimation) { 512 String from; 513 String to; 514 currentValuesForValuesAnimation(percent, effectivePercent, from, to); 515 if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo ) { 516 m_animationValid = calculateFromAndToValues(from, to); 517 if (!m_animationValid) 518 return; 519 m_lastValuesAnimationFrom = from; 520 m_lastValuesAnimationTo = to; 521 } 522 } else if (!m_keyPoints.isEmpty() && calcMode() != CalcModePaced) 523 effectivePercent = calculatePercentFromKeyPoints(percent); 524 else 525 effectivePercent = percent; 526 527 calculateAnimatedValue(effectivePercent, repeat, resultElement); 528 } 529 530 void SVGAnimationElement::endedActiveInterval() 531 { 532 } 533 534 } 535 536 // vim:ts=4:noet 537 #endif // ENABLE(SVG_ANIMATION) 538 539