void nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr, nsSMILValue& aResult) { mHasChanged = false; mPrevSampleWasSingleValueAnimation = false; mWasSkippedInPrevSample = false; // Skip animations that are inactive or in error if (!IsActiveOrFrozen() || mErrorFlags != 0) return; // Get the animation values nsSMILValueArray values; nsresult rv = GetValues(aSMILAttr, values); if (NS_FAILED(rv)) return; // Check that we have the right number of keySplines and keyTimes CheckValueListDependentAttrs(values.Length()); if (mErrorFlags != 0) return; // If this interval is active, we must have a non-negative mSampleTime NS_ABORT_IF_FALSE(mSampleTime >= 0 || !mIsActive, "Negative sample time for active animation"); NS_ABORT_IF_FALSE(mSimpleDuration.IsResolved() || mLastValue, "Unresolved simple duration for active or frozen animation"); // If we want to add but don't have a base value then just fail outright. // This can happen when we skipped getting the base value because there's an // animation function in the sandwich that should replace it but that function // failed unexpectedly. bool isAdditive = IsAdditive(); if (isAdditive && aResult.IsNull()) return; nsSMILValue result; if (values.Length() == 1 && !IsToAnimation()) { // Single-valued animation result = values[0]; mPrevSampleWasSingleValueAnimation = true; } else if (mLastValue) { // Sampling last value const nsSMILValue& last = values[values.Length() - 1]; result = last; // See comment in AccumulateResult: to-animation does not accumulate if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) { // If the target attribute type doesn't support addition Add will // fail leaving result = last result.Add(last, mRepeatIteration); } } else { // Interpolation if (NS_FAILED(InterpolateResult(values, result, aResult))) return; if (NS_FAILED(AccumulateResult(values, result))) return; } // If additive animation isn't required or isn't supported, set the value. if (!isAdditive || NS_FAILED(aResult.SandwichAdd(result))) { aResult.Swap(result); // Note: The old value of aResult is now in |result|, and it will get // cleaned up when |result| goes out of scope, when this function returns. } }
nsresult nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues, nsSMILValue& aResult, nsSMILValue& aBaseValue) { // Sanity check animation values if ((!IsToAnimation() && aValues.Length() < 2) || (IsToAnimation() && aValues.Length() != 1)) { NS_ERROR("Unexpected number of values"); return NS_ERROR_FAILURE; } if (IsToAnimation() && aBaseValue.IsNull()) { return NS_ERROR_FAILURE; } // Get the normalised progress through the simple duration. // // If we have an indefinite simple duration, just set the progress to be // 0 which will give us the expected behaviour of the animation being fixed at // its starting point. double simpleProgress = 0.0; if (mSimpleDuration.IsResolved()) { nsSMILTime dur = mSimpleDuration.GetMillis(); NS_ABORT_IF_FALSE(dur >= 0, "Simple duration should not be negative"); NS_ABORT_IF_FALSE(mSampleTime >= 0, "Sample time should not be negative"); if (mSampleTime >= dur || mSampleTime < 0) { NS_ERROR("Animation sampled outside interval"); return NS_ERROR_FAILURE; } if (dur > 0) { simpleProgress = (double)mSampleTime / dur; } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0) } nsresult rv = NS_OK; nsSMILCalcMode calcMode = GetCalcMode(); if (calcMode != CALC_DISCRETE) { // Get the normalised progress between adjacent values const nsSMILValue* from = nsnull; const nsSMILValue* to = nsnull; // Init to -1 to make sure that if we ever forget to set this, the // NS_ABORT_IF_FALSE that tests that intervalProgress is in range will fail. double intervalProgress = -1.f; if (IsToAnimation()) { from = &aBaseValue; to = &aValues[0]; if (calcMode == CALC_PACED) { // Note: key[Times/Splines/Points] are ignored for calcMode="paced" intervalProgress = simpleProgress; } else { double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, calcMode); intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0); } } else if (calcMode == CALC_PACED) { rv = ComputePacedPosition(aValues, simpleProgress, intervalProgress, from, to); // Note: If the above call fails, we'll skip the "from->Interpolate" // call below, and we'll drop into the CALC_DISCRETE section // instead. (as the spec says we should, because our failure was // presumably due to the values being non-additive) } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, calcMode); PRUint32 index = (PRUint32)floor(scaledSimpleProgress * (aValues.Length() - 1)); from = &aValues[index]; to = &aValues[index + 1]; intervalProgress = scaledSimpleProgress * (aValues.Length() - 1) - index; intervalProgress = ScaleIntervalProgress(intervalProgress, index); } if (NS_SUCCEEDED(rv)) { NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation"); NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation"); NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f, "Interval progress should be in the range [0, 1)"); rv = from->Interpolate(*to, intervalProgress, aResult); } } // Discrete-CalcMode case // Note: If interpolation failed (isn't supported for this type), the SVG // spec says to force discrete mode. if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) { double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, CALC_DISCRETE); if (IsToAnimation()) { // We don't follow SMIL 3, 12.6.4, where discrete to animations // are the same as <set> animations. Instead, we treat it as a // discrete animation with two values (the underlying value and // the to="" value), and honor keyTimes="" as well. PRUint32 index = (PRUint32)floor(scaledSimpleProgress * 2); aResult = index == 0 ? aBaseValue : aValues[0]; } else { PRUint32 index = (PRUint32)floor(scaledSimpleProgress * aValues.Length()); aResult = aValues[index]; } rv = NS_OK; } return rv; }