KHTML
SVGAnimationElement.cpp
Go to the documentation of this file.
00001 /* 00002 Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org> 00003 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org> 00004 Copyright (C) 2007 Eric Seidel <eric@webkit.org> 00005 Copyright (C) 2008 Apple Inc. All rights reserved. 00006 00007 This file is part of the KDE project 00008 00009 This library is free software; you can redistribute it and/or 00010 modify it under the terms of the GNU Library General Public 00011 License as published by the Free Software Foundation; either 00012 version 2 of the License, or (at your option) any later version. 00013 00014 This library is distributed in the hope that it will be useful, 00015 but WITHOUT ANY WARRANTY; without even the implied warranty of 00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00017 Library General Public License for more details. 00018 00019 You should have received a copy of the GNU Library General Public License 00020 along with this library; see the file COPYING.LIB. If not, write to 00021 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00022 Boston, MA 02110-1301, USA. 00023 */ 00024 00025 #include "config.h" 00026 #if ENABLE(SVG_ANIMATION) 00027 #include "SVGAnimationElement.h" 00028 00029 #include "CSSComputedStyleDeclaration.h" 00030 #include "CSSParser.h" 00031 #include "CSSPropertyNames.h" 00032 #include "Document.h" 00033 #include "Event.h" 00034 #include "EventListener.h" 00035 #include "FloatConversion.h" 00036 #include "HTMLNames.h" 00037 #include "SVGElementInstance.h" 00038 #include "SVGNames.h" 00039 #include "SVGURIReference.h" 00040 #include "SVGUseElement.h" 00041 #include "XLinkNames.h" 00042 #include <math.h> 00043 00044 using namespace std; 00045 00046 namespace WebCore { 00047 00048 SVGAnimationElement::SVGAnimationElement(const QualifiedName& tagName, Document* doc) 00049 : SVGSMILElement(tagName, doc) 00050 , SVGTests() 00051 , SVGExternalResourcesRequired() 00052 , m_animationValid(false) 00053 { 00054 } 00055 00056 SVGAnimationElement::~SVGAnimationElement() 00057 { 00058 } 00059 00060 static void parseKeyTimes(const String& parse, Vector<float>& result, bool verifyOrder) 00061 { 00062 result.clear(); 00063 Vector<String> parseList; 00064 parse.split(';', parseList); 00065 for (unsigned n = 0; n < parseList.size(); ++n) { 00066 String timeString = parseList[n]; 00067 bool ok; 00068 float time = timeString.toFloat(&ok); 00069 if (!ok || time < 0 || time > 1.f) 00070 goto fail; 00071 if (verifyOrder) { 00072 if (!n) { 00073 if (time != 0) 00074 goto fail; 00075 } else if (time < result.last()) 00076 goto fail; 00077 } 00078 result.append(time); 00079 } 00080 return; 00081 fail: 00082 result.clear(); 00083 } 00084 00085 static void parseKeySplines(const String& parse, Vector<UnitBezier>& result) 00086 { 00087 result.clear(); 00088 Vector<String> parseList; 00089 parse.split(';', parseList); 00090 for (unsigned n = 0; n < parseList.size(); ++n) { 00091 Vector<String> parseSpline; 00092 parseList[n].split(',', parseSpline); 00093 // The spec says the sepator is a space, all tests use commas. Weird. 00094 if (parseSpline.size() == 1) 00095 parseList[n].split(' ', parseSpline); 00096 if (parseSpline.size() != 4) 00097 goto fail; 00098 double curveValues[4]; 00099 for (unsigned i = 0; i < 4; ++i) { 00100 String parseNumber = parseSpline[i]; 00101 bool ok; 00102 curveValues[i] = parseNumber.toDouble(&ok); 00103 if (!ok || curveValues[i] < 0.0 || curveValues[i] > 1.0) 00104 goto fail; 00105 } 00106 result.append(UnitBezier(curveValues[0], curveValues[1], curveValues[2], curveValues[3])); 00107 } 00108 return; 00109 fail: 00110 result.clear(); 00111 } 00112 00113 void SVGAnimationElement::parseMappedAttribute(MappedAttribute* attr) 00114 { 00115 if (attr->name() == SVGNames::valuesAttr) 00116 attr->value().string().split(';', m_values); 00117 else if (attr->name() == SVGNames::keyTimesAttr) 00118 parseKeyTimes(attr->value(), m_keyTimes, true); 00119 else if (attr->name() == SVGNames::keyPointsAttr && hasTagName(SVGNames::animateMotionTag)) { 00120 // This is specified to be an animateMotion attribute only but it is simpler to put it here 00121 // where the other timing calculatations are. 00122 parseKeyTimes(attr->value(), m_keyPoints, false); 00123 } else if (attr->name() == SVGNames::keySplinesAttr) 00124 parseKeySplines(attr->value(), m_keySplines); 00125 else { 00126 if (SVGTests::parseMappedAttribute(attr)) 00127 return; 00128 if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) 00129 return; 00130 SVGSMILElement::parseMappedAttribute(attr); 00131 } 00132 } 00133 00134 void SVGAnimationElement::attributeChanged(Attribute* attr, bool preserveDecls) 00135 { 00136 // Assumptions may not hold after an attribute change. 00137 m_animationValid = false; 00138 SVGSMILElement::attributeChanged(attr, preserveDecls); 00139 } 00140 00141 float SVGAnimationElement::getStartTime() const 00142 { 00143 return narrowPrecisionToFloat(intervalBegin().value()); 00144 } 00145 00146 float SVGAnimationElement::getCurrentTime() const 00147 { 00148 return narrowPrecisionToFloat(elapsed().value()); 00149 } 00150 00151 float SVGAnimationElement::getSimpleDuration(ExceptionCode&) const 00152 { 00153 return narrowPrecisionToFloat(simpleDuration().value()); 00154 } 00155 00156 bool SVGAnimationElement::beginElement(ExceptionCode& ec) 00157 { 00158 return beginElementAt(0, ec); 00159 } 00160 00161 bool SVGAnimationElement::beginElementAt(float offset, ExceptionCode& ec) 00162 { 00163 addBeginTime(elapsed() + offset); 00164 return true; 00165 } 00166 00167 bool SVGAnimationElement::endElement(ExceptionCode& ec) 00168 { 00169 return endElementAt(0, ec); 00170 } 00171 00172 bool SVGAnimationElement::endElementAt(float offset, ExceptionCode& ec) 00173 { 00174 if (offset < 0) 00175 return false; 00176 00177 addEndTime(elapsed() + offset); 00178 return true; 00179 } 00180 00181 SVGAnimationElement::AnimationMode SVGAnimationElement::animationMode() const 00182 { 00183 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues 00184 if (hasTagName(SVGNames::setTag)) 00185 return ToAnimation; 00186 if (!animationPath().isEmpty()) 00187 return PathAnimation; 00188 if (hasAttribute(SVGNames::valuesAttr)) 00189 return ValuesAnimation; 00190 if (!toValue().isEmpty()) 00191 return fromValue().isEmpty() ? ToAnimation : FromToAnimation; 00192 if (!byValue().isEmpty()) 00193 return fromValue().isEmpty() ? ByAnimation : FromByAnimation; 00194 return NoAnimation; 00195 } 00196 00197 SVGAnimationElement::CalcMode SVGAnimationElement::calcMode() const 00198 { 00199 static const AtomicString discrete("discrete"); 00200 static const AtomicString linear("linear"); 00201 static const AtomicString paced("paced"); 00202 static const AtomicString spline("spline"); 00203 const AtomicString& value = getAttribute(SVGNames::calcModeAttr); 00204 if (value == discrete) 00205 return CalcModeDiscrete; 00206 if (value == linear) 00207 return CalcModeLinear; 00208 if (value == paced) 00209 return CalcModePaced; 00210 if (value == spline) 00211 return CalcModeSpline; 00212 return hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear; 00213 } 00214 00215 SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const 00216 { 00217 static const AtomicString css("CSS"); 00218 static const AtomicString xml("XML"); 00219 const AtomicString& value = getAttribute(SVGNames::attributeTypeAttr); 00220 if (value == css) 00221 return AttributeTypeCSS; 00222 if (value == xml) 00223 return AttributeTypeXML; 00224 return AttributeTypeAuto; 00225 } 00226 00227 String SVGAnimationElement::toValue() const 00228 { 00229 return getAttribute(SVGNames::toAttr); 00230 } 00231 00232 String SVGAnimationElement::byValue() const 00233 { 00234 return getAttribute(SVGNames::byAttr); 00235 } 00236 00237 String SVGAnimationElement::fromValue() const 00238 { 00239 return getAttribute(SVGNames::fromAttr); 00240 } 00241 00242 bool SVGAnimationElement::isAdditive() const 00243 { 00244 static const AtomicString sum("sum"); 00245 const AtomicString& value = getAttribute(SVGNames::additiveAttr); 00246 return value == sum || animationMode() == ByAnimation; 00247 } 00248 00249 bool SVGAnimationElement::isAccumulated() const 00250 { 00251 static const AtomicString sum("sum"); 00252 const AtomicString& value = getAttribute(SVGNames::accumulateAttr); 00253 return value == sum && animationMode() != ToAnimation; 00254 } 00255 00256 bool SVGAnimationElement::hasValidTarget() const 00257 { 00258 return targetElement(); 00259 } 00260 00261 bool SVGAnimationElement::attributeIsCSS(const String& attributeName) 00262 { 00263 // FIXME: We should have a map of all SVG properties and their attribute types so we 00264 // could validate animations better. The spec is very vague about this. 00265 unsigned id = cssPropertyID(attributeName); 00266 // SVG range 00267 if (id >= CSSPropertyClipPath && id <= CSSPropertyWritingMode) 00268 return true; 00269 // Regular CSS properties also in SVG 00270 return id == CSSPropertyColor || id == CSSPropertyDisplay || id == CSSPropertyOpacity 00271 || (id >= CSSPropertyFont && id <= CSSPropertyFontWeight) 00272 || id == CSSPropertyOverflow || id == CSSPropertyVisibility; 00273 } 00274 00275 bool SVGAnimationElement::targetAttributeIsCSS() const 00276 { 00277 AttributeType type = attributeType(); 00278 if (type == AttributeTypeCSS) 00279 return true; 00280 if (type == AttributeTypeXML) 00281 return false; 00282 return attributeIsCSS(attributeName()); 00283 } 00284 00285 void SVGAnimationElement::setTargetAttributeAnimatedValue(const String& value) 00286 { 00287 if (!hasValidTarget()) 00288 return; 00289 SVGElement* target = targetElement(); 00290 String attributeName = this->attributeName(); 00291 if (!target || attributeName.isEmpty() || value.isNull()) 00292 return; 00293 00294 // We don't want the instance tree to get rebuild. Instances are updated in the loop below. 00295 if (target->isStyled()) 00296 static_cast<SVGStyledElement*>(target)->setInstanceUpdatesBlocked(true); 00297 00298 ExceptionCode ec; 00299 bool isCSS = targetAttributeIsCSS(); 00300 if (isCSS) { 00301 // FIXME: This should set the override style, not the inline style. 00302 // Sadly override styles are not yet implemented. 00303 target->style()->setProperty(attributeName, value, "", ec); 00304 } else { 00305 // FIXME: This should set the 'presentation' value, not the actual 00306 // attribute value. Whatever that means in practice. 00307 target->setAttribute(attributeName, value, ec); 00308 } 00309 00310 if (target->isStyled()) 00311 static_cast<SVGStyledElement*>(target)->setInstanceUpdatesBlocked(false); 00312 00313 // If the target element is used in an <use> instance tree, update that as well. 00314 HashSet<SVGElementInstance*>* instances = document()->accessSVGExtensions()->instancesForElement(target); 00315 if (!instances) 00316 return; 00317 HashSet<SVGElementInstance*>::iterator end = instances->end(); 00318 for (HashSet<SVGElementInstance*>::iterator it = instances->begin(); it != end; ++it) { 00319 SVGElement* shadowTreeElement = (*it)->shadowTreeElement(); 00320 ASSERT(shadowTreeElement); 00321 if (isCSS) 00322 shadowTreeElement->style()->setProperty(attributeName, value, "", ec); 00323 else 00324 shadowTreeElement->setAttribute(attributeName, value, ec); 00325 (*it)->correspondingUseElement()->setChanged(); 00326 } 00327 } 00328 00329 void SVGAnimationElement::calculateKeyTimesForCalcModePaced() 00330 { 00331 ASSERT(calcMode() == CalcModePaced); 00332 ASSERT(animationMode() == ValuesAnimation); 00333 00334 unsigned valuesCount = m_values.size(); 00335 ASSERT(valuesCount > 1); 00336 Vector<float> keyTimesForPaced; 00337 float totalDistance = 0; 00338 keyTimesForPaced.append(0); 00339 for (unsigned n = 0; n < valuesCount - 1; ++n) { 00340 // Distance in any units 00341 float distance = calculateDistance(m_values[n], m_values[n + 1]); 00342 if (distance < 0) 00343 return; 00344 totalDistance += distance; 00345 keyTimesForPaced.append(distance); 00346 } 00347 if (!totalDistance) 00348 return; 00349 00350 // Normalize. 00351 for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n) 00352 keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance; 00353 keyTimesForPaced[keyTimesForPaced.size() - 1] = 1.f; 00354 00355 // Use key times calculated based on pacing instead of the user provided ones. 00356 m_keyTimes.swap(keyTimesForPaced); 00357 } 00358 00359 static inline double solveEpsilon(double duration) { return 1. / (200. * duration); } 00360 00361 float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const 00362 { 00363 ASSERT(calcMode() == CalcModeSpline); 00364 ASSERT(splineIndex < m_keySplines.size()); 00365 UnitBezier bezier = m_keySplines[splineIndex]; 00366 SMILTime duration = simpleDuration(); 00367 if (!duration.isFinite()) 00368 duration = 100.0; 00369 return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value()))); 00370 } 00371 00372 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const 00373 { 00374 ASSERT(!m_keyPoints.isEmpty()); 00375 ASSERT(calcMode() != CalcModePaced); 00376 unsigned keyTimesCount = m_keyTimes.size(); 00377 ASSERT(keyTimesCount > 1); 00378 ASSERT(m_keyPoints.size() == keyTimesCount); 00379 00380 unsigned index; 00381 for (index = 1; index < keyTimesCount; ++index) { 00382 if (m_keyTimes[index] >= percent) 00383 break; 00384 } 00385 --index; 00386 00387 float fromPercent = m_keyTimes[index]; 00388 float toPercent = m_keyTimes[index + 1]; 00389 float fromKeyPoint = m_keyPoints[index]; 00390 float toKeyPoint = m_keyPoints[index + 1]; 00391 00392 if (calcMode() == CalcModeDiscrete) 00393 return percent == 1.0f ? toKeyPoint : fromKeyPoint; 00394 00395 float keyPointPercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent); 00396 00397 if (calcMode() == CalcModeSpline) { 00398 ASSERT(m_keySplines.size() == m_keyPoints.size() - 1); 00399 keyPointPercent = calculatePercentForSpline(keyPointPercent, index); 00400 } 00401 return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint; 00402 } 00403 00404 void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float& effectivePercent, String& from, String& to) const 00405 { 00406 ASSERT(!m_keyPoints.isEmpty()); 00407 ASSERT(m_keyPoints.size() == m_keyTimes.size()); 00408 ASSERT(calcMode() != CalcModePaced); 00409 effectivePercent = calculatePercentFromKeyPoints(percent); 00410 unsigned index = effectivePercent == 1.0f ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1)); 00411 from = m_values[index]; 00412 to = m_values[index + 1]; 00413 } 00414 00415 void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float& effectivePercent, String& from, String& to) const 00416 { 00417 unsigned valuesCount = m_values.size(); 00418 ASSERT(m_animationValid); 00419 ASSERT(valuesCount > 1); 00420 00421 CalcMode calcMode = this->calcMode(); 00422 if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced) 00423 return currentValuesFromKeyPoints(percent, effectivePercent, from, to); 00424 00425 unsigned keyTimesCount = m_keyTimes.size(); 00426 ASSERT(!keyTimesCount || valuesCount == keyTimesCount); 00427 ASSERT(!keyTimesCount || (keyTimesCount > 1 && m_keyTimes[0] == 0)); 00428 00429 unsigned index; 00430 for (index = 1; index < keyTimesCount; ++index) { 00431 if (m_keyTimes[index] >= percent) 00432 break; 00433 } 00434 --index; 00435 00436 if (calcMode == CalcModeDiscrete) { 00437 if (!keyTimesCount) 00438 index = percent == 1.0f ? valuesCount - 1 : static_cast<unsigned>(percent * valuesCount); 00439 from = m_values[index]; 00440 to = m_values[index]; 00441 effectivePercent = 0.0f; 00442 return; 00443 } 00444 00445 float fromPercent; 00446 float toPercent; 00447 if (keyTimesCount) { 00448 fromPercent = m_keyTimes[index]; 00449 toPercent = m_keyTimes[index + 1]; 00450 } else { 00451 index = static_cast<unsigned>(percent * (valuesCount - 1)); 00452 fromPercent = static_cast<float>(index) / (valuesCount - 1); 00453 toPercent = static_cast<float>(index + 1) / (valuesCount - 1); 00454 } 00455 00456 if (index == valuesCount - 1) 00457 --index; 00458 from = m_values[index]; 00459 to = m_values[index + 1]; 00460 ASSERT(toPercent > fromPercent); 00461 effectivePercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent); 00462 00463 if (calcMode == CalcModeSpline) { 00464 ASSERT(m_keySplines.size() == m_values.size() - 1); 00465 effectivePercent = calculatePercentForSpline(effectivePercent, index); 00466 } 00467 } 00468 00469 void SVGAnimationElement::startedActiveInterval() 00470 { 00471 m_animationValid = false; 00472 00473 if (!hasValidTarget()) 00474 return; 00475 00476 AnimationMode animationMode = this->animationMode(); 00477 if (animationMode == NoAnimation) 00478 return; 00479 if (animationMode == FromToAnimation) 00480 m_animationValid = calculateFromAndToValues(fromValue(), toValue()); 00481 else if (animationMode == ToAnimation) { 00482 // For to-animations the from value is the current accumulated value from lower priority animations. 00483 // The value is not static and is determined during the animation. 00484 m_animationValid = calculateFromAndToValues(String(), toValue()); 00485 } else if (animationMode == FromByAnimation) 00486 m_animationValid = calculateFromAndByValues(fromValue(), byValue()); 00487 else if (animationMode == ByAnimation) 00488 m_animationValid = calculateFromAndByValues(String(), byValue()); 00489 else if (animationMode == ValuesAnimation) { 00490 CalcMode calcMode = this->calcMode(); 00491 m_animationValid = m_values.size() > 1 00492 && (calcMode == CalcModePaced || !hasAttribute(SVGNames::keyTimesAttr) || hasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size())) 00493 && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1.0) 00494 && (calcMode != CalcModeSpline || (m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1) || m_keySplines.size() == m_keyPoints.size() - 1)) 00495 && (!hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size())); 00496 if (calcMode == CalcModePaced && m_animationValid) 00497 calculateKeyTimesForCalcModePaced(); 00498 } else if (animationMode == PathAnimation) 00499 m_animationValid = calcMode() == CalcModePaced || !hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()); 00500 } 00501 00502 void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement* resultElement) 00503 { 00504 if (!m_animationValid) 00505 return; 00506 00507 float effectivePercent; 00508 if (animationMode() == ValuesAnimation) { 00509 String from; 00510 String to; 00511 currentValuesForValuesAnimation(percent, effectivePercent, from, to); 00512 if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo ) { 00513 m_animationValid = calculateFromAndToValues(from, to); 00514 if (!m_animationValid) 00515 return; 00516 m_lastValuesAnimationFrom = from; 00517 m_lastValuesAnimationTo = to; 00518 } 00519 } else if (!m_keyPoints.isEmpty() && calcMode() != CalcModePaced) 00520 effectivePercent = calculatePercentFromKeyPoints(percent); 00521 else 00522 effectivePercent = percent; 00523 00524 calculateAnimatedValue(effectivePercent, repeat, resultElement); 00525 } 00526 00527 void SVGAnimationElement::endedActiveInterval() 00528 { 00529 } 00530 00531 } 00532 00533 // vim:ts=4:noet 00534 #endif // ENABLE(SVG_ANIMATION) 00535
KDE 4.6 API Reference