tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SVGMotionSMILType.cpp (18669B)


      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 /* implementation of nsISMILType for use by <animateMotion> element */
      8 
      9 #include "SVGMotionSMILType.h"
     10 
     11 #include <math.h>
     12 
     13 #include "gfx2DGlue.h"
     14 #include "mozilla/SMILValue.h"
     15 #include "mozilla/gfx/Point.h"
     16 #include "nsDebug.h"
     17 #include "nsISupportsUtils.h"
     18 #include "nsMathUtils.h"
     19 #include "nsTArray.h"
     20 
     21 using namespace mozilla::gfx;
     22 
     23 namespace mozilla {
     24 
     25 /*static*/
     26 SVGMotionSMILType SVGMotionSMILType::sSingleton;
     27 
     28 // Helper enum, for distinguishing between types of MotionSegment structs
     29 enum SegmentType { eSegmentType_Translation, eSegmentType_PathPoint };
     30 
     31 // Helper Structs: containers for params to define our MotionSegment
     32 // (either simple translation or point-on-a-path)
     33 struct TranslationParams {  // Simple translation
     34  float mX;
     35  float mY;
     36 };
     37 struct PathPointParams {  // Point along a path
     38  // Refcounted: need to AddRef/Release.  This can't be an nsRefPtr because
     39  // this struct is used inside a union so it can't have a default constructor.
     40  Path* MOZ_OWNING_REF mPath;
     41  float mDistToPoint;  // Distance from path start to the point on the path that
     42                       // we're interested in.
     43 };
     44 
     45 /**
     46 * Helper Struct: MotionSegment
     47 *
     48 * Instances of this class represent the points that we move between during
     49 * <animateMotion>.  Each SMILValue will get a nsTArray of these (generally
     50 * with at most 1 entry in the array, except for in SandwichAdd).  (This
     51 * matches our behavior in SVGTransformListSMILType.)
     52 *
     53 * NOTE: In general, MotionSegments are represented as points on a path
     54 * (eSegmentType_PathPoint), so that we can easily interpolate and compute
     55 * distance *along their path*.  However, Add() outputs MotionSegments as
     56 * simple translations (eSegmentType_Translation), because adding two points
     57 * from a path (e.g. when accumulating a repeated animation) will generally
     58 * take you to an arbitrary point *off* of the path.
     59 */
     60 struct MotionSegment {
     61  // Default constructor just locks us into being a Translation, and leaves
     62  // other fields uninitialized (since client is presumably about to set them)
     63  MotionSegment()
     64      : mRotateType(eRotateType_Auto),
     65        mRotateAngle(0.0),
     66        mSegmentType(eSegmentType_Translation),
     67        mU{} {}
     68 
     69  // Constructor for a translation
     70  MotionSegment(float aX, float aY, float aRotateAngle)
     71      : mRotateType(eRotateType_Explicit),
     72        mRotateAngle(aRotateAngle),
     73        mSegmentType(eSegmentType_Translation) {
     74    mU.mTranslationParams.mX = aX;
     75    mU.mTranslationParams.mY = aY;
     76  }
     77 
     78  // Constructor for a point on a path (NOTE: AddRef's)
     79  MotionSegment(Path* aPath, float aDistToPoint, RotateType aRotateType,
     80                float aRotateAngle)
     81      : mRotateType(aRotateType),
     82        mRotateAngle(aRotateAngle),
     83        mSegmentType(eSegmentType_PathPoint) {
     84    mU.mPathPointParams.mPath = aPath;
     85    mU.mPathPointParams.mDistToPoint = aDistToPoint;
     86 
     87    NS_ADDREF(mU.mPathPointParams.mPath);  // Retain a reference to path
     88  }
     89 
     90  // Copy constructor (NOTE: AddRef's if we're eSegmentType_PathPoint)
     91  MotionSegment(const MotionSegment& aOther)
     92      : mRotateType(aOther.mRotateType),
     93        mRotateAngle(aOther.mRotateAngle),
     94        mSegmentType(aOther.mSegmentType) {
     95    if (mSegmentType == eSegmentType_Translation) {
     96      mU.mTranslationParams = aOther.mU.mTranslationParams;
     97    } else {  // mSegmentType == eSegmentType_PathPoint
     98      mU.mPathPointParams = aOther.mU.mPathPointParams;
     99      NS_ADDREF(mU.mPathPointParams.mPath);  // Retain a reference to path
    100    }
    101  }
    102 
    103  // Destructor (releases any reference we were holding onto)
    104  ~MotionSegment() {
    105    if (mSegmentType == eSegmentType_PathPoint) {
    106      NS_RELEASE(mU.mPathPointParams.mPath);
    107    }
    108  }
    109 
    110  // Comparison operators
    111  bool operator==(const MotionSegment& aOther) const {
    112    // Compare basic params
    113    if (mSegmentType != aOther.mSegmentType ||
    114        mRotateType != aOther.mRotateType ||
    115        (mRotateType == eRotateType_Explicit &&   // Technically, angle mismatch
    116         mRotateAngle != aOther.mRotateAngle)) {  // only matters for Explicit.
    117      return false;
    118    }
    119 
    120    // Compare translation params, if we're a translation.
    121    if (mSegmentType == eSegmentType_Translation) {
    122      return mU.mTranslationParams.mX == aOther.mU.mTranslationParams.mX &&
    123             mU.mTranslationParams.mY == aOther.mU.mTranslationParams.mY;
    124    }
    125 
    126    // Else, compare path-point params, if we're a path point.
    127    return (mU.mPathPointParams.mPath == aOther.mU.mPathPointParams.mPath) &&
    128           (mU.mPathPointParams.mDistToPoint ==
    129            aOther.mU.mPathPointParams.mDistToPoint);
    130  }
    131 
    132  bool operator!=(const MotionSegment& aOther) const {
    133    return !(*this == aOther);
    134  }
    135 
    136  // Member Data
    137  // -----------
    138  RotateType mRotateType;  // Explicit angle vs. auto vs. auto-reverse.
    139  float mRotateAngle;      // Only used if mRotateType == eRotateType_Explicit.
    140  const SegmentType mSegmentType;  // This determines how we interpret
    141                                   // mU. (const for safety/sanity)
    142 
    143  union {  // Union to let us hold the params for either segment-type.
    144    TranslationParams mTranslationParams;
    145    PathPointParams mPathPointParams;
    146  } mU;
    147 };
    148 
    149 using MotionSegmentArray = FallibleTArray<MotionSegment>;
    150 
    151 // Helper methods to cast SMILValue.mU.mPtr to the right pointer-type
    152 static MotionSegmentArray& ExtractMotionSegmentArray(SMILValue& aValue) {
    153  return *static_cast<MotionSegmentArray*>(aValue.mU.mPtr);
    154 }
    155 
    156 static const MotionSegmentArray& ExtractMotionSegmentArray(
    157    const SMILValue& aValue) {
    158  return *static_cast<const MotionSegmentArray*>(aValue.mU.mPtr);
    159 }
    160 
    161 // nsISMILType Methods
    162 // -------------------
    163 
    164 void SVGMotionSMILType::InitValue(SMILValue& aValue) const {
    165  MOZ_ASSERT(aValue.IsNull(), "Unexpected SMIL type");
    166 
    167  aValue.mType = this;
    168  aValue.mU.mPtr = new MotionSegmentArray(1);
    169 }
    170 
    171 void SVGMotionSMILType::DestroyValue(SMILValue& aValue) const {
    172  MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL type");
    173 
    174  MotionSegmentArray* arr = static_cast<MotionSegmentArray*>(aValue.mU.mPtr);
    175  delete arr;
    176 
    177  aValue.mU.mPtr = nullptr;
    178  aValue.mType = SMILNullType::Singleton();
    179 }
    180 
    181 nsresult SVGMotionSMILType::Assign(SMILValue& aDest,
    182                                   const SMILValue& aSrc) const {
    183  MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types");
    184  MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type");
    185 
    186  const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aSrc);
    187  MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest);
    188  if (!dstArr.Assign(srcArr, fallible)) {
    189    return NS_ERROR_OUT_OF_MEMORY;
    190  }
    191 
    192  return NS_OK;
    193 }
    194 
    195 bool SVGMotionSMILType::IsEqual(const SMILValue& aLeft,
    196                                const SMILValue& aRight) const {
    197  MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types");
    198  MOZ_ASSERT(aLeft.mType == this, "Unexpected SMIL type");
    199 
    200  const MotionSegmentArray& leftArr = ExtractMotionSegmentArray(aLeft);
    201  const MotionSegmentArray& rightArr = ExtractMotionSegmentArray(aRight);
    202 
    203  // If array-lengths don't match, we're trivially non-equal.
    204  if (leftArr.Length() != rightArr.Length()) {
    205    return false;
    206  }
    207 
    208  // Array-lengths match -- check each array-entry for equality.
    209  uint32_t length = leftArr.Length();  // == rightArr->Length(), if we get here
    210  for (uint32_t i = 0; i < length; ++i) {
    211    if (leftArr[i] != rightArr[i]) {
    212      return false;
    213    }
    214  }
    215 
    216  return true;  // If we get here, we found no differences.
    217 }
    218 
    219 // Helper method for Add & CreateMatrix
    220 inline static void GetAngleAndPointAtDistance(
    221    Path* aPath, float aDistance, RotateType aRotateType,
    222    float& aRotateAngle,  // in & out-param.
    223    Point& aPoint)        // out-param.
    224 {
    225  if (aRotateType == eRotateType_Explicit) {
    226    // Leave aRotateAngle as-is.
    227    aPoint = aPath->ComputePointAtLength(aDistance);
    228  } else {
    229    Point tangent;  // Unit vector tangent to the point we find.
    230    aPoint = aPath->ComputePointAtLength(aDistance, &tangent);
    231    float tangentAngle = atan2(tangent.y, tangent.x);
    232    if (aRotateType == eRotateType_Auto) {
    233      aRotateAngle = tangentAngle;
    234    } else {
    235      MOZ_ASSERT(aRotateType == eRotateType_AutoReverse);
    236      aRotateAngle = M_PI + tangentAngle;
    237    }
    238  }
    239 }
    240 
    241 nsresult SVGMotionSMILType::Add(SMILValue& aDest, const SMILValue& aValueToAdd,
    242                                uint32_t aCount) const {
    243  MOZ_ASSERT(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types");
    244  MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type");
    245 
    246  MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest);
    247  const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd);
    248 
    249  // We're doing a simple add here (as opposed to a sandwich add below).  We
    250  // only do this when we're accumulating a repeat result.
    251  // NOTE: In other nsISMILTypes, we use this method with a barely-initialized
    252  // |aDest| value to assist with "by" animation.  (In this case,
    253  // "barely-initialized" would mean dstArr.Length() would be empty.)  However,
    254  // we don't do this for <animateMotion>, because we instead use our "by"
    255  // value to construct an equivalent "path" attribute, and we use *that* for
    256  // our actual animation.
    257  MOZ_ASSERT(srcArr.Length() == 1, "Invalid source segment arr to add");
    258  MOZ_ASSERT(dstArr.Length() == 1, "Invalid dest segment arr to add to");
    259  const MotionSegment& srcSeg = srcArr[0];
    260  const MotionSegment& dstSeg = dstArr[0];
    261  MOZ_ASSERT(srcSeg.mSegmentType == eSegmentType_PathPoint,
    262             "expecting to be adding points from a motion path");
    263  MOZ_ASSERT(dstSeg.mSegmentType == eSegmentType_PathPoint,
    264             "expecting to be adding points from a motion path");
    265 
    266  const PathPointParams& srcParams = srcSeg.mU.mPathPointParams;
    267  const PathPointParams& dstParams = dstSeg.mU.mPathPointParams;
    268 
    269  MOZ_ASSERT(srcSeg.mRotateType == dstSeg.mRotateType &&
    270                 srcSeg.mRotateAngle == dstSeg.mRotateAngle,
    271             "unexpected angle mismatch");
    272  MOZ_ASSERT(srcParams.mPath == dstParams.mPath, "unexpected path mismatch");
    273  Path* path = srcParams.mPath;
    274 
    275  // Use destination to get our rotate angle.
    276  float rotateAngle = dstSeg.mRotateAngle;
    277  Point dstPt;
    278  GetAngleAndPointAtDistance(path, dstParams.mDistToPoint, dstSeg.mRotateType,
    279                             rotateAngle, dstPt);
    280 
    281  Point srcPt = path->ComputePointAtLength(srcParams.mDistToPoint);
    282 
    283  float newX = dstPt.x + srcPt.x * aCount;
    284  float newY = dstPt.y + srcPt.y * aCount;
    285 
    286  // Replace destination's current value -- a point-on-a-path -- with the
    287  // translation that results from our addition.
    288  dstArr.ReplaceElementAt(0, MotionSegment(newX, newY, rotateAngle));
    289  return NS_OK;
    290 }
    291 
    292 nsresult SVGMotionSMILType::SandwichAdd(SMILValue& aDest,
    293                                        const SMILValue& aValueToAdd) const {
    294  MOZ_ASSERT(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types");
    295  MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type");
    296  MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest);
    297  const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd);
    298 
    299  // We're only expecting to be adding 1 segment on to the list
    300  MOZ_ASSERT(srcArr.Length() == 1,
    301             "Trying to do sandwich add of more than one value");
    302 
    303  if (!dstArr.AppendElement(srcArr[0], fallible)) {
    304    return NS_ERROR_OUT_OF_MEMORY;
    305  }
    306 
    307  return NS_OK;
    308 }
    309 
    310 nsresult SVGMotionSMILType::ComputeDistance(const SMILValue& aFrom,
    311                                            const SMILValue& aTo,
    312                                            double& aDistance) const {
    313  MOZ_ASSERT(aFrom.mType == aTo.mType, "Incompatible SMIL types");
    314  MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type");
    315  const MotionSegmentArray& fromArr = ExtractMotionSegmentArray(aFrom);
    316  const MotionSegmentArray& toArr = ExtractMotionSegmentArray(aTo);
    317 
    318  // ComputeDistance is only used for calculating distances between single
    319  // values in a values array. So we should only have one entry in each array.
    320  MOZ_ASSERT(fromArr.Length() == 1, "Wrong number of elements in from value");
    321  MOZ_ASSERT(toArr.Length() == 1, "Wrong number of elements in to value");
    322 
    323  const MotionSegment& from = fromArr[0];
    324  const MotionSegment& to = toArr[0];
    325 
    326  MOZ_ASSERT(from.mSegmentType == to.mSegmentType,
    327             "Mismatched MotionSegment types");
    328  if (from.mSegmentType == eSegmentType_PathPoint) {
    329    const PathPointParams& fromParams = from.mU.mPathPointParams;
    330    const PathPointParams& toParams = to.mU.mPathPointParams;
    331    MOZ_ASSERT(fromParams.mPath == toParams.mPath,
    332               "Interpolation endpoints should be from same path");
    333    aDistance = std::fabs(toParams.mDistToPoint - fromParams.mDistToPoint);
    334  } else {
    335    const TranslationParams& fromParams = from.mU.mTranslationParams;
    336    const TranslationParams& toParams = to.mU.mTranslationParams;
    337    float dX = toParams.mX - fromParams.mX;
    338    float dY = toParams.mY - fromParams.mY;
    339    aDistance = NS_hypot(dX, dY);
    340  }
    341 
    342  return NS_OK;
    343 }
    344 
    345 // Helper method for Interpolate()
    346 static inline float InterpolateFloat(const float& aStartFlt,
    347                                     const float& aEndFlt,
    348                                     const double& aUnitDistance) {
    349  return aStartFlt + aUnitDistance * (aEndFlt - aStartFlt);
    350 }
    351 
    352 nsresult SVGMotionSMILType::Interpolate(const SMILValue& aStartVal,
    353                                        const SMILValue& aEndVal,
    354                                        double aUnitDistance,
    355                                        SMILValue& aResult) const {
    356  MOZ_ASSERT(aStartVal.mType == aEndVal.mType,
    357             "Trying to interpolate different types");
    358  MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation");
    359  MOZ_ASSERT(aResult.mType == this, "Unexpected result type");
    360  MOZ_ASSERT(aUnitDistance >= 0.0 && aUnitDistance <= 1.0,
    361             "unit distance value out of bounds");
    362 
    363  const MotionSegmentArray& startArr = ExtractMotionSegmentArray(aStartVal);
    364  const MotionSegmentArray& endArr = ExtractMotionSegmentArray(aEndVal);
    365  MotionSegmentArray& resultArr = ExtractMotionSegmentArray(aResult);
    366 
    367  MOZ_ASSERT(endArr.Length() == 1,
    368             "Invalid end-point for animateMotion interpolation");
    369  MOZ_ASSERT(resultArr.IsEmpty(),
    370             "Expecting result to be just-initialized w/ empty array");
    371 
    372  const MotionSegment& endSeg = endArr[0];
    373  MOZ_ASSERT(endSeg.mSegmentType == eSegmentType_PathPoint,
    374             "Expecting to be interpolating along a path");
    375 
    376  const PathPointParams& endParams = endSeg.mU.mPathPointParams;
    377  // NOTE: Ususally, path & angle should match between start & end (since
    378  // presumably start & end came from the same <animateMotion> element),
    379  // unless start is empty. (as it would be for pure 'to' animation)
    380  // Notable exception: when a to-animation layers on top of lower priority
    381  // animation(s) -- see comment below.
    382  Path* path = endParams.mPath;
    383  RotateType rotateType = endSeg.mRotateType;
    384  float rotateAngle = endSeg.mRotateAngle;
    385 
    386  float startDist;
    387  if (startArr.IsEmpty() ||
    388      startArr[0].mU.mPathPointParams.mPath != endParams.mPath) {
    389    // When a to-animation follows other lower priority animation(s),
    390    // the endParams will contain a different path from the animation(s)
    391    // that it layers on top of.
    392    // Per SMIL spec, we should interpolate from the value at startArr.
    393    // However, neither Chrome nor Safari implements to-animation that way.
    394    // For simplicity, we use the same behavior as other browsers: override
    395    // previous animations and start at the initial underlying value.
    396    startDist = 0.0f;
    397  } else {
    398    MOZ_ASSERT(startArr.Length() <= 1,
    399               "Invalid start-point for animateMotion interpolation");
    400    const MotionSegment& startSeg = startArr[0];
    401    MOZ_ASSERT(startSeg.mSegmentType == eSegmentType_PathPoint,
    402               "Expecting to be interpolating along a path");
    403    const PathPointParams& startParams = startSeg.mU.mPathPointParams;
    404    MOZ_ASSERT(startSeg.mRotateType == endSeg.mRotateType &&
    405                   startSeg.mRotateAngle == endSeg.mRotateAngle,
    406               "unexpected angle mismatch");
    407    startDist = startParams.mDistToPoint;
    408  }
    409 
    410  // Get the interpolated distance along our path.
    411  float resultDist =
    412      InterpolateFloat(startDist, endParams.mDistToPoint, aUnitDistance);
    413 
    414  // Construct the intermediate result segment, and put it in our outparam.
    415  // AppendElement has guaranteed success here, since InitValue() allocates
    416  // 1 slot.
    417  MOZ_ALWAYS_TRUE(resultArr.AppendElement(
    418      MotionSegment(path, resultDist, rotateType, rotateAngle), fallible));
    419  return NS_OK;
    420 }
    421 
    422 /* static */ gfx::Matrix SVGMotionSMILType::CreateMatrix(
    423    const SMILValue& aSMILVal) {
    424  const MotionSegmentArray& arr = ExtractMotionSegmentArray(aSMILVal);
    425 
    426  gfx::Matrix matrix;
    427  uint32_t length = arr.Length();
    428  for (uint32_t i = 0; i < length; i++) {
    429    Point point;                              // initialized below
    430    float rotateAngle = arr[i].mRotateAngle;  // might get updated below
    431    if (arr[i].mSegmentType == eSegmentType_Translation) {
    432      point.x = arr[i].mU.mTranslationParams.mX;
    433      point.y = arr[i].mU.mTranslationParams.mY;
    434      MOZ_ASSERT(arr[i].mRotateType == eRotateType_Explicit,
    435                 "'auto'/'auto-reverse' should have been converted to "
    436                 "explicit angles when we generated this translation");
    437    } else {
    438      GetAngleAndPointAtDistance(arr[i].mU.mPathPointParams.mPath,
    439                                 arr[i].mU.mPathPointParams.mDistToPoint,
    440                                 arr[i].mRotateType, rotateAngle, point);
    441    }
    442    matrix.PreTranslate(point.x, point.y);
    443    matrix.PreRotate(rotateAngle);
    444  }
    445  return matrix;
    446 }
    447 
    448 /* static */
    449 SMILValue SVGMotionSMILType::ConstructSMILValue(Path* aPath, float aDist,
    450                                                RotateType aRotateType,
    451                                                float aRotateAngle) {
    452  SMILValue smilVal(&SVGMotionSMILType::sSingleton);
    453  MotionSegmentArray& arr = ExtractMotionSegmentArray(smilVal);
    454 
    455  // AppendElement has guaranteed success here, since InitValue() allocates
    456  // 1 slot.
    457  MOZ_ALWAYS_TRUE(arr.AppendElement(
    458      MotionSegment(aPath, aDist, aRotateType, aRotateAngle), fallible));
    459  return smilVal;
    460 }
    461 
    462 }  // namespace mozilla