diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/svg/SVGMotionSMILType.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/svg/SVGMotionSMILType.cpp')
-rw-r--r-- | dom/svg/SVGMotionSMILType.cpp | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/dom/svg/SVGMotionSMILType.cpp b/dom/svg/SVGMotionSMILType.cpp new file mode 100644 index 000000000..2e91d1774 --- /dev/null +++ b/dom/svg/SVGMotionSMILType.cpp @@ -0,0 +1,491 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* implementation of nsISMILType for use by <animateMotion> element */ + +#include "SVGMotionSMILType.h" + +#include "gfx2DGlue.h" +#include "mozilla/gfx/Point.h" +#include "nsSMILValue.h" +#include "nsDebug.h" +#include "nsMathUtils.h" +#include "nsISupportsUtils.h" +#include "nsTArray.h" +#include <math.h> + +using namespace mozilla::gfx; + +namespace mozilla { + +/*static*/ SVGMotionSMILType SVGMotionSMILType::sSingleton; + + +// Helper enum, for distinguishing between types of MotionSegment structs +enum SegmentType { + eSegmentType_Translation, + eSegmentType_PathPoint +}; + +// Helper Structs: containers for params to define our MotionSegment +// (either simple translation or point-on-a-path) +struct TranslationParams { // Simple translation + float mX; + float mY; +}; +struct PathPointParams { // Point along a path + // Refcounted: need to AddRef/Release. This can't be an nsRefPtr because + // this struct is used inside a union so it can't have a default constructor. + Path* MOZ_OWNING_REF mPath; + float mDistToPoint; // Distance from path start to the point on the path that + // we're interested in. +}; + +/** + * Helper Struct: MotionSegment + * + * Instances of this class represent the points that we move between during + * <animateMotion>. Each nsSMILValue will get a nsTArray of these (generally + * with at most 1 entry in the array, except for in SandwichAdd). (This + * matches our behavior in nsSVGTransformSMILType.) + * + * NOTE: In general, MotionSegments are represented as points on a path + * (eSegmentType_PathPoint), so that we can easily interpolate and compute + * distance *along their path*. However, Add() outputs MotionSegments as + * simple translations (eSegmentType_Translation), because adding two points + * from a path (e.g. when accumulating a repeated animation) will generally + * take you to an arbitrary point *off* of the path. + */ +struct MotionSegment +{ + // Default constructor just locks us into being a Translation, and leaves + // other fields uninitialized (since client is presumably about to set them) + MotionSegment() + : mSegmentType(eSegmentType_Translation) + { } + + // Constructor for a translation + MotionSegment(float aX, float aY, float aRotateAngle) + : mRotateType(eRotateType_Explicit), mRotateAngle(aRotateAngle), + mSegmentType(eSegmentType_Translation) + { + mU.mTranslationParams.mX = aX; + mU.mTranslationParams.mY = aY; + } + + // Constructor for a point on a path (NOTE: AddRef's) + MotionSegment(Path* aPath, float aDistToPoint, + RotateType aRotateType, float aRotateAngle) + : mRotateType(aRotateType), mRotateAngle(aRotateAngle), + mSegmentType(eSegmentType_PathPoint) + { + mU.mPathPointParams.mPath = aPath; + mU.mPathPointParams.mDistToPoint = aDistToPoint; + + NS_ADDREF(mU.mPathPointParams.mPath); // Retain a reference to path + } + + // Copy constructor (NOTE: AddRef's if we're eSegmentType_PathPoint) + MotionSegment(const MotionSegment& aOther) + : mRotateType(aOther.mRotateType), mRotateAngle(aOther.mRotateAngle), + mSegmentType(aOther.mSegmentType) + { + if (mSegmentType == eSegmentType_Translation) { + mU.mTranslationParams = aOther.mU.mTranslationParams; + } else { // mSegmentType == eSegmentType_PathPoint + mU.mPathPointParams = aOther.mU.mPathPointParams; + NS_ADDREF(mU.mPathPointParams.mPath); // Retain a reference to path + } + } + + // Destructor (releases any reference we were holding onto) + ~MotionSegment() + { + if (mSegmentType == eSegmentType_PathPoint) { + NS_RELEASE(mU.mPathPointParams.mPath); + } + } + + // Comparison operators + bool operator==(const MotionSegment& aOther) const + { + // Compare basic params + if (mSegmentType != aOther.mSegmentType || + mRotateType != aOther.mRotateType || + (mRotateType == eRotateType_Explicit && // Technically, angle mismatch + mRotateAngle != aOther.mRotateAngle)) { // only matters for Explicit. + return false; + } + + // Compare translation params, if we're a translation. + if (mSegmentType == eSegmentType_Translation) { + return mU.mTranslationParams.mX == aOther.mU.mTranslationParams.mX && + mU.mTranslationParams.mY == aOther.mU.mTranslationParams.mY; + } + + // Else, compare path-point params, if we're a path point. + return (mU.mPathPointParams.mPath == aOther.mU.mPathPointParams.mPath) && + (mU.mPathPointParams.mDistToPoint == + aOther.mU.mPathPointParams.mDistToPoint); + } + + bool operator!=(const MotionSegment& aOther) const + { + return !(*this == aOther); + } + + // Member Data + // ----------- + RotateType mRotateType; // Explicit angle vs. auto vs. auto-reverse. + float mRotateAngle; // Only used if mRotateType == eRotateType_Explicit. + const SegmentType mSegmentType; // This determines how we interpret + // mU. (const for safety/sanity) + + union { // Union to let us hold the params for either segment-type. + TranslationParams mTranslationParams; + PathPointParams mPathPointParams; + } mU; +}; + +typedef FallibleTArray<MotionSegment> MotionSegmentArray; + +// Helper methods to cast nsSMILValue.mU.mPtr to the right pointer-type +static MotionSegmentArray& +ExtractMotionSegmentArray(nsSMILValue& aValue) +{ + return *static_cast<MotionSegmentArray*>(aValue.mU.mPtr); +} + +static const MotionSegmentArray& +ExtractMotionSegmentArray(const nsSMILValue& aValue) +{ + return *static_cast<const MotionSegmentArray*>(aValue.mU.mPtr); +} + +// nsISMILType Methods +// ------------------- + +void +SVGMotionSMILType::Init(nsSMILValue& aValue) const +{ + MOZ_ASSERT(aValue.IsNull(), "Unexpected SMIL type"); + + aValue.mType = this; + aValue.mU.mPtr = new MotionSegmentArray(1); +} + +void +SVGMotionSMILType::Destroy(nsSMILValue& aValue) const +{ + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL type"); + + MotionSegmentArray* arr = static_cast<MotionSegmentArray*>(aValue.mU.mPtr); + delete arr; + + aValue.mU.mPtr = nullptr; + aValue.mType = nsSMILNullType::Singleton(); +} + +nsresult +SVGMotionSMILType::Assign(nsSMILValue& aDest, const nsSMILValue& aSrc) const +{ + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + + const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aSrc); + MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest); + if (!dstArr.Assign(srcArr, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +bool +SVGMotionSMILType::IsEqual(const nsSMILValue& aLeft, + const nsSMILValue& aRight) const +{ + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected SMIL type"); + + const MotionSegmentArray& leftArr = ExtractMotionSegmentArray(aLeft); + const MotionSegmentArray& rightArr = ExtractMotionSegmentArray(aRight); + + // If array-lengths don't match, we're trivially non-equal. + if (leftArr.Length() != rightArr.Length()) { + return false; + } + + // Array-lengths match -- check each array-entry for equality. + uint32_t length = leftArr.Length(); // == rightArr->Length(), if we get here + for (uint32_t i = 0; i < length; ++i) { + if (leftArr[i] != rightArr[i]) { + return false; + } + } + + return true; // If we get here, we found no differences. +} + +// Helper method for Add & CreateMatrix +inline static void +GetAngleAndPointAtDistance(Path* aPath, float aDistance, + RotateType aRotateType, + float& aRotateAngle, // in & out-param. + Point& aPoint) // out-param. +{ + if (aRotateType == eRotateType_Explicit) { + // Leave aRotateAngle as-is. + aPoint = aPath->ComputePointAtLength(aDistance); + } else { + Point tangent; // Unit vector tangent to the point we find. + aPoint = aPath->ComputePointAtLength(aDistance, &tangent); + float tangentAngle = atan2(tangent.y, tangent.x); + if (aRotateType == eRotateType_Auto) { + aRotateAngle = tangentAngle; + } else { + MOZ_ASSERT(aRotateType == eRotateType_AutoReverse); + aRotateAngle = M_PI + tangentAngle; + } + } +} + +nsresult +SVGMotionSMILType::Add(nsSMILValue& aDest, const nsSMILValue& aValueToAdd, + uint32_t aCount) const +{ + MOZ_ASSERT(aDest.mType == aValueToAdd.mType, + "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + + MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest); + const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd); + + // We're doing a simple add here (as opposed to a sandwich add below). We + // only do this when we're accumulating a repeat result. + // NOTE: In other nsISMILTypes, we use this method with a barely-initialized + // |aDest| value to assist with "by" animation. (In this case, + // "barely-initialized" would mean dstArr.Length() would be empty.) However, + // we don't do this for <animateMotion>, because we instead use our "by" + // value to construct an equivalent "path" attribute, and we use *that* for + // our actual animation. + MOZ_ASSERT(srcArr.Length() == 1, "Invalid source segment arr to add"); + MOZ_ASSERT(dstArr.Length() == 1, "Invalid dest segment arr to add to"); + const MotionSegment& srcSeg = srcArr[0]; + const MotionSegment& dstSeg = dstArr[0]; + MOZ_ASSERT(srcSeg.mSegmentType == eSegmentType_PathPoint, + "expecting to be adding points from a motion path"); + MOZ_ASSERT(dstSeg.mSegmentType == eSegmentType_PathPoint, + "expecting to be adding points from a motion path"); + + const PathPointParams& srcParams = srcSeg.mU.mPathPointParams; + const PathPointParams& dstParams = dstSeg.mU.mPathPointParams; + + MOZ_ASSERT(srcSeg.mRotateType == dstSeg.mRotateType && + srcSeg.mRotateAngle == dstSeg.mRotateAngle, + "unexpected angle mismatch"); + MOZ_ASSERT(srcParams.mPath == dstParams.mPath, + "unexpected path mismatch"); + Path* path = srcParams.mPath; + + // Use destination to get our rotate angle. + float rotateAngle = dstSeg.mRotateAngle; + Point dstPt; + GetAngleAndPointAtDistance(path, dstParams.mDistToPoint, dstSeg.mRotateType, + rotateAngle, dstPt); + + Point srcPt = path->ComputePointAtLength(srcParams.mDistToPoint); + + float newX = dstPt.x + srcPt.x * aCount; + float newY = dstPt.y + srcPt.y * aCount; + + // Replace destination's current value -- a point-on-a-path -- with the + // translation that results from our addition. + dstArr.ReplaceElementAt(0, MotionSegment(newX, newY, rotateAngle)); + return NS_OK; +} + +nsresult +SVGMotionSMILType::SandwichAdd(nsSMILValue& aDest, + const nsSMILValue& aValueToAdd) const +{ + MOZ_ASSERT(aDest.mType == aValueToAdd.mType, + "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest); + const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd); + + // We're only expecting to be adding 1 segment on to the list + MOZ_ASSERT(srcArr.Length() == 1, + "Trying to do sandwich add of more than one value"); + + if (!dstArr.AppendElement(srcArr[0], fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +nsresult +SVGMotionSMILType::ComputeDistance(const nsSMILValue& aFrom, + const nsSMILValue& aTo, + double& aDistance) const +{ + MOZ_ASSERT(aFrom.mType == aTo.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + const MotionSegmentArray& fromArr = ExtractMotionSegmentArray(aFrom); + const MotionSegmentArray& toArr = ExtractMotionSegmentArray(aTo); + + // ComputeDistance is only used for calculating distances between single + // values in a values array. So we should only have one entry in each array. + MOZ_ASSERT(fromArr.Length() == 1, + "Wrong number of elements in from value"); + MOZ_ASSERT(toArr.Length() == 1, + "Wrong number of elements in to value"); + + const MotionSegment& from = fromArr[0]; + const MotionSegment& to = toArr[0]; + + MOZ_ASSERT(from.mSegmentType == to.mSegmentType, + "Mismatched MotionSegment types"); + if (from.mSegmentType == eSegmentType_PathPoint) { + const PathPointParams& fromParams = from.mU.mPathPointParams; + const PathPointParams& toParams = to.mU.mPathPointParams; + MOZ_ASSERT(fromParams.mPath == toParams.mPath, + "Interpolation endpoints should be from same path"); + MOZ_ASSERT(fromParams.mDistToPoint <= toParams.mDistToPoint, + "To value shouldn't be before from value on path"); + aDistance = fabs(toParams.mDistToPoint - fromParams.mDistToPoint); + } else { + const TranslationParams& fromParams = from.mU.mTranslationParams; + const TranslationParams& toParams = to.mU.mTranslationParams; + float dX = toParams.mX - fromParams.mX; + float dY = toParams.mY - fromParams.mY; + aDistance = NS_hypot(dX, dY); + } + + return NS_OK; +} + +// Helper method for Interpolate() +static inline float +InterpolateFloat(const float& aStartFlt, const float& aEndFlt, + const double& aUnitDistance) +{ + return aStartFlt + aUnitDistance * (aEndFlt - aStartFlt); +} + +nsresult +SVGMotionSMILType::Interpolate(const nsSMILValue& aStartVal, + const nsSMILValue& aEndVal, + double aUnitDistance, + nsSMILValue& aResult) const +{ + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, + "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + MOZ_ASSERT(aUnitDistance >= 0.0 && aUnitDistance <= 1.0, + "unit distance value out of bounds"); + + const MotionSegmentArray& startArr = ExtractMotionSegmentArray(aStartVal); + const MotionSegmentArray& endArr = ExtractMotionSegmentArray(aEndVal); + MotionSegmentArray& resultArr = ExtractMotionSegmentArray(aResult); + + MOZ_ASSERT(startArr.Length() <= 1, + "Invalid start-point for animateMotion interpolation"); + MOZ_ASSERT(endArr.Length() == 1, + "Invalid end-point for animateMotion interpolation"); + MOZ_ASSERT(resultArr.IsEmpty(), + "Expecting result to be just-initialized w/ empty array"); + + const MotionSegment& endSeg = endArr[0]; + MOZ_ASSERT(endSeg.mSegmentType == eSegmentType_PathPoint, + "Expecting to be interpolating along a path"); + + const PathPointParams& endParams = endSeg.mU.mPathPointParams; + // NOTE: path & angle should match between start & end (since presumably + // start & end came from the same <animateMotion> element), unless start is + // empty. (as it would be for pure 'to' animation) + Path* path = endParams.mPath; + RotateType rotateType = endSeg.mRotateType; + float rotateAngle = endSeg.mRotateAngle; + + float startDist; + if (startArr.IsEmpty()) { + startDist = 0.0f; + } else { + const MotionSegment& startSeg = startArr[0]; + MOZ_ASSERT(startSeg.mSegmentType == eSegmentType_PathPoint, + "Expecting to be interpolating along a path"); + const PathPointParams& startParams = startSeg.mU.mPathPointParams; + MOZ_ASSERT(startSeg.mRotateType == endSeg.mRotateType && + startSeg.mRotateAngle == endSeg.mRotateAngle, + "unexpected angle mismatch"); + MOZ_ASSERT(startParams.mPath == endParams.mPath, + "unexpected path mismatch"); + startDist = startParams.mDistToPoint; + } + + // Get the interpolated distance along our path. + float resultDist = InterpolateFloat(startDist, endParams.mDistToPoint, + aUnitDistance); + + // Construct the intermediate result segment, and put it in our outparam. + // AppendElement has guaranteed success here, since Init() allocates 1 slot. + MOZ_ALWAYS_TRUE(resultArr.AppendElement(MotionSegment(path, resultDist, + rotateType, + rotateAngle), + fallible)); + return NS_OK; +} + +/* static */ gfx::Matrix +SVGMotionSMILType::CreateMatrix(const nsSMILValue& aSMILVal) +{ + const MotionSegmentArray& arr = ExtractMotionSegmentArray(aSMILVal); + + gfx::Matrix matrix; + uint32_t length = arr.Length(); + for (uint32_t i = 0; i < length; i++) { + Point point; // initialized below + float rotateAngle = arr[i].mRotateAngle; // might get updated below + if (arr[i].mSegmentType == eSegmentType_Translation) { + point.x = arr[i].mU.mTranslationParams.mX; + point.y = arr[i].mU.mTranslationParams.mY; + MOZ_ASSERT(arr[i].mRotateType == eRotateType_Explicit, + "'auto'/'auto-reverse' should have been converted to " + "explicit angles when we generated this translation"); + } else { + GetAngleAndPointAtDistance(arr[i].mU.mPathPointParams.mPath, + arr[i].mU.mPathPointParams.mDistToPoint, + arr[i].mRotateType, + rotateAngle, point); + } + matrix.PreTranslate(point.x, point.y); + matrix.PreRotate(rotateAngle); + } + return matrix; +} + +/* static */ nsSMILValue +SVGMotionSMILType::ConstructSMILValue(Path* aPath, + float aDist, + RotateType aRotateType, + float aRotateAngle) +{ + nsSMILValue smilVal(&SVGMotionSMILType::sSingleton); + MotionSegmentArray& arr = ExtractMotionSegmentArray(smilVal); + + // AppendElement has guaranteed success here, since Init() allocates 1 slot. + MOZ_ALWAYS_TRUE(arr.AppendElement(MotionSegment(aPath, aDist, + aRotateType, aRotateAngle), + fallible)); + return smilVal; +} + +} // namespace mozilla |