1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef DOM_SMIL_SMILANIMATIONFUNCTION_H_
8 #define DOM_SMIL_SMILANIMATIONFUNCTION_H_
9 
10 #include "mozilla/SMILAttr.h"
11 #include "mozilla/SMILKeySpline.h"
12 #include "mozilla/SMILTargetIdentifier.h"
13 #include "mozilla/SMILTimeValue.h"
14 #include "mozilla/SMILTypes.h"
15 #include "mozilla/SMILValue.h"
16 #include "nsAttrValue.h"
17 #include "nsGkAtoms.h"
18 #include "nsString.h"
19 #include "nsTArray.h"
20 
21 namespace mozilla {
22 namespace dom {
23 class SVGAnimationElement;
24 }  // namespace dom
25 
26 //----------------------------------------------------------------------
27 // SMILAnimationFunction
28 //
29 // The animation function calculates animation values. It it is provided with
30 // time parameters (sample time, repeat iteration etc.) and it uses this to
31 // build an appropriate animation value by performing interpolation and
32 // addition operations.
33 //
34 // It is responsible for implementing the animation parameters of an animation
35 // element (e.g. from, by, to, values, calcMode, additive, accumulate, keyTimes,
36 // keySplines)
37 //
38 class SMILAnimationFunction {
39  public:
40   SMILAnimationFunction();
41 
42   /*
43    * Sets the owning animation element which this class uses to query attribute
44    * values and compare document positions.
45    */
46   void SetAnimationElement(
47       mozilla::dom::SVGAnimationElement* aAnimationElement);
48 
49   /*
50    * Sets animation-specific attributes (or marks them dirty, in the case
51    * of from/to/by/values).
52    *
53    * @param aAttribute The attribute being set
54    * @param aValue     The updated value of the attribute.
55    * @param aResult    The nsAttrValue object that may be used for storing the
56    *                   parsed result.
57    * @param aParseResult  Outparam used for reporting parse errors. Will be set
58    *                      to NS_OK if everything succeeds.
59    * @return  true if aAttribute is a recognized animation-related
60    *          attribute; false otherwise.
61    */
62   virtual bool SetAttr(nsAtom* aAttribute, const nsAString& aValue,
63                        nsAttrValue& aResult, nsresult* aParseResult = nullptr);
64 
65   /*
66    * Unsets the given attribute.
67    *
68    * @returns true if aAttribute is a recognized animation-related
69    *          attribute; false otherwise.
70    */
71   virtual bool UnsetAttr(nsAtom* aAttribute);
72 
73   /**
74    * Indicate a new sample has occurred.
75    *
76    * @param aSampleTime The sample time for this timed element expressed in
77    *                    simple time.
78    * @param aSimpleDuration The simple duration for this timed element.
79    * @param aRepeatIteration  The repeat iteration for this sample. The first
80    *                          iteration has a value of 0.
81    */
82   void SampleAt(SMILTime aSampleTime, const SMILTimeValue& aSimpleDuration,
83                 uint32_t aRepeatIteration);
84 
85   /**
86    * Indicate to sample using the last value defined for the animation function.
87    * This value is not normally sampled due to the end-point exclusive timing
88    * model but only occurs when the fill mode is "freeze" and the active
89    * duration is an even multiple of the simple duration.
90    *
91    * @param aRepeatIteration  The repeat iteration for this sample. The first
92    *                          iteration has a value of 0.
93    */
94   void SampleLastValue(uint32_t aRepeatIteration);
95 
96   /**
97    * Indicate that this animation is now active. This is used to instruct the
98    * animation function that it should now add its result to the animation
99    * sandwich. The begin time is also provided for proper prioritization of
100    * animation functions, and for this reason, this method must be called
101    * before either of the Sample methods.
102    *
103    * @param aBeginTime The begin time for the newly active interval.
104    */
105   void Activate(SMILTime aBeginTime);
106 
107   /**
108    * Indicate that this animation is no longer active. This is used to instruct
109    * the animation function that it should no longer add its result to the
110    * animation sandwich.
111    *
112    * @param aIsFrozen true if this animation should continue to contribute
113    *                  to the animation sandwich using the most recent sample
114    *                  parameters.
115    */
116   void Inactivate(bool aIsFrozen);
117 
118   /**
119    * Combines the result of this animation function for the last sample with the
120    * specified value.
121    *
122    * @param aSMILAttr This animation's target attribute. Used here for
123    *                  doing attribute-specific parsing of from/to/by/values.
124    *
125    * @param aResult   The value to compose with.
126    */
127   void ComposeResult(const SMILAttr& aSMILAttr, SMILValue& aResult);
128 
129   /**
130    * Returns the relative priority of this animation to another. The priority is
131    * used for determining the position of the animation in the animation
132    * sandwich -- higher priority animations are applied on top of lower
133    * priority animations.
134    *
135    * @return  -1 if this animation has lower priority or 1 if this animation has
136    *          higher priority
137    *
138    * This method should never return any other value, including 0.
139    */
140   int8_t CompareTo(const SMILAnimationFunction* aOther) const;
141 
142   /*
143    * The following methods are provided so that the compositor can optimize its
144    * operations by only composing those animation that will affect the final
145    * result.
146    */
147 
148   /**
149    * Indicates if the animation is currently active or frozen. Inactive
150    * animations will not contribute to the composed result.
151    *
152    * @return  true if the animation is active or frozen, false otherwise.
153    */
IsActiveOrFrozen()154   bool IsActiveOrFrozen() const {
155     /*
156      * - Frozen animations should be considered active for the purposes of
157      * compositing.
158      * - This function does not assume that our SMILValues (by/from/to/values)
159      * have already been parsed.
160      */
161     return (mIsActive || mIsFrozen);
162   }
163 
164   /**
165    * Indicates if the animation is active.
166    *
167    * @return  true if the animation is active, false otherwise.
168    */
IsActive()169   bool IsActive() const { return mIsActive; }
170 
171   /**
172    * Indicates if this animation will replace the passed in result rather than
173    * adding to it. Animations that replace the underlying value may be called
174    * without first calling lower priority animations.
175    *
176    * @return  True if the animation will replace, false if it will add or
177    *          otherwise build on the passed in value.
178    */
179   virtual bool WillReplace() const;
180 
181   /**
182    * Indicates if the parameters for this animation have changed since the last
183    * time it was composited. This allows rendering to be performed only when
184    * necessary, particularly when no animations are active.
185    *
186    * Note that the caller is responsible for determining if the animation
187    * target has changed (with help from my UpdateCachedTarget() method).
188    *
189    * @return  true if the animation parameters have changed, false
190    *          otherwise.
191    */
192   bool HasChanged() const;
193 
194   /**
195    * This method lets us clear the 'HasChanged' flag for inactive animations
196    * after we've reacted to their change to the 'inactive' state, so that we
197    * won't needlessly recompose their targets in every sample.
198    *
199    * This should only be called on an animation function that is inactive and
200    * that returns true from HasChanged().
201    */
ClearHasChanged()202   void ClearHasChanged() {
203     MOZ_ASSERT(HasChanged(),
204                "clearing mHasChanged flag, when it's already false");
205     MOZ_ASSERT(!IsActiveOrFrozen(),
206                "clearing mHasChanged flag for active animation");
207     mHasChanged = false;
208   }
209 
210   /**
211    * Updates the cached record of our animation target, and returns a boolean
212    * that indicates whether the target has changed since the last call to this
213    * function. (This lets SMILCompositor check whether its animation
214    * functions have changed value or target since the last sample.  If none of
215    * them have, then the compositor doesn't need to do anything.)
216    *
217    * @param aNewTarget A SMILTargetIdentifier representing the animation
218    *                   target of this function for this sample.
219    * @return  true if |aNewTarget| is different from the old cached value;
220    *          otherwise, false.
221    */
222   bool UpdateCachedTarget(const SMILTargetIdentifier& aNewTarget);
223 
224   /**
225    * Returns true if this function was skipped in the previous sample (because
226    * there was a higher-priority non-additive animation). If a skipped animation
227    * function is later used, then the animation sandwich must be recomposited.
228    */
WasSkippedInPrevSample()229   bool WasSkippedInPrevSample() const { return mWasSkippedInPrevSample; }
230 
231   /**
232    * Mark this animation function as having been skipped. By marking the
233    * function as skipped, if it is used in a subsequent sample we'll know to
234    * recomposite the sandwich.
235    */
SetWasSkipped()236   void SetWasSkipped() { mWasSkippedInPrevSample = true; }
237 
238   /**
239    * Returns true if we need to recalculate the animation value on every sample.
240    * (e.g. because it depends on context like the font-size)
241    */
ValueNeedsReparsingEverySample()242   bool ValueNeedsReparsingEverySample() const {
243     return mValueNeedsReparsingEverySample;
244   }
245 
246   // Comparator utility class, used for sorting SMILAnimationFunctions
247   class Comparator {
248    public:
Equals(const SMILAnimationFunction * aElem1,const SMILAnimationFunction * aElem2)249     bool Equals(const SMILAnimationFunction* aElem1,
250                 const SMILAnimationFunction* aElem2) const {
251       return (aElem1->CompareTo(aElem2) == 0);
252     }
LessThan(const SMILAnimationFunction * aElem1,const SMILAnimationFunction * aElem2)253     bool LessThan(const SMILAnimationFunction* aElem1,
254                   const SMILAnimationFunction* aElem2) const {
255       return (aElem1->CompareTo(aElem2) < 0);
256     }
257   };
258 
259  protected:
260   // alias declarations
261   using SMILValueArray = FallibleTArray<SMILValue>;
262 
263   // Types
264   enum SMILCalcMode : uint8_t {
265     CALC_LINEAR,
266     CALC_DISCRETE,
267     CALC_PACED,
268     CALC_SPLINE
269   };
270 
271   // Used for sorting SMILAnimationFunctions
GetBeginTime()272   SMILTime GetBeginTime() const { return mBeginTime; }
273 
274   // Property getters
275   bool GetAccumulate() const;
276   bool GetAdditive() const;
277   virtual SMILCalcMode GetCalcMode() const;
278 
279   // Property setters
280   nsresult SetAccumulate(const nsAString& aAccumulate, nsAttrValue& aResult);
281   nsresult SetAdditive(const nsAString& aAdditive, nsAttrValue& aResult);
282   nsresult SetCalcMode(const nsAString& aCalcMode, nsAttrValue& aResult);
283   nsresult SetKeyTimes(const nsAString& aKeyTimes, nsAttrValue& aResult);
284   nsresult SetKeySplines(const nsAString& aKeySplines, nsAttrValue& aResult);
285 
286   // Property un-setters
287   void UnsetAccumulate();
288   void UnsetAdditive();
289   void UnsetCalcMode();
290   void UnsetKeyTimes();
291   void UnsetKeySplines();
292 
293   // Helpers
294   virtual nsresult InterpolateResult(const SMILValueArray& aValues,
295                                      SMILValue& aResult, SMILValue& aBaseValue);
296   nsresult AccumulateResult(const SMILValueArray& aValues, SMILValue& aResult);
297 
298   nsresult ComputePacedPosition(const SMILValueArray& aValues,
299                                 double aSimpleProgress,
300                                 double& aIntervalProgress,
301                                 const SMILValue*& aFrom, const SMILValue*& aTo);
302   double ComputePacedTotalDistance(const SMILValueArray& aValues) const;
303 
304   /**
305    * Adjust the simple progress, that is, the point within the simple duration,
306    * by applying any keyTimes.
307    */
308   double ScaleSimpleProgress(double aProgress, SMILCalcMode aCalcMode);
309   /**
310    * Adjust the progress within an interval, that is, between two animation
311    * values, by applying any keySplines.
312    */
313   double ScaleIntervalProgress(double aProgress, uint32_t aIntervalIndex);
314 
315   // Convenience attribute getters -- use these instead of querying
316   // mAnimationElement as these may need to be overridden by subclasses
317   virtual bool HasAttr(nsAtom* aAttName) const;
318   virtual const nsAttrValue* GetAttr(nsAtom* aAttName) const;
319   virtual bool GetAttr(nsAtom* aAttName, nsAString& aResult) const;
320 
321   bool ParseAttr(nsAtom* aAttName, const SMILAttr& aSMILAttr,
322                  SMILValue& aResult, bool& aPreventCachingOfSandwich) const;
323 
324   virtual nsresult GetValues(const SMILAttr& aSMILAttr,
325                              SMILValueArray& aResult);
326 
327   virtual void CheckValueListDependentAttrs(uint32_t aNumValues);
328   void CheckKeyTimes(uint32_t aNumValues);
329   void CheckKeySplines(uint32_t aNumValues);
330 
IsToAnimation()331   virtual bool IsToAnimation() const {
332     return !HasAttr(nsGkAtoms::values) && HasAttr(nsGkAtoms::to) &&
333            !HasAttr(nsGkAtoms::from);
334   }
335 
336   // Returns true if we know our composited value won't change over the
337   // simple duration of this animation (for a fixed base value).
338   virtual bool IsValueFixedForSimpleDuration() const;
339 
IsAdditive()340   inline bool IsAdditive() const {
341     /*
342      * Animation is additive if:
343      *
344      * (1) additive = "sum" (GetAdditive() == true), or
345      * (2) it is 'by animation' (by is set, from and values are not)
346      *
347      * Although animation is not additive if it is 'to animation'
348      */
349     bool isByAnimation = (!HasAttr(nsGkAtoms::values) &&
350                           HasAttr(nsGkAtoms::by) && !HasAttr(nsGkAtoms::from));
351     return !IsToAnimation() && (GetAdditive() || isByAnimation);
352   }
353 
354   // Setters for error flags
355   // These correspond to bit-indices in mErrorFlags, for tracking parse errors
356   // in these attributes, when those parse errors should block us from doing
357   // animation.
358   enum AnimationAttributeIdx {
359     BF_ACCUMULATE = 0,
360     BF_ADDITIVE = 1,
361     BF_CALC_MODE = 2,
362     BF_KEY_TIMES = 3,
363     BF_KEY_SPLINES = 4,
364     BF_KEY_POINTS = 5  // <animateMotion> only
365   };
366 
SetAccumulateErrorFlag(bool aNewValue)367   inline void SetAccumulateErrorFlag(bool aNewValue) {
368     SetErrorFlag(BF_ACCUMULATE, aNewValue);
369   }
SetAdditiveErrorFlag(bool aNewValue)370   inline void SetAdditiveErrorFlag(bool aNewValue) {
371     SetErrorFlag(BF_ADDITIVE, aNewValue);
372   }
SetCalcModeErrorFlag(bool aNewValue)373   inline void SetCalcModeErrorFlag(bool aNewValue) {
374     SetErrorFlag(BF_CALC_MODE, aNewValue);
375   }
SetKeyTimesErrorFlag(bool aNewValue)376   inline void SetKeyTimesErrorFlag(bool aNewValue) {
377     SetErrorFlag(BF_KEY_TIMES, aNewValue);
378   }
SetKeySplinesErrorFlag(bool aNewValue)379   inline void SetKeySplinesErrorFlag(bool aNewValue) {
380     SetErrorFlag(BF_KEY_SPLINES, aNewValue);
381   }
SetKeyPointsErrorFlag(bool aNewValue)382   inline void SetKeyPointsErrorFlag(bool aNewValue) {
383     SetErrorFlag(BF_KEY_POINTS, aNewValue);
384   }
SetErrorFlag(AnimationAttributeIdx aField,bool aValue)385   inline void SetErrorFlag(AnimationAttributeIdx aField, bool aValue) {
386     if (aValue) {
387       mErrorFlags |= (0x01 << aField);
388     } else {
389       mErrorFlags &= ~(0x01 << aField);
390     }
391   }
392 
393   // Members
394   // -------
395 
396   static nsAttrValue::EnumTable sAdditiveTable[];
397   static nsAttrValue::EnumTable sCalcModeTable[];
398   static nsAttrValue::EnumTable sAccumulateTable[];
399 
400   FallibleTArray<double> mKeyTimes;
401   FallibleTArray<SMILKeySpline> mKeySplines;
402 
403   // These are the parameters provided by the previous sample. Currently we
404   // perform lazy calculation. That is, we only calculate the result if and when
405   // instructed by the compositor. This allows us to apply the result directly
406   // to the animation value and allows the compositor to filter out functions
407   // that it determines will not contribute to the final result.
408   SMILTime mSampleTime;  // sample time within simple dur
409   SMILTimeValue mSimpleDuration;
410   uint32_t mRepeatIteration;
411 
412   SMILTime mBeginTime;  // document time
413 
414   // The owning animation element. This is used for sorting based on document
415   // position and for fetching attribute values stored in the element.
416   // Raw pointer is OK here, because this SMILAnimationFunction can't outlive
417   // its owning animation element.
418   mozilla::dom::SVGAnimationElement* mAnimationElement;
419 
420   // Which attributes have been set but have had errors. This is not used for
421   // all attributes but only those which have specified error behaviour
422   // associated with them.
423   uint16_t mErrorFlags;
424 
425   // Allows us to check whether an animation function has changed target from
426   // sample to sample (because if neither target nor animated value have
427   // changed, we don't have to do anything).
428   SMILWeakTargetIdentifier mLastTarget;
429 
430   // Boolean flags
431   bool mIsActive : 1;
432   bool mIsFrozen : 1;
433   bool mLastValue : 1;
434   bool mHasChanged : 1;
435   bool mValueNeedsReparsingEverySample : 1;
436   bool mPrevSampleWasSingleValueAnimation : 1;
437   bool mWasSkippedInPrevSample : 1;
438 };
439 
440 }  // namespace mozilla
441 
442 #endif  // DOM_SMIL_SMILANIMATIONFUNCTION_H_
443