• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

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 

KHTML

Skip menu "KHTML"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal