summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGMotionSMILType.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/svg/SVGMotionSMILType.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/svg/SVGMotionSMILType.cpp')
-rw-r--r--dom/svg/SVGMotionSMILType.cpp459
1 files changed, 459 insertions, 0 deletions
diff --git a/dom/svg/SVGMotionSMILType.cpp b/dom/svg/SVGMotionSMILType.cpp
new file mode 100644
index 0000000000..15521ee54d
--- /dev/null
+++ b/dom/svg/SVGMotionSMILType.cpp
@@ -0,0 +1,459 @@
+/* -*- 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 "mozilla/SMILValue.h"
+#include "mozilla/gfx/Point.h"
+#include "gfx2DGlue.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 SMILValue will get a nsTArray of these (generally
+ * with at most 1 entry in the array, except for in SandwichAdd). (This
+ * matches our behavior in SVGTransformListSMILType.)
+ *
+ * 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()
+ : mRotateType(eRotateType_Auto),
+ mRotateAngle(0.0),
+ mSegmentType(eSegmentType_Translation),
+ mU{} {}
+
+ // 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;
+};
+
+using MotionSegmentArray = FallibleTArray<MotionSegment>;
+
+// Helper methods to cast SMILValue.mU.mPtr to the right pointer-type
+static MotionSegmentArray& ExtractMotionSegmentArray(SMILValue& aValue) {
+ return *static_cast<MotionSegmentArray*>(aValue.mU.mPtr);
+}
+
+static const MotionSegmentArray& ExtractMotionSegmentArray(
+ const SMILValue& aValue) {
+ return *static_cast<const MotionSegmentArray*>(aValue.mU.mPtr);
+}
+
+// nsISMILType Methods
+// -------------------
+
+void SVGMotionSMILType::Init(SMILValue& aValue) const {
+ MOZ_ASSERT(aValue.IsNull(), "Unexpected SMIL type");
+
+ aValue.mType = this;
+ aValue.mU.mPtr = new MotionSegmentArray(1);
+}
+
+void SVGMotionSMILType::Destroy(SMILValue& 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 = SMILNullType::Singleton();
+}
+
+nsresult SVGMotionSMILType::Assign(SMILValue& aDest,
+ const SMILValue& 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 SMILValue& aLeft,
+ const SMILValue& 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(SMILValue& aDest, const SMILValue& 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(SMILValue& aDest,
+ const SMILValue& 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 SMILValue& aFrom,
+ const SMILValue& 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");
+ aDistance = std::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 SMILValue& aStartVal,
+ const SMILValue& aEndVal,
+ double aUnitDistance,
+ SMILValue& 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(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: Ususally, 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)
+ // Notable exception: when a to-animation layers on top of lower priority
+ // animation(s) -- see comment below.
+ Path* path = endParams.mPath;
+ RotateType rotateType = endSeg.mRotateType;
+ float rotateAngle = endSeg.mRotateAngle;
+
+ float startDist;
+ if (startArr.IsEmpty() ||
+ startArr[0].mU.mPathPointParams.mPath != endParams.mPath) {
+ // When a to-animation follows other lower priority animation(s),
+ // the endParams will contain a different path from the animation(s)
+ // that it layers on top of.
+ // Per SMIL spec, we should interpolate from the value at startArr.
+ // However, neither Chrome nor Safari implements to-animation that way.
+ // For simplicity, we use the same behavior as other browsers: override
+ // previous animations and start at the initial underlying value.
+ startDist = 0.0f;
+ } else {
+ MOZ_ASSERT(startArr.Length() <= 1,
+ "Invalid start-point for animateMotion interpolation");
+ 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");
+ 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 SMILValue& 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 */
+SMILValue SVGMotionSMILType::ConstructSMILValue(Path* aPath, float aDist,
+ RotateType aRotateType,
+ float aRotateAngle) {
+ SMILValue 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