Home | History | Annotate | Download | only in svg
      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