1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is the Mozilla SMIL module.
16 : *
17 : * The Initial Developer of the Original Code is Brian Birtles.
18 : * Portions created by the Initial Developer are Copyright (C) 2005
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Brian Birtles <birtles@gmail.com>
23 : * Chris Double <chris.double@double.co.nz>
24 : * Daniel Holbert <dholbert@cs.stanford.edu>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsSMILAnimationFunction.h"
41 : #include "nsISMILAttr.h"
42 : #include "nsSMILParserUtils.h"
43 : #include "nsSMILNullType.h"
44 : #include "nsISMILAnimationElement.h"
45 : #include "nsSMILTimedElement.h"
46 : #include "nsGkAtoms.h"
47 : #include "nsCOMPtr.h"
48 : #include "nsCOMArray.h"
49 : #include "nsIContent.h"
50 : #include "nsAutoPtr.h"
51 : #include "nsContentUtils.h"
52 : #include "nsReadableUtils.h"
53 : #include "nsString.h"
54 : #include <math.h>
55 :
56 : //----------------------------------------------------------------------
57 : // Static members
58 :
59 : nsAttrValue::EnumTable nsSMILAnimationFunction::sAccumulateTable[] = {
60 : {"none", false},
61 : {"sum", true},
62 : {nsnull, 0}
63 : };
64 :
65 : nsAttrValue::EnumTable nsSMILAnimationFunction::sAdditiveTable[] = {
66 : {"replace", false},
67 : {"sum", true},
68 : {nsnull, 0}
69 : };
70 :
71 : nsAttrValue::EnumTable nsSMILAnimationFunction::sCalcModeTable[] = {
72 : {"linear", CALC_LINEAR},
73 : {"discrete", CALC_DISCRETE},
74 : {"paced", CALC_PACED},
75 : {"spline", CALC_SPLINE},
76 : {nsnull, 0}
77 : };
78 :
79 : // Any negative number should be fine as a sentinel here,
80 : // because valid distances are non-negative.
81 : #define COMPUTE_DISTANCE_ERROR (-1)
82 :
83 : //----------------------------------------------------------------------
84 : // Constructors etc.
85 :
86 0 : nsSMILAnimationFunction::nsSMILAnimationFunction()
87 : : mSampleTime(-1),
88 : mRepeatIteration(0),
89 : mBeginTime(LL_MININT),
90 : mAnimationElement(nsnull),
91 : mErrorFlags(0),
92 : mIsActive(false),
93 : mIsFrozen(false),
94 : mLastValue(false),
95 : mHasChanged(true),
96 : mValueNeedsReparsingEverySample(false),
97 : mPrevSampleWasSingleValueAnimation(false),
98 0 : mWasSkippedInPrevSample(false)
99 : {
100 0 : }
101 :
102 : void
103 0 : nsSMILAnimationFunction::SetAnimationElement(
104 : nsISMILAnimationElement* aAnimationElement)
105 : {
106 0 : mAnimationElement = aAnimationElement;
107 0 : }
108 :
109 : bool
110 0 : nsSMILAnimationFunction::SetAttr(nsIAtom* aAttribute, const nsAString& aValue,
111 : nsAttrValue& aResult, nsresult* aParseResult)
112 : {
113 0 : bool foundMatch = true;
114 0 : nsresult parseResult = NS_OK;
115 :
116 : // The attributes 'by', 'from', 'to', and 'values' may be parsed differently
117 : // depending on the element & attribute we're animating. So instead of
118 : // parsing them now we re-parse them at every sample.
119 0 : if (aAttribute == nsGkAtoms::by ||
120 : aAttribute == nsGkAtoms::from ||
121 : aAttribute == nsGkAtoms::to ||
122 : aAttribute == nsGkAtoms::values) {
123 : // We parse to, from, by, values at sample time.
124 : // XXX Need to flag which attribute has changed and then when we parse it at
125 : // sample time, report any errors and reset the flag
126 0 : mHasChanged = true;
127 0 : aResult.SetTo(aValue);
128 0 : } else if (aAttribute == nsGkAtoms::accumulate) {
129 0 : parseResult = SetAccumulate(aValue, aResult);
130 0 : } else if (aAttribute == nsGkAtoms::additive) {
131 0 : parseResult = SetAdditive(aValue, aResult);
132 0 : } else if (aAttribute == nsGkAtoms::calcMode) {
133 0 : parseResult = SetCalcMode(aValue, aResult);
134 0 : } else if (aAttribute == nsGkAtoms::keyTimes) {
135 0 : parseResult = SetKeyTimes(aValue, aResult);
136 0 : } else if (aAttribute == nsGkAtoms::keySplines) {
137 0 : parseResult = SetKeySplines(aValue, aResult);
138 : } else {
139 0 : foundMatch = false;
140 : }
141 :
142 0 : if (foundMatch && aParseResult) {
143 0 : *aParseResult = parseResult;
144 : }
145 :
146 0 : return foundMatch;
147 : }
148 :
149 : bool
150 0 : nsSMILAnimationFunction::UnsetAttr(nsIAtom* aAttribute)
151 : {
152 0 : bool foundMatch = true;
153 :
154 0 : if (aAttribute == nsGkAtoms::by ||
155 : aAttribute == nsGkAtoms::from ||
156 : aAttribute == nsGkAtoms::to ||
157 : aAttribute == nsGkAtoms::values) {
158 0 : mHasChanged = true;
159 0 : } else if (aAttribute == nsGkAtoms::accumulate) {
160 0 : UnsetAccumulate();
161 0 : } else if (aAttribute == nsGkAtoms::additive) {
162 0 : UnsetAdditive();
163 0 : } else if (aAttribute == nsGkAtoms::calcMode) {
164 0 : UnsetCalcMode();
165 0 : } else if (aAttribute == nsGkAtoms::keyTimes) {
166 0 : UnsetKeyTimes();
167 0 : } else if (aAttribute == nsGkAtoms::keySplines) {
168 0 : UnsetKeySplines();
169 : } else {
170 0 : foundMatch = false;
171 : }
172 :
173 0 : return foundMatch;
174 : }
175 :
176 : void
177 0 : nsSMILAnimationFunction::SampleAt(nsSMILTime aSampleTime,
178 : const nsSMILTimeValue& aSimpleDuration,
179 : PRUint32 aRepeatIteration)
180 : {
181 : // * Update mHasChanged ("Might this sample be different from prev one?")
182 : // Were we previously sampling a fill="freeze" final val? (We're not anymore.)
183 0 : mHasChanged |= mLastValue;
184 :
185 : // Are we sampling at a new point in simple duration? And does that matter?
186 : mHasChanged |=
187 0 : (mSampleTime != aSampleTime || mSimpleDuration != aSimpleDuration) &&
188 0 : !IsValueFixedForSimpleDuration();
189 :
190 : // Are we on a new repeat and accumulating across repeats?
191 0 : if (!mErrorFlags) { // (can't call GetAccumulate() if we've had parse errors)
192 0 : mHasChanged |= (mRepeatIteration != aRepeatIteration) && GetAccumulate();
193 : }
194 :
195 0 : mSampleTime = aSampleTime;
196 0 : mSimpleDuration = aSimpleDuration;
197 0 : mRepeatIteration = aRepeatIteration;
198 0 : mLastValue = false;
199 0 : }
200 :
201 : void
202 0 : nsSMILAnimationFunction::SampleLastValue(PRUint32 aRepeatIteration)
203 : {
204 0 : if (mHasChanged || !mLastValue || mRepeatIteration != aRepeatIteration) {
205 0 : mHasChanged = true;
206 : }
207 :
208 0 : mRepeatIteration = aRepeatIteration;
209 0 : mLastValue = true;
210 0 : }
211 :
212 : void
213 0 : nsSMILAnimationFunction::Activate(nsSMILTime aBeginTime)
214 : {
215 0 : mBeginTime = aBeginTime;
216 0 : mIsActive = true;
217 0 : mIsFrozen = false;
218 0 : mFrozenValue = nsSMILValue();
219 0 : mHasChanged = true;
220 0 : }
221 :
222 : void
223 0 : nsSMILAnimationFunction::Inactivate(bool aIsFrozen)
224 : {
225 0 : mIsActive = false;
226 0 : mIsFrozen = aIsFrozen;
227 0 : mFrozenValue = nsSMILValue();
228 0 : mHasChanged = true;
229 0 : }
230 :
231 : void
232 0 : nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr,
233 : nsSMILValue& aResult)
234 : {
235 0 : mHasChanged = false;
236 0 : mPrevSampleWasSingleValueAnimation = false;
237 0 : mWasSkippedInPrevSample = false;
238 :
239 : // Skip animations that are inactive or in error
240 0 : if (!IsActiveOrFrozen() || mErrorFlags != 0)
241 0 : return;
242 :
243 : // Get the animation values
244 0 : nsSMILValueArray values;
245 0 : nsresult rv = GetValues(aSMILAttr, values);
246 0 : if (NS_FAILED(rv))
247 : return;
248 :
249 : // Check that we have the right number of keySplines and keyTimes
250 0 : CheckValueListDependentAttrs(values.Length());
251 0 : if (mErrorFlags != 0)
252 : return;
253 :
254 : // If this interval is active, we must have a non-negative mSampleTime
255 0 : NS_ABORT_IF_FALSE(mSampleTime >= 0 || !mIsActive,
256 : "Negative sample time for active animation");
257 0 : NS_ABORT_IF_FALSE(mSimpleDuration.IsResolved() || mLastValue,
258 : "Unresolved simple duration for active or frozen animation");
259 :
260 : // If we want to add but don't have a base value then just fail outright.
261 : // This can happen when we skipped getting the base value because there's an
262 : // animation function in the sandwich that should replace it but that function
263 : // failed unexpectedly.
264 0 : bool isAdditive = IsAdditive();
265 0 : if (isAdditive && aResult.IsNull())
266 : return;
267 :
268 0 : nsSMILValue result;
269 :
270 0 : if (values.Length() == 1 && !IsToAnimation()) {
271 :
272 : // Single-valued animation
273 0 : result = values[0];
274 0 : mPrevSampleWasSingleValueAnimation = true;
275 :
276 0 : } else if (mLastValue) {
277 :
278 : // Sampling last value
279 0 : const nsSMILValue& last = values[values.Length() - 1];
280 0 : result = last;
281 :
282 : // See comment in AccumulateResult: to-animation does not accumulate
283 0 : if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) {
284 : // If the target attribute type doesn't support addition Add will
285 : // fail leaving result = last
286 0 : result.Add(last, mRepeatIteration);
287 : }
288 :
289 0 : } else if (!mFrozenValue.IsNull() && !mHasChanged) {
290 :
291 : // Frozen to animation
292 0 : result = mFrozenValue;
293 :
294 : } else {
295 :
296 : // Interpolation
297 0 : if (NS_FAILED(InterpolateResult(values, result, aResult)))
298 : return;
299 :
300 0 : if (NS_FAILED(AccumulateResult(values, result)))
301 : return;
302 :
303 0 : if (IsToAnimation() && mIsFrozen) {
304 0 : mFrozenValue = result;
305 : }
306 : }
307 :
308 : // If additive animation isn't required or isn't supported, set the value.
309 0 : if (!isAdditive || NS_FAILED(aResult.SandwichAdd(result))) {
310 0 : aResult.Swap(result);
311 : // Note: The old value of aResult is now in |result|, and it will get
312 : // cleaned up when |result| goes out of scope, when this function returns.
313 : }
314 : }
315 :
316 : PRInt8
317 0 : nsSMILAnimationFunction::CompareTo(const nsSMILAnimationFunction* aOther) const
318 : {
319 0 : NS_ENSURE_TRUE(aOther, 0);
320 :
321 0 : NS_ASSERTION(aOther != this, "Trying to compare to self");
322 :
323 : // Inactive animations sort first
324 0 : if (!IsActiveOrFrozen() && aOther->IsActiveOrFrozen())
325 0 : return -1;
326 :
327 0 : if (IsActiveOrFrozen() && !aOther->IsActiveOrFrozen())
328 0 : return 1;
329 :
330 : // Sort based on begin time
331 0 : if (mBeginTime != aOther->GetBeginTime())
332 0 : return mBeginTime > aOther->GetBeginTime() ? 1 : -1;
333 :
334 : // Next sort based on syncbase dependencies: the dependent element sorts after
335 : // its syncbase
336 : const nsSMILTimedElement& thisTimedElement =
337 0 : mAnimationElement->TimedElement();
338 : const nsSMILTimedElement& otherTimedElement =
339 0 : aOther->mAnimationElement->TimedElement();
340 0 : if (thisTimedElement.IsTimeDependent(otherTimedElement))
341 0 : return 1;
342 0 : if (otherTimedElement.IsTimeDependent(thisTimedElement))
343 0 : return -1;
344 :
345 : // Animations that appear later in the document sort after those earlier in
346 : // the document
347 0 : nsIContent& thisContent = mAnimationElement->AsElement();
348 0 : nsIContent& otherContent = aOther->mAnimationElement->AsElement();
349 :
350 0 : NS_ABORT_IF_FALSE(&thisContent != &otherContent,
351 : "Two animations cannot have the same animation content element!");
352 :
353 0 : return (nsContentUtils::PositionIsBefore(&thisContent, &otherContent))
354 0 : ? -1 : 1;
355 : }
356 :
357 : bool
358 0 : nsSMILAnimationFunction::WillReplace() const
359 : {
360 : /*
361 : * In IsAdditive() we don't consider to-animation to be additive as it is
362 : * a special case that is dealt with differently in the compositing method but
363 : * here we return false for to animation as it builds on the underlying value
364 : * unless its a frozen to animation.
365 : */
366 0 : return !mErrorFlags && (!(IsAdditive() || IsToAnimation()) ||
367 0 : (IsToAnimation() && mIsFrozen && !mHasChanged));
368 : }
369 :
370 : bool
371 0 : nsSMILAnimationFunction::HasChanged() const
372 : {
373 0 : return mHasChanged || mValueNeedsReparsingEverySample;
374 : }
375 :
376 : bool
377 0 : nsSMILAnimationFunction::UpdateCachedTarget(const nsSMILTargetIdentifier& aNewTarget)
378 : {
379 0 : if (!mLastTarget.Equals(aNewTarget)) {
380 0 : mLastTarget = aNewTarget;
381 0 : return true;
382 : }
383 0 : return false;
384 : }
385 :
386 : //----------------------------------------------------------------------
387 : // Implementation helpers
388 :
389 : nsresult
390 0 : nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues,
391 : nsSMILValue& aResult,
392 : nsSMILValue& aBaseValue)
393 : {
394 : // Sanity check animation values
395 0 : if ((!IsToAnimation() && aValues.Length() < 2) ||
396 0 : (IsToAnimation() && aValues.Length() != 1)) {
397 0 : NS_ERROR("Unexpected number of values");
398 0 : return NS_ERROR_FAILURE;
399 : }
400 :
401 0 : if (IsToAnimation() && aBaseValue.IsNull()) {
402 0 : return NS_ERROR_FAILURE;
403 : }
404 :
405 : // Get the normalised progress through the simple duration.
406 : //
407 : // If we have an indefinite simple duration, just set the progress to be
408 : // 0 which will give us the expected behaviour of the animation being fixed at
409 : // its starting point.
410 0 : double simpleProgress = 0.0;
411 :
412 0 : if (mSimpleDuration.IsDefinite()) {
413 0 : nsSMILTime dur = mSimpleDuration.GetMillis();
414 :
415 0 : NS_ABORT_IF_FALSE(dur >= 0, "Simple duration should not be negative");
416 0 : NS_ABORT_IF_FALSE(mSampleTime >= 0, "Sample time should not be negative");
417 :
418 0 : if (mSampleTime >= dur || mSampleTime < 0) {
419 0 : NS_ERROR("Animation sampled outside interval");
420 0 : return NS_ERROR_FAILURE;
421 : }
422 :
423 0 : if (dur > 0) {
424 0 : simpleProgress = (double)mSampleTime / dur;
425 : } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0)
426 : }
427 :
428 0 : nsresult rv = NS_OK;
429 0 : nsSMILCalcMode calcMode = GetCalcMode();
430 0 : if (calcMode != CALC_DISCRETE) {
431 : // Get the normalised progress between adjacent values
432 0 : const nsSMILValue* from = nsnull;
433 0 : const nsSMILValue* to = nsnull;
434 : // Init to -1 to make sure that if we ever forget to set this, the
435 : // NS_ABORT_IF_FALSE that tests that intervalProgress is in range will fail.
436 0 : double intervalProgress = -1.f;
437 0 : if (IsToAnimation()) {
438 0 : from = &aBaseValue;
439 0 : to = &aValues[0];
440 0 : if (calcMode == CALC_PACED) {
441 : // Note: key[Times/Splines/Points] are ignored for calcMode="paced"
442 0 : intervalProgress = simpleProgress;
443 : } else {
444 : double scaledSimpleProgress =
445 0 : ScaleSimpleProgress(simpleProgress, calcMode);
446 0 : intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0);
447 : }
448 0 : } else if (calcMode == CALC_PACED) {
449 : rv = ComputePacedPosition(aValues, simpleProgress,
450 0 : intervalProgress, from, to);
451 : // Note: If the above call fails, we'll skip the "from->Interpolate"
452 : // call below, and we'll drop into the CALC_DISCRETE section
453 : // instead. (as the spec says we should, because our failure was
454 : // presumably due to the values being non-additive)
455 : } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE
456 : double scaledSimpleProgress =
457 0 : ScaleSimpleProgress(simpleProgress, calcMode);
458 : PRUint32 index = (PRUint32)floor(scaledSimpleProgress *
459 0 : (aValues.Length() - 1));
460 0 : from = &aValues[index];
461 0 : to = &aValues[index + 1];
462 : intervalProgress =
463 0 : scaledSimpleProgress * (aValues.Length() - 1) - index;
464 0 : intervalProgress = ScaleIntervalProgress(intervalProgress, index);
465 : }
466 :
467 0 : if (NS_SUCCEEDED(rv)) {
468 0 : NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation");
469 0 : NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation");
470 0 : NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f,
471 : "Interval progress should be in the range [0, 1)");
472 0 : rv = from->Interpolate(*to, intervalProgress, aResult);
473 : }
474 : }
475 :
476 : // Discrete-CalcMode case
477 : // Note: If interpolation failed (isn't supported for this type), the SVG
478 : // spec says to force discrete mode.
479 0 : if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) {
480 : double scaledSimpleProgress =
481 0 : ScaleSimpleProgress(simpleProgress, CALC_DISCRETE);
482 :
483 : // Floating-point errors can mean that, for example, a sample time of 29s in
484 : // a 100s duration animation gives us a simple progress of 0.28999999999
485 : // instead of the 0.29 we'd expect. Normally this isn't a noticeable
486 : // problem, but when we have sudden jumps in animation values (such as is
487 : // the case here with discrete animation) we can get unexpected results.
488 : //
489 : // To counteract this, before we perform a floor() on the animation
490 : // progress, we add a tiny fudge factor to push us into the correct interval
491 : // in cases where floating-point errors might cause us to fall short.
492 : static const double kFloatingPointFudgeFactor = 1.0e-16;
493 0 : if (scaledSimpleProgress + kFloatingPointFudgeFactor <= 1.0) {
494 0 : scaledSimpleProgress += kFloatingPointFudgeFactor;
495 : }
496 :
497 0 : if (IsToAnimation()) {
498 : // We don't follow SMIL 3, 12.6.4, where discrete to animations
499 : // are the same as <set> animations. Instead, we treat it as a
500 : // discrete animation with two values (the underlying value and
501 : // the to="" value), and honor keyTimes="" as well.
502 0 : PRUint32 index = (PRUint32)floor(scaledSimpleProgress * 2);
503 0 : aResult = index == 0 ? aBaseValue : aValues[0];
504 : } else {
505 0 : PRUint32 index = (PRUint32)floor(scaledSimpleProgress * aValues.Length());
506 0 : aResult = aValues[index];
507 : }
508 0 : rv = NS_OK;
509 : }
510 0 : return rv;
511 : }
512 :
513 : nsresult
514 0 : nsSMILAnimationFunction::AccumulateResult(const nsSMILValueArray& aValues,
515 : nsSMILValue& aResult)
516 : {
517 0 : if (!IsToAnimation() && GetAccumulate() && mRepeatIteration)
518 : {
519 0 : const nsSMILValue& lastValue = aValues[aValues.Length() - 1];
520 :
521 : // If the target attribute type doesn't support addition, Add will
522 : // fail and we leave aResult untouched.
523 0 : aResult.Add(lastValue, mRepeatIteration);
524 : }
525 :
526 0 : return NS_OK;
527 : }
528 :
529 : /*
530 : * Given the simple progress for a paced animation, this method:
531 : * - determines which two elements of the values array we're in between
532 : * (returned as aFrom and aTo)
533 : * - determines where we are between them
534 : * (returned as aIntervalProgress)
535 : *
536 : * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance
537 : * computation.
538 : */
539 : nsresult
540 0 : nsSMILAnimationFunction::ComputePacedPosition(const nsSMILValueArray& aValues,
541 : double aSimpleProgress,
542 : double& aIntervalProgress,
543 : const nsSMILValue*& aFrom,
544 : const nsSMILValue*& aTo)
545 : {
546 0 : NS_ASSERTION(0.0f <= aSimpleProgress && aSimpleProgress < 1.0f,
547 : "aSimpleProgress is out of bounds");
548 0 : NS_ASSERTION(GetCalcMode() == CALC_PACED,
549 : "Calling paced-specific function, but not in paced mode");
550 0 : NS_ABORT_IF_FALSE(aValues.Length() >= 2, "Unexpected number of values");
551 :
552 : // Trivial case: If we have just 2 values, then there's only one interval
553 : // for us to traverse, and our progress across that interval is the exact
554 : // same as our overall progress.
555 0 : if (aValues.Length() == 2) {
556 0 : aIntervalProgress = aSimpleProgress;
557 0 : aFrom = &aValues[0];
558 0 : aTo = &aValues[1];
559 0 : return NS_OK;
560 : }
561 :
562 0 : double totalDistance = ComputePacedTotalDistance(aValues);
563 0 : if (totalDistance == COMPUTE_DISTANCE_ERROR)
564 0 : return NS_ERROR_FAILURE;
565 :
566 : // If we have 0 total distance, then it's unclear where our "paced" position
567 : // should be. We can just fail, which drops us into discrete animation mode.
568 : // (That's fine, since our values are apparently indistinguishable anyway.)
569 0 : if (totalDistance == 0.0) {
570 0 : return NS_ERROR_FAILURE;
571 : }
572 :
573 : // total distance we should have moved at this point in time.
574 : // (called 'remainingDist' due to how it's used in loop below)
575 0 : double remainingDist = aSimpleProgress * totalDistance;
576 :
577 : // Must be satisfied, because totalDistance is a sum of (non-negative)
578 : // distances, and aSimpleProgress is non-negative
579 0 : NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
580 :
581 : // Find where remainingDist puts us in the list of values
582 : // Note: We could optimize this next loop by caching the
583 : // interval-distances in an array, but maybe that's excessive.
584 0 : for (PRUint32 i = 0; i < aValues.Length() - 1; i++) {
585 : // Note: The following assertion is valid because remainingDist should
586 : // start out non-negative, and this loop never shaves off more than its
587 : // current value.
588 0 : NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
589 :
590 : double curIntervalDist;
591 :
592 : #ifdef DEBUG
593 : nsresult rv =
594 : #endif
595 0 : aValues[i].ComputeDistance(aValues[i+1], curIntervalDist);
596 0 : NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv),
597 : "If we got through ComputePacedTotalDistance, we should "
598 : "be able to recompute each sub-distance without errors");
599 :
600 0 : NS_ASSERTION(curIntervalDist >= 0, "distance values must be non-negative");
601 : // Clamp distance value at 0, just in case ComputeDistance is evil.
602 0 : curIntervalDist = NS_MAX(curIntervalDist, 0.0);
603 :
604 0 : if (remainingDist >= curIntervalDist) {
605 0 : remainingDist -= curIntervalDist;
606 : } else {
607 : // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why?
608 : // Because this clause is only hit when remainingDist < curIntervalDist,
609 : // and if curIntervalDist were 0, that would mean remainingDist would
610 : // have to be < 0. But that can't happen, because remainingDist (as
611 : // a distance) is non-negative by definition.
612 0 : NS_ASSERTION(curIntervalDist != 0,
613 : "We should never get here with this set to 0...");
614 :
615 : // We found the right spot -- an interpolated position between
616 : // values i and i+1.
617 0 : aFrom = &aValues[i];
618 0 : aTo = &aValues[i+1];
619 0 : aIntervalProgress = remainingDist / curIntervalDist;
620 0 : return NS_OK;
621 : }
622 : }
623 :
624 : NS_NOTREACHED("shouldn't complete loop & get here -- if we do, "
625 0 : "then aSimpleProgress was probably out of bounds");
626 0 : return NS_ERROR_FAILURE;
627 : }
628 :
629 : /*
630 : * Computes the total distance to be travelled by a paced animation.
631 : *
632 : * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if
633 : * our values don't support distance computation.
634 : */
635 : double
636 0 : nsSMILAnimationFunction::ComputePacedTotalDistance(
637 : const nsSMILValueArray& aValues) const
638 : {
639 0 : NS_ASSERTION(GetCalcMode() == CALC_PACED,
640 : "Calling paced-specific function, but not in paced mode");
641 :
642 0 : double totalDistance = 0.0;
643 0 : for (PRUint32 i = 0; i < aValues.Length() - 1; i++) {
644 : double tmpDist;
645 0 : nsresult rv = aValues[i].ComputeDistance(aValues[i+1], tmpDist);
646 0 : if (NS_FAILED(rv)) {
647 0 : return COMPUTE_DISTANCE_ERROR;
648 : }
649 :
650 : // Clamp distance value to 0, just in case we have an evil ComputeDistance
651 : // implementation somewhere
652 0 : NS_ABORT_IF_FALSE(tmpDist >= 0.0f, "distance values must be non-negative");
653 0 : tmpDist = NS_MAX(tmpDist, 0.0);
654 :
655 0 : totalDistance += tmpDist;
656 : }
657 :
658 0 : return totalDistance;
659 : }
660 :
661 : double
662 0 : nsSMILAnimationFunction::ScaleSimpleProgress(double aProgress,
663 : nsSMILCalcMode aCalcMode)
664 : {
665 0 : if (!HasAttr(nsGkAtoms::keyTimes))
666 0 : return aProgress;
667 :
668 0 : PRUint32 numTimes = mKeyTimes.Length();
669 :
670 0 : if (numTimes < 2)
671 0 : return aProgress;
672 :
673 0 : PRUint32 i = 0;
674 0 : for (; i < numTimes - 2 && aProgress >= mKeyTimes[i+1]; ++i);
675 :
676 0 : if (aCalcMode == CALC_DISCRETE) {
677 : // discrete calcMode behaviour differs in that each keyTime defines the time
678 : // from when the corresponding value is set, and therefore the last value
679 : // needn't be 1. So check if we're in the last 'interval', that is, the
680 : // space between the final value and 1.0.
681 0 : if (aProgress >= mKeyTimes[i+1]) {
682 0 : NS_ABORT_IF_FALSE(i == numTimes - 2,
683 : "aProgress is not in range of the current interval, yet the current"
684 : " interval is not the last bounded interval either.");
685 0 : ++i;
686 : }
687 0 : return (double)i / numTimes;
688 : }
689 :
690 0 : double& intervalStart = mKeyTimes[i];
691 0 : double& intervalEnd = mKeyTimes[i+1];
692 :
693 0 : double intervalLength = intervalEnd - intervalStart;
694 0 : if (intervalLength <= 0.0)
695 0 : return intervalStart;
696 :
697 : return (i + (aProgress - intervalStart) / intervalLength) /
698 0 : double(numTimes - 1);
699 : }
700 :
701 : double
702 0 : nsSMILAnimationFunction::ScaleIntervalProgress(double aProgress,
703 : PRUint32 aIntervalIndex)
704 : {
705 0 : if (GetCalcMode() != CALC_SPLINE)
706 0 : return aProgress;
707 :
708 0 : if (!HasAttr(nsGkAtoms::keySplines))
709 0 : return aProgress;
710 :
711 0 : NS_ABORT_IF_FALSE(aIntervalIndex < mKeySplines.Length(),
712 : "Invalid interval index");
713 :
714 0 : nsSMILKeySpline const &spline = mKeySplines[aIntervalIndex];
715 0 : return spline.GetSplineValue(aProgress);
716 : }
717 :
718 : bool
719 0 : nsSMILAnimationFunction::HasAttr(nsIAtom* aAttName) const
720 : {
721 0 : return mAnimationElement->HasAnimAttr(aAttName);
722 : }
723 :
724 : const nsAttrValue*
725 0 : nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName) const
726 : {
727 0 : return mAnimationElement->GetAnimAttr(aAttName);
728 : }
729 :
730 : bool
731 0 : nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName, nsAString& aResult) const
732 : {
733 0 : return mAnimationElement->GetAnimAttr(aAttName, aResult);
734 : }
735 :
736 : /*
737 : * A utility function to make querying an attribute that corresponds to an
738 : * nsSMILValue a little neater.
739 : *
740 : * @param aAttName The attribute name (in the global namespace).
741 : * @param aSMILAttr The SMIL attribute to perform the parsing.
742 : * @param[out] aResult The resulting nsSMILValue.
743 : * @param[out] aPreventCachingOfSandwich
744 : * If |aResult| contains dependencies on its context that
745 : * should prevent the result of the animation sandwich from
746 : * being cached and reused in future samples (as reported
747 : * by nsISMILAttr::ValueFromString), then this outparam
748 : * will be set to true. Otherwise it is left unmodified.
749 : *
750 : * Returns false if a parse error occurred, otherwise returns true.
751 : */
752 : bool
753 0 : nsSMILAnimationFunction::ParseAttr(nsIAtom* aAttName,
754 : const nsISMILAttr& aSMILAttr,
755 : nsSMILValue& aResult,
756 : bool& aPreventCachingOfSandwich) const
757 : {
758 0 : nsAutoString attValue;
759 0 : if (GetAttr(aAttName, attValue)) {
760 0 : bool preventCachingOfSandwich = false;
761 : nsresult rv = aSMILAttr.ValueFromString(attValue, mAnimationElement,
762 0 : aResult, preventCachingOfSandwich);
763 0 : if (NS_FAILED(rv))
764 0 : return false;
765 :
766 0 : if (preventCachingOfSandwich) {
767 0 : aPreventCachingOfSandwich = true;
768 : }
769 : }
770 0 : return true;
771 : }
772 :
773 : /*
774 : * SMILANIM specifies the following rules for animation function values:
775 : *
776 : * (1) if values is set, it overrides everything
777 : * (2) for from/to/by animation at least to or by must be specified, from on its
778 : * own (or nothing) is an error--which we will ignore
779 : * (3) if both by and to are specified only to will be used, by will be ignored
780 : * (4) if by is specified without from (by animation), forces additive behaviour
781 : * (5) if to is specified without from (to animation), special care needs to be
782 : * taken when compositing animation as such animations are composited last.
783 : *
784 : * This helper method applies these rules to fill in the values list and to set
785 : * some internal state.
786 : */
787 : nsresult
788 0 : nsSMILAnimationFunction::GetValues(const nsISMILAttr& aSMILAttr,
789 : nsSMILValueArray& aResult)
790 : {
791 0 : if (!mAnimationElement)
792 0 : return NS_ERROR_FAILURE;
793 :
794 0 : mValueNeedsReparsingEverySample = false;
795 0 : nsSMILValueArray result;
796 :
797 : // If "values" is set, use it
798 0 : if (HasAttr(nsGkAtoms::values)) {
799 0 : nsAutoString attValue;
800 0 : GetAttr(nsGkAtoms::values, attValue);
801 0 : bool preventCachingOfSandwich = false;
802 : nsresult rv = nsSMILParserUtils::ParseValues(attValue, mAnimationElement,
803 : aSMILAttr, result,
804 0 : preventCachingOfSandwich);
805 0 : if (NS_FAILED(rv))
806 0 : return rv;
807 :
808 0 : if (preventCachingOfSandwich) {
809 0 : mValueNeedsReparsingEverySample = true;
810 : }
811 : // Else try to/from/by
812 : } else {
813 0 : bool preventCachingOfSandwich = false;
814 0 : bool parseOk = true;
815 0 : nsSMILValue to, from, by;
816 : parseOk &= ParseAttr(nsGkAtoms::to, aSMILAttr, to,
817 0 : preventCachingOfSandwich);
818 : parseOk &= ParseAttr(nsGkAtoms::from, aSMILAttr, from,
819 0 : preventCachingOfSandwich);
820 : parseOk &= ParseAttr(nsGkAtoms::by, aSMILAttr, by,
821 0 : preventCachingOfSandwich);
822 :
823 0 : if (preventCachingOfSandwich) {
824 0 : mValueNeedsReparsingEverySample = true;
825 : }
826 :
827 0 : if (!parseOk)
828 0 : return NS_ERROR_FAILURE;
829 :
830 0 : result.SetCapacity(2);
831 0 : if (!to.IsNull()) {
832 0 : if (!from.IsNull()) {
833 0 : result.AppendElement(from);
834 0 : result.AppendElement(to);
835 : } else {
836 0 : result.AppendElement(to);
837 : }
838 0 : } else if (!by.IsNull()) {
839 0 : nsSMILValue effectiveFrom(by.mType);
840 0 : if (!from.IsNull())
841 0 : effectiveFrom = from;
842 : // Set values to 'from; from + by'
843 0 : result.AppendElement(effectiveFrom);
844 0 : nsSMILValue effectiveTo(effectiveFrom);
845 0 : if (!effectiveTo.IsNull() && NS_SUCCEEDED(effectiveTo.Add(by))) {
846 0 : result.AppendElement(effectiveTo);
847 : } else {
848 : // Using by-animation with non-additive type or bad base-value
849 0 : return NS_ERROR_FAILURE;
850 : }
851 : } else {
852 : // No values, no to, no by -- call it a day
853 0 : return NS_ERROR_FAILURE;
854 : }
855 : }
856 :
857 0 : result.SwapElements(aResult);
858 :
859 0 : return NS_OK;
860 : }
861 :
862 : void
863 0 : nsSMILAnimationFunction::CheckValueListDependentAttrs(PRUint32 aNumValues)
864 : {
865 0 : CheckKeyTimes(aNumValues);
866 0 : CheckKeySplines(aNumValues);
867 0 : }
868 :
869 : /**
870 : * Performs checks for the keyTimes attribute required by the SMIL spec but
871 : * which depend on other attributes and therefore needs to be updated as
872 : * dependent attributes are set.
873 : */
874 : void
875 0 : nsSMILAnimationFunction::CheckKeyTimes(PRUint32 aNumValues)
876 : {
877 0 : if (!HasAttr(nsGkAtoms::keyTimes))
878 0 : return;
879 :
880 0 : nsSMILCalcMode calcMode = GetCalcMode();
881 :
882 : // attribute is ignored for calcMode = paced
883 0 : if (calcMode == CALC_PACED) {
884 0 : SetKeyTimesErrorFlag(false);
885 0 : return;
886 : }
887 :
888 0 : PRUint32 numKeyTimes = mKeyTimes.Length();
889 0 : if (numKeyTimes < 1) {
890 : // keyTimes isn't set or failed preliminary checks
891 0 : SetKeyTimesErrorFlag(true);
892 0 : return;
893 : }
894 :
895 : // no. keyTimes == no. values
896 : // For to-animation the number of values is considered to be 2.
897 : bool matchingNumOfValues =
898 0 : numKeyTimes == (IsToAnimation() ? 2 : aNumValues);
899 0 : if (!matchingNumOfValues) {
900 0 : SetKeyTimesErrorFlag(true);
901 0 : return;
902 : }
903 :
904 : // first value must be 0
905 0 : if (mKeyTimes[0] != 0.0) {
906 0 : SetKeyTimesErrorFlag(true);
907 0 : return;
908 : }
909 :
910 : // last value must be 1 for linear or spline calcModes
911 0 : if (calcMode != CALC_DISCRETE && numKeyTimes > 1 &&
912 0 : mKeyTimes[numKeyTimes - 1] != 1.0) {
913 0 : SetKeyTimesErrorFlag(true);
914 0 : return;
915 : }
916 :
917 0 : SetKeyTimesErrorFlag(false);
918 : }
919 :
920 : void
921 0 : nsSMILAnimationFunction::CheckKeySplines(PRUint32 aNumValues)
922 : {
923 : // attribute is ignored if calc mode is not spline
924 0 : if (GetCalcMode() != CALC_SPLINE) {
925 0 : SetKeySplinesErrorFlag(false);
926 0 : return;
927 : }
928 :
929 : // calc mode is spline but the attribute is not set
930 0 : if (!HasAttr(nsGkAtoms::keySplines)) {
931 0 : SetKeySplinesErrorFlag(false);
932 0 : return;
933 : }
934 :
935 0 : if (mKeySplines.Length() < 1) {
936 : // keyTimes isn't set or failed preliminary checks
937 0 : SetKeySplinesErrorFlag(true);
938 0 : return;
939 : }
940 :
941 : // ignore splines if there's only one value
942 0 : if (aNumValues == 1 && !IsToAnimation()) {
943 0 : SetKeySplinesErrorFlag(false);
944 0 : return;
945 : }
946 :
947 : // no. keySpline specs == no. values - 1
948 0 : PRUint32 splineSpecs = mKeySplines.Length();
949 0 : if ((splineSpecs != aNumValues - 1 && !IsToAnimation()) ||
950 0 : (IsToAnimation() && splineSpecs != 1)) {
951 0 : SetKeySplinesErrorFlag(true);
952 0 : return;
953 : }
954 :
955 0 : SetKeySplinesErrorFlag(false);
956 : }
957 :
958 : bool
959 0 : nsSMILAnimationFunction::IsValueFixedForSimpleDuration() const
960 : {
961 0 : return mSimpleDuration.IsIndefinite() ||
962 0 : (!mHasChanged && mPrevSampleWasSingleValueAnimation);
963 : }
964 :
965 : //----------------------------------------------------------------------
966 : // Property getters
967 :
968 : bool
969 0 : nsSMILAnimationFunction::GetAccumulate() const
970 : {
971 0 : const nsAttrValue* value = GetAttr(nsGkAtoms::accumulate);
972 0 : if (!value)
973 0 : return false;
974 :
975 0 : return value->GetEnumValue();
976 : }
977 :
978 : bool
979 0 : nsSMILAnimationFunction::GetAdditive() const
980 : {
981 0 : const nsAttrValue* value = GetAttr(nsGkAtoms::additive);
982 0 : if (!value)
983 0 : return false;
984 :
985 0 : return value->GetEnumValue();
986 : }
987 :
988 : nsSMILAnimationFunction::nsSMILCalcMode
989 0 : nsSMILAnimationFunction::GetCalcMode() const
990 : {
991 0 : const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode);
992 0 : if (!value)
993 0 : return CALC_LINEAR;
994 :
995 0 : return nsSMILCalcMode(value->GetEnumValue());
996 : }
997 :
998 : //----------------------------------------------------------------------
999 : // Property setters / un-setters:
1000 :
1001 : nsresult
1002 0 : nsSMILAnimationFunction::SetAccumulate(const nsAString& aAccumulate,
1003 : nsAttrValue& aResult)
1004 : {
1005 0 : mHasChanged = true;
1006 : bool parseResult =
1007 0 : aResult.ParseEnumValue(aAccumulate, sAccumulateTable, true);
1008 0 : SetAccumulateErrorFlag(!parseResult);
1009 0 : return parseResult ? NS_OK : NS_ERROR_FAILURE;
1010 : }
1011 :
1012 : void
1013 0 : nsSMILAnimationFunction::UnsetAccumulate()
1014 : {
1015 0 : SetAccumulateErrorFlag(false);
1016 0 : mHasChanged = true;
1017 0 : }
1018 :
1019 : nsresult
1020 0 : nsSMILAnimationFunction::SetAdditive(const nsAString& aAdditive,
1021 : nsAttrValue& aResult)
1022 : {
1023 0 : mHasChanged = true;
1024 : bool parseResult
1025 0 : = aResult.ParseEnumValue(aAdditive, sAdditiveTable, true);
1026 0 : SetAdditiveErrorFlag(!parseResult);
1027 0 : return parseResult ? NS_OK : NS_ERROR_FAILURE;
1028 : }
1029 :
1030 : void
1031 0 : nsSMILAnimationFunction::UnsetAdditive()
1032 : {
1033 0 : SetAdditiveErrorFlag(false);
1034 0 : mHasChanged = true;
1035 0 : }
1036 :
1037 : nsresult
1038 0 : nsSMILAnimationFunction::SetCalcMode(const nsAString& aCalcMode,
1039 : nsAttrValue& aResult)
1040 : {
1041 0 : mHasChanged = true;
1042 : bool parseResult
1043 0 : = aResult.ParseEnumValue(aCalcMode, sCalcModeTable, true);
1044 0 : SetCalcModeErrorFlag(!parseResult);
1045 0 : return parseResult ? NS_OK : NS_ERROR_FAILURE;
1046 : }
1047 :
1048 : void
1049 0 : nsSMILAnimationFunction::UnsetCalcMode()
1050 : {
1051 0 : SetCalcModeErrorFlag(false);
1052 0 : mHasChanged = true;
1053 0 : }
1054 :
1055 : nsresult
1056 0 : nsSMILAnimationFunction::SetKeySplines(const nsAString& aKeySplines,
1057 : nsAttrValue& aResult)
1058 : {
1059 0 : mKeySplines.Clear();
1060 0 : aResult.SetTo(aKeySplines);
1061 :
1062 0 : nsTArray<double> keySplines;
1063 0 : nsresult rv = nsSMILParserUtils::ParseKeySplines(aKeySplines, keySplines);
1064 :
1065 0 : if (keySplines.Length() < 1 || keySplines.Length() % 4)
1066 0 : rv = NS_ERROR_FAILURE;
1067 :
1068 0 : if (NS_SUCCEEDED(rv))
1069 : {
1070 0 : mKeySplines.SetCapacity(keySplines.Length() % 4);
1071 0 : for (PRUint32 i = 0; i < keySplines.Length() && NS_SUCCEEDED(rv); i += 4)
1072 : {
1073 0 : if (!mKeySplines.AppendElement(nsSMILKeySpline(keySplines[i],
1074 0 : keySplines[i+1],
1075 0 : keySplines[i+2],
1076 0 : keySplines[i+3]))) {
1077 0 : rv = NS_ERROR_OUT_OF_MEMORY;
1078 : }
1079 : }
1080 : }
1081 :
1082 0 : mHasChanged = true;
1083 :
1084 0 : return rv;
1085 : }
1086 :
1087 : void
1088 0 : nsSMILAnimationFunction::UnsetKeySplines()
1089 : {
1090 0 : mKeySplines.Clear();
1091 0 : SetKeySplinesErrorFlag(false);
1092 0 : mHasChanged = true;
1093 0 : }
1094 :
1095 : nsresult
1096 0 : nsSMILAnimationFunction::SetKeyTimes(const nsAString& aKeyTimes,
1097 : nsAttrValue& aResult)
1098 : {
1099 0 : mKeyTimes.Clear();
1100 0 : aResult.SetTo(aKeyTimes);
1101 :
1102 : nsresult rv =
1103 : nsSMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyTimes, true,
1104 0 : mKeyTimes);
1105 :
1106 0 : if (NS_SUCCEEDED(rv) && mKeyTimes.Length() < 1)
1107 0 : rv = NS_ERROR_FAILURE;
1108 :
1109 0 : if (NS_FAILED(rv))
1110 0 : mKeyTimes.Clear();
1111 :
1112 0 : mHasChanged = true;
1113 :
1114 0 : return NS_OK;
1115 : }
1116 :
1117 : void
1118 0 : nsSMILAnimationFunction::UnsetKeyTimes()
1119 : {
1120 0 : mKeyTimes.Clear();
1121 0 : SetKeyTimesErrorFlag(false);
1122 0 : mHasChanged = true;
1123 0 : }
|