File indexing completed on 2024-04-28 15:24:28

0001 /*
0002     Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
0003                   2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
0004     Copyright (C) 2007 Eric Seidel <eric@webkit.org>
0005     Copyright (C) 2008 Apple Inc. All rights reserved.
0006 
0007     This file is part of the KDE project
0008 
0009     This library is free software; you can redistribute it and/or
0010     modify it under the terms of the GNU Library General Public
0011     License as published by the Free Software Foundation; either
0012     version 2 of the License, or (at your option) any later version.
0013 
0014     This library is distributed in the hope that it will be useful,
0015     but WITHOUT ANY WARRANTY; without even the implied warranty of
0016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0017     Library General Public License for more details.
0018 
0019     You should have received a copy of the GNU Library General Public License
0020     along with this library; see the file COPYING.LIB.  If not, write to
0021     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0022     Boston, MA 02110-1301, USA.
0023 */
0024 
0025 #if ENABLE(SVG_ANIMATION)
0026 #include "SVGAnimationElement.h"
0027 
0028 #include "CSSComputedStyleDeclaration.h"
0029 #include "CSSParser.h"
0030 #include "CSSPropertyNames.h"
0031 #include "Document.h"
0032 #include "Event.h"
0033 #include "EventListener.h"
0034 #include "FloatConversion.h"
0035 #include "HTMLNames.h"
0036 #include "SVGElementInstance.h"
0037 #include "SVGNames.h"
0038 #include "SVGURIReference.h"
0039 #include "SVGUseElement.h"
0040 #include "XLinkNames.h"
0041 #include <math.h>
0042 
0043 using namespace std;
0044 
0045 namespace WebCore
0046 {
0047 
0048 SVGAnimationElement::SVGAnimationElement(const QualifiedName &tagName, Document *doc)
0049     : SVGSMILElement(tagName, doc)
0050     , SVGTests()
0051     , SVGExternalResourcesRequired()
0052     , m_animationValid(false)
0053 {
0054 }
0055 
0056 SVGAnimationElement::~SVGAnimationElement()
0057 {
0058 }
0059 
0060 static void parseKeyTimes(const String &parse, Vector<float> &result, bool verifyOrder)
0061 {
0062     result.clear();
0063     Vector<String> parseList;
0064     parse.split(';', parseList);
0065     for (unsigned n = 0; n < parseList.size(); ++n) {
0066         String timeString = parseList[n];
0067         bool ok;
0068         float time = timeString.toFloat(&ok);
0069         if (!ok || time < 0 || time > 1.f) {
0070             goto fail;
0071         }
0072         if (verifyOrder) {
0073             if (!n) {
0074                 if (time != 0) {
0075                     goto fail;
0076                 }
0077             } else if (time < result.last()) {
0078                 goto fail;
0079             }
0080         }
0081         result.append(time);
0082     }
0083     return;
0084 fail:
0085     result.clear();
0086 }
0087 
0088 static void parseKeySplines(const String &parse, Vector<UnitBezier> &result)
0089 {
0090     result.clear();
0091     Vector<String> parseList;
0092     parse.split(';', parseList);
0093     for (unsigned n = 0; n < parseList.size(); ++n) {
0094         Vector<String> parseSpline;
0095         parseList[n].split(',', parseSpline);
0096         // The spec says the sepator is a space, all tests use commas. Weird.
0097         if (parseSpline.size() == 1) {
0098             parseList[n].split(' ', parseSpline);
0099         }
0100         if (parseSpline.size() != 4) {
0101             goto fail;
0102         }
0103         double curveValues[4];
0104         for (unsigned i = 0; i < 4; ++i) {
0105             String parseNumber = parseSpline[i];
0106             bool ok;
0107             curveValues[i] = parseNumber.toDouble(&ok);
0108             if (!ok || curveValues[i] < 0.0 || curveValues[i] > 1.0) {
0109                 goto fail;
0110             }
0111         }
0112         result.append(UnitBezier(curveValues[0], curveValues[1], curveValues[2], curveValues[3]));
0113     }
0114     return;
0115 fail:
0116     result.clear();
0117 }
0118 
0119 void SVGAnimationElement::parseMappedAttribute(MappedAttribute *attr)
0120 {
0121     if (attr->name() == SVGNames::valuesAttr) {
0122         attr->value().string().split(';', m_values);
0123     } else if (attr->name() == SVGNames::keyTimesAttr) {
0124         parseKeyTimes(attr->value(), m_keyTimes, true);
0125     } else if (attr->name() == SVGNames::keyPointsAttr && hasTagName(SVGNames::animateMotionTag)) {
0126         // This is specified to be an animateMotion attribute only but it is simpler to put it here
0127         // where the other timing calculatations are.
0128         parseKeyTimes(attr->value(), m_keyPoints, false);
0129     } else if (attr->name() == SVGNames::keySplinesAttr) {
0130         parseKeySplines(attr->value(), m_keySplines);
0131     } else {
0132         if (SVGTests::parseMappedAttribute(attr)) {
0133             return;
0134         }
0135         if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) {
0136             return;
0137         }
0138         SVGSMILElement::parseMappedAttribute(attr);
0139     }
0140 }
0141 
0142 void SVGAnimationElement::attributeChanged(Attribute *attr, bool preserveDecls)
0143 {
0144     // Assumptions may not hold after an attribute change.
0145     m_animationValid = false;
0146     SVGSMILElement::attributeChanged(attr, preserveDecls);
0147 }
0148 
0149 float SVGAnimationElement::getStartTime() const
0150 {
0151     return narrowPrecisionToFloat(intervalBegin().value());
0152 }
0153 
0154 float SVGAnimationElement::getCurrentTime() const
0155 {
0156     return narrowPrecisionToFloat(elapsed().value());
0157 }
0158 
0159 float SVGAnimationElement::getSimpleDuration(ExceptionCode &) const
0160 {
0161     return narrowPrecisionToFloat(simpleDuration().value());
0162 }
0163 
0164 bool SVGAnimationElement::beginElement(ExceptionCode &ec)
0165 {
0166     return beginElementAt(0, ec);
0167 }
0168 
0169 bool SVGAnimationElement::beginElementAt(float offset, ExceptionCode &ec)
0170 {
0171     addBeginTime(elapsed() + offset);
0172     return true;
0173 }
0174 
0175 bool SVGAnimationElement::endElement(ExceptionCode &ec)
0176 {
0177     return endElementAt(0, ec);
0178 }
0179 
0180 bool SVGAnimationElement::endElementAt(float offset, ExceptionCode &ec)
0181 {
0182     if (offset < 0) {
0183         return false;
0184     }
0185 
0186     addEndTime(elapsed() + offset);
0187     return true;
0188 }
0189 
0190 SVGAnimationElement::AnimationMode SVGAnimationElement::animationMode() const
0191 {
0192     // https://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
0193     if (hasTagName(SVGNames::setTag)) {
0194         return ToAnimation;
0195     }
0196     if (!animationPath().isEmpty()) {
0197         return PathAnimation;
0198     }
0199     if (hasAttribute(SVGNames::valuesAttr)) {
0200         return ValuesAnimation;
0201     }
0202     if (!toValue().isEmpty()) {
0203         return fromValue().isEmpty() ? ToAnimation : FromToAnimation;
0204     }
0205     if (!byValue().isEmpty()) {
0206         return fromValue().isEmpty() ? ByAnimation : FromByAnimation;
0207     }
0208     return NoAnimation;
0209 }
0210 
0211 SVGAnimationElement::CalcMode SVGAnimationElement::calcMode() const
0212 {
0213     static const AtomicString discrete("discrete");
0214     static const AtomicString linear("linear");
0215     static const AtomicString paced("paced");
0216     static const AtomicString spline("spline");
0217     const AtomicString &value = getAttribute(SVGNames::calcModeAttr);
0218     if (value == discrete) {
0219         return CalcModeDiscrete;
0220     }
0221     if (value == linear) {
0222         return CalcModeLinear;
0223     }
0224     if (value == paced) {
0225         return CalcModePaced;
0226     }
0227     if (value == spline) {
0228         return CalcModeSpline;
0229     }
0230     return hasTagName(SVGNames::animateMotionTag) ? CalcModePaced : CalcModeLinear;
0231 }
0232 
0233 SVGAnimationElement::AttributeType SVGAnimationElement::attributeType() const
0234 {
0235     static const AtomicString css("CSS");
0236     static const AtomicString xml("XML");
0237     const AtomicString &value = getAttribute(SVGNames::attributeTypeAttr);
0238     if (value == css) {
0239         return AttributeTypeCSS;
0240     }
0241     if (value == xml) {
0242         return AttributeTypeXML;
0243     }
0244     return AttributeTypeAuto;
0245 }
0246 
0247 String SVGAnimationElement::toValue() const
0248 {
0249     return getAttribute(SVGNames::toAttr);
0250 }
0251 
0252 String SVGAnimationElement::byValue() const
0253 {
0254     return getAttribute(SVGNames::byAttr);
0255 }
0256 
0257 String SVGAnimationElement::fromValue() const
0258 {
0259     return getAttribute(SVGNames::fromAttr);
0260 }
0261 
0262 bool SVGAnimationElement::isAdditive() const
0263 {
0264     static const AtomicString sum("sum");
0265     const AtomicString &value = getAttribute(SVGNames::additiveAttr);
0266     return value == sum || animationMode() == ByAnimation;
0267 }
0268 
0269 bool SVGAnimationElement::isAccumulated() const
0270 {
0271     static const AtomicString sum("sum");
0272     const AtomicString &value = getAttribute(SVGNames::accumulateAttr);
0273     return value == sum && animationMode() != ToAnimation;
0274 }
0275 
0276 bool SVGAnimationElement::hasValidTarget() const
0277 {
0278     return targetElement();
0279 }
0280 
0281 bool SVGAnimationElement::attributeIsCSS(const String &attributeName)
0282 {
0283     // FIXME: We should have a map of all SVG properties and their attribute types so we
0284     // could validate animations better. The spec is very vague about this.
0285     unsigned id = cssPropertyID(attributeName);
0286     // SVG range
0287     if (id >= CSSPropertyClipPath && id <= CSSPropertyWritingMode) {
0288         return true;
0289     }
0290     // Regular CSS properties also in SVG
0291     return id == CSSPropertyColor || id == CSSPropertyDisplay || id == CSSPropertyOpacity
0292            || (id >= CSSPropertyFont && id <= CSSPropertyFontWeight)
0293            || id == CSSPropertyOverflow || id == CSSPropertyVisibility;
0294 }
0295 
0296 bool SVGAnimationElement::targetAttributeIsCSS() const
0297 {
0298     AttributeType type = attributeType();
0299     if (type == AttributeTypeCSS) {
0300         return true;
0301     }
0302     if (type == AttributeTypeXML) {
0303         return false;
0304     }
0305     return attributeIsCSS(attributeName());
0306 }
0307 
0308 void SVGAnimationElement::setTargetAttributeAnimatedValue(const String &value)
0309 {
0310     if (!hasValidTarget()) {
0311         return;
0312     }
0313     SVGElement *target = targetElement();
0314     String attributeName = this->attributeName();
0315     if (!target || attributeName.isEmpty() || value.isNull()) {
0316         return;
0317     }
0318 
0319     // We don't want the instance tree to get rebuild. Instances are updated in the loop below.
0320     if (target->isStyled()) {
0321         static_cast<SVGStyledElement *>(target)->setInstanceUpdatesBlocked(true);
0322     }
0323 
0324     ExceptionCode ec;
0325     bool isCSS = targetAttributeIsCSS();
0326     if (isCSS) {
0327         // FIXME: This should set the override style, not the inline style.
0328         // Sadly override styles are not yet implemented.
0329         target->style()->setProperty(attributeName, value, "", ec);
0330     } else {
0331         // FIXME: This should set the 'presentation' value, not the actual
0332         // attribute value. Whatever that means in practice.
0333         target->setAttribute(attributeName, value, ec);
0334     }
0335 
0336     if (target->isStyled()) {
0337         static_cast<SVGStyledElement *>(target)->setInstanceUpdatesBlocked(false);
0338     }
0339 
0340     // If the target element is used in an <use> instance tree, update that as well.
0341     HashSet<SVGElementInstance *> *instances = document()->accessSVGExtensions()->instancesForElement(target);
0342     if (!instances) {
0343         return;
0344     }
0345     HashSet<SVGElementInstance *>::iterator end = instances->end();
0346     for (HashSet<SVGElementInstance *>::iterator it = instances->begin(); it != end; ++it) {
0347         SVGElement *shadowTreeElement = (*it)->shadowTreeElement();
0348         ASSERT(shadowTreeElement);
0349         if (isCSS) {
0350             shadowTreeElement->style()->setProperty(attributeName, value, "", ec);
0351         } else {
0352             shadowTreeElement->setAttribute(attributeName, value, ec);
0353         }
0354         (*it)->correspondingUseElement()->setChanged();
0355     }
0356 }
0357 
0358 void SVGAnimationElement::calculateKeyTimesForCalcModePaced()
0359 {
0360     ASSERT(calcMode() == CalcModePaced);
0361     ASSERT(animationMode() == ValuesAnimation);
0362 
0363     unsigned valuesCount = m_values.size();
0364     ASSERT(valuesCount > 1);
0365     Vector<float> keyTimesForPaced;
0366     float totalDistance = 0;
0367     keyTimesForPaced.append(0);
0368     for (unsigned n = 0; n < valuesCount - 1; ++n) {
0369         // Distance in any units
0370         float distance = calculateDistance(m_values[n], m_values[n + 1]);
0371         if (distance < 0) {
0372             return;
0373         }
0374         totalDistance += distance;
0375         keyTimesForPaced.append(distance);
0376     }
0377     if (!totalDistance) {
0378         return;
0379     }
0380 
0381     // Normalize.
0382     for (unsigned n = 1; n < keyTimesForPaced.size() - 1; ++n) {
0383         keyTimesForPaced[n] = keyTimesForPaced[n - 1] + keyTimesForPaced[n] / totalDistance;
0384     }
0385     keyTimesForPaced[keyTimesForPaced.size() - 1] = 1.f;
0386 
0387     // Use key times calculated based on pacing instead of the user provided ones.
0388     m_keyTimes.swap(keyTimesForPaced);
0389 }
0390 
0391 static inline double solveEpsilon(double duration)
0392 {
0393     return 1. / (200. * duration);
0394 }
0395 
0396 float SVGAnimationElement::calculatePercentForSpline(float percent, unsigned splineIndex) const
0397 {
0398     ASSERT(calcMode() == CalcModeSpline);
0399     ASSERT(splineIndex < m_keySplines.size());
0400     UnitBezier bezier = m_keySplines[splineIndex];
0401     SMILTime duration = simpleDuration();
0402     if (!duration.isFinite()) {
0403         duration = 100.0;
0404     }
0405     return narrowPrecisionToFloat(bezier.solve(percent, solveEpsilon(duration.value())));
0406 }
0407 
0408 float SVGAnimationElement::calculatePercentFromKeyPoints(float percent) const
0409 {
0410     ASSERT(!m_keyPoints.isEmpty());
0411     ASSERT(calcMode() != CalcModePaced);
0412     unsigned keyTimesCount = m_keyTimes.size();
0413     ASSERT(keyTimesCount > 1);
0414     ASSERT(m_keyPoints.size() == keyTimesCount);
0415 
0416     unsigned index;
0417     for (index = 1; index < keyTimesCount; ++index) {
0418         if (m_keyTimes[index] >= percent) {
0419             break;
0420         }
0421     }
0422     --index;
0423 
0424     float fromPercent = m_keyTimes[index];
0425     float toPercent = m_keyTimes[index + 1];
0426     float fromKeyPoint = m_keyPoints[index];
0427     float toKeyPoint = m_keyPoints[index + 1];
0428 
0429     if (calcMode() == CalcModeDiscrete) {
0430         return percent == 1.0f ? toKeyPoint : fromKeyPoint;
0431     }
0432 
0433     float keyPointPercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent);
0434 
0435     if (calcMode() == CalcModeSpline) {
0436         ASSERT(m_keySplines.size() == m_keyPoints.size() - 1);
0437         keyPointPercent = calculatePercentForSpline(keyPointPercent, index);
0438     }
0439     return (toKeyPoint - fromKeyPoint) * keyPointPercent + fromKeyPoint;
0440 }
0441 
0442 void SVGAnimationElement::currentValuesFromKeyPoints(float percent, float &effectivePercent, String &from, String &to) const
0443 {
0444     ASSERT(!m_keyPoints.isEmpty());
0445     ASSERT(m_keyPoints.size() == m_keyTimes.size());
0446     ASSERT(calcMode() != CalcModePaced);
0447     effectivePercent = calculatePercentFromKeyPoints(percent);
0448     unsigned index = effectivePercent == 1.0f ? m_values.size() - 2 : static_cast<unsigned>(effectivePercent * (m_values.size() - 1));
0449     from = m_values[index];
0450     to = m_values[index + 1];
0451 }
0452 
0453 void SVGAnimationElement::currentValuesForValuesAnimation(float percent, float &effectivePercent, String &from, String &to) const
0454 {
0455     unsigned valuesCount = m_values.size();
0456     ASSERT(m_animationValid);
0457     ASSERT(valuesCount > 1);
0458 
0459     CalcMode calcMode = this->calcMode();
0460     if (!m_keyPoints.isEmpty() && calcMode != CalcModePaced) {
0461         return currentValuesFromKeyPoints(percent, effectivePercent, from, to);
0462     }
0463 
0464     unsigned keyTimesCount = m_keyTimes.size();
0465     ASSERT(!keyTimesCount || valuesCount == keyTimesCount);
0466     ASSERT(!keyTimesCount || (keyTimesCount > 1 && m_keyTimes[0] == 0));
0467 
0468     unsigned index;
0469     for (index = 1; index < keyTimesCount; ++index) {
0470         if (m_keyTimes[index] >= percent) {
0471             break;
0472         }
0473     }
0474     --index;
0475 
0476     if (calcMode == CalcModeDiscrete) {
0477         if (!keyTimesCount) {
0478             index = percent == 1.0f ? valuesCount - 1 : static_cast<unsigned>(percent * valuesCount);
0479         }
0480         from = m_values[index];
0481         to = m_values[index];
0482         effectivePercent = 0.0f;
0483         return;
0484     }
0485 
0486     float fromPercent;
0487     float toPercent;
0488     if (keyTimesCount) {
0489         fromPercent = m_keyTimes[index];
0490         toPercent = m_keyTimes[index + 1];
0491     } else {
0492         index = static_cast<unsigned>(percent * (valuesCount - 1));
0493         fromPercent =  static_cast<float>(index) / (valuesCount - 1);
0494         toPercent =  static_cast<float>(index + 1) / (valuesCount - 1);
0495     }
0496 
0497     if (index == valuesCount - 1) {
0498         --index;
0499     }
0500     from = m_values[index];
0501     to = m_values[index + 1];
0502     ASSERT(toPercent > fromPercent);
0503     effectivePercent = percent == 1.0f ? 1.0f : (percent - fromPercent) / (toPercent - fromPercent);
0504 
0505     if (calcMode == CalcModeSpline) {
0506         ASSERT(m_keySplines.size() == m_values.size() - 1);
0507         effectivePercent = calculatePercentForSpline(effectivePercent, index);
0508     }
0509 }
0510 
0511 void SVGAnimationElement::startedActiveInterval()
0512 {
0513     m_animationValid = false;
0514 
0515     if (!hasValidTarget()) {
0516         return;
0517     }
0518 
0519     AnimationMode animationMode = this->animationMode();
0520     if (animationMode == NoAnimation) {
0521         return;
0522     }
0523     if (animationMode == FromToAnimation) {
0524         m_animationValid = calculateFromAndToValues(fromValue(), toValue());
0525     } else if (animationMode == ToAnimation) {
0526         // For to-animations the from value is the current accumulated value from lower priority animations.
0527         // The value is not static and is determined during the animation.
0528         m_animationValid = calculateFromAndToValues(String(), toValue());
0529     } else if (animationMode == FromByAnimation) {
0530         m_animationValid = calculateFromAndByValues(fromValue(), byValue());
0531     } else if (animationMode == ByAnimation) {
0532         m_animationValid = calculateFromAndByValues(String(), byValue());
0533     } else if (animationMode == ValuesAnimation) {
0534         CalcMode calcMode = this->calcMode();
0535         m_animationValid = m_values.size() > 1
0536                            && (calcMode == CalcModePaced || !hasAttribute(SVGNames::keyTimesAttr) || hasAttribute(SVGNames::keyPointsAttr) || (m_values.size() == m_keyTimes.size()))
0537                            && (calcMode == CalcModeDiscrete || !m_keyTimes.size() || m_keyTimes.last() == 1.0)
0538                            && (calcMode != CalcModeSpline || (m_keySplines.size() && (m_keySplines.size() == m_values.size() - 1) || m_keySplines.size() == m_keyPoints.size() - 1))
0539                            && (!hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size()));
0540         if (calcMode == CalcModePaced && m_animationValid) {
0541             calculateKeyTimesForCalcModePaced();
0542         }
0543     } else if (animationMode == PathAnimation) {
0544         m_animationValid = calcMode() == CalcModePaced || !hasAttribute(SVGNames::keyPointsAttr) || (m_keyTimes.size() > 1 && m_keyTimes.size() == m_keyPoints.size());
0545     }
0546 }
0547 
0548 void SVGAnimationElement::updateAnimation(float percent, unsigned repeat, SVGSMILElement *resultElement)
0549 {
0550     if (!m_animationValid) {
0551         return;
0552     }
0553 
0554     float effectivePercent;
0555     if (animationMode() == ValuesAnimation) {
0556         String from;
0557         String to;
0558         currentValuesForValuesAnimation(percent, effectivePercent, from, to);
0559         if (from != m_lastValuesAnimationFrom || to != m_lastValuesAnimationTo) {
0560             m_animationValid = calculateFromAndToValues(from, to);
0561             if (!m_animationValid) {
0562                 return;
0563             }
0564             m_lastValuesAnimationFrom = from;
0565             m_lastValuesAnimationTo = to;
0566         }
0567     } else if (!m_keyPoints.isEmpty() && calcMode() != CalcModePaced) {
0568         effectivePercent = calculatePercentFromKeyPoints(percent);
0569     } else {
0570         effectivePercent = percent;
0571     }
0572 
0573     calculateAnimatedValue(effectivePercent, repeat, resultElement);
0574 }
0575 
0576 void SVGAnimationElement::endedActiveInterval()
0577 {
0578 }
0579 
0580 }
0581 
0582 #endif // ENABLE(SVG_ANIMATION)
0583