tor-browser

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

SVGMotionSMILAnimationFunction.cpp (14592B)


      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 #include "SVGMotionSMILAnimationFunction.h"
      8 
      9 #include "SVGAnimatedOrient.h"
     10 #include "SVGMotionSMILPathUtils.h"
     11 #include "SVGMotionSMILType.h"
     12 #include "mozilla/SMILParserUtils.h"
     13 #include "mozilla/dom/SVGAnimationElement.h"
     14 #include "mozilla/dom/SVGMPathElement.h"
     15 #include "mozilla/dom/SVGPathElement.h"
     16 #include "mozilla/gfx/2D.h"
     17 #include "nsAttrValue.h"
     18 #include "nsAttrValueInlines.h"
     19 #include "nsAttrValueOrString.h"
     20 
     21 using namespace mozilla::dom;
     22 using namespace mozilla::dom::SVGAngle_Binding;
     23 using namespace mozilla::gfx;
     24 
     25 namespace mozilla {
     26 
     27 SVGMotionSMILAnimationFunction::SVGMotionSMILAnimationFunction()
     28    : mRotateType(eRotateType_Explicit),
     29      mRotateAngle(0.0f),
     30      mPathSourceType(ePathSourceType_None),
     31      mIsPathStale(true)  // Try to initialize path on first GetValues call
     32 {}
     33 
     34 void SVGMotionSMILAnimationFunction::MarkStaleIfAttributeAffectsPath(
     35    nsAtom* aAttribute) {
     36  bool isAffected;
     37  if (aAttribute == nsGkAtoms::path) {
     38    isAffected = (mPathSourceType <= ePathSourceType_PathAttr);
     39  } else if (aAttribute == nsGkAtoms::values) {
     40    isAffected = (mPathSourceType <= ePathSourceType_ValuesAttr);
     41  } else if (aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to) {
     42    isAffected = (mPathSourceType <= ePathSourceType_ToAttr);
     43  } else if (aAttribute == nsGkAtoms::by) {
     44    isAffected = (mPathSourceType <= ePathSourceType_ByAttr);
     45  } else {
     46    MOZ_ASSERT_UNREACHABLE(
     47        "Should only call this method for path-describing "
     48        "attrs");
     49    isAffected = false;
     50  }
     51 
     52  if (isAffected) {
     53    mIsPathStale = true;
     54    mHasChanged = true;
     55  }
     56 }
     57 
     58 bool SVGMotionSMILAnimationFunction::SetAttr(nsAtom* aAttribute,
     59                                             const nsAString& aValue,
     60                                             nsAttrValue& aResult,
     61                                             nsresult* aParseResult) {
     62  // Handle motion-specific attrs
     63  if (aAttribute == nsGkAtoms::keyPoints) {
     64    nsresult rv = SetKeyPoints(aValue, aResult);
     65    if (aParseResult) {
     66      *aParseResult = rv;
     67    }
     68  } else if (aAttribute == nsGkAtoms::rotate) {
     69    nsresult rv = SetRotate(aValue, aResult);
     70    if (aParseResult) {
     71      *aParseResult = rv;
     72    }
     73  } else if (aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::by ||
     74             aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to ||
     75             aAttribute == nsGkAtoms::values) {
     76    aResult.SetTo(aValue);
     77    MarkStaleIfAttributeAffectsPath(aAttribute);
     78    if (aParseResult) {
     79      *aParseResult = NS_OK;
     80    }
     81  } else {
     82    // Defer to superclass method
     83    return SMILAnimationFunction::SetAttr(aAttribute, aValue, aResult,
     84                                          aParseResult);
     85  }
     86 
     87  return true;
     88 }
     89 
     90 bool SVGMotionSMILAnimationFunction::UnsetAttr(nsAtom* aAttribute) {
     91  if (aAttribute == nsGkAtoms::keyPoints) {
     92    UnsetKeyPoints();
     93  } else if (aAttribute == nsGkAtoms::rotate) {
     94    UnsetRotate();
     95  } else if (aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::by ||
     96             aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to ||
     97             aAttribute == nsGkAtoms::values) {
     98    MarkStaleIfAttributeAffectsPath(aAttribute);
     99  } else {
    100    // Defer to superclass method
    101    return SMILAnimationFunction::UnsetAttr(aAttribute);
    102  }
    103 
    104  return true;
    105 }
    106 
    107 SMILAnimationFunction::SMILCalcMode
    108 SVGMotionSMILAnimationFunction::GetCalcMode() const {
    109  const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode);
    110  if (!value) {
    111    return CALC_PACED;  // animateMotion defaults to calcMode="paced"
    112  }
    113 
    114  return SMILCalcMode(value->GetEnumValue());
    115 }
    116 
    117 //----------------------------------------------------------------------
    118 // Helpers for GetValues
    119 
    120 /*
    121 * Returns the first <mpath> child of the given element
    122 */
    123 
    124 static SVGMPathElement* GetFirstMPathChild(nsIContent* aElem) {
    125  for (nsIContent* child = aElem->GetFirstChild(); child;
    126       child = child->GetNextSibling()) {
    127    if (child->IsSVGElement(nsGkAtoms::mpath)) {
    128      return static_cast<SVGMPathElement*>(child);
    129    }
    130  }
    131 
    132  return nullptr;
    133 }
    134 
    135 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromBasicAttrs(
    136    const nsIContent* aContextElem) {
    137  MOZ_ASSERT(!HasAttr(nsGkAtoms::path),
    138             "Should be using |path| attr if we have it");
    139  MOZ_ASSERT(!mPath, "regenerating when we already have path");
    140  MOZ_ASSERT(mPathVertices.IsEmpty(),
    141             "regenerating when we already have vertices");
    142 
    143  const auto* context = SVGElement::FromNode(aContextElem);
    144  if (!context) {
    145    NS_ERROR("Uh oh, SVG animateMotion element targeting a non-SVG node");
    146    return;
    147  }
    148  SVGMotionSMILPathUtils::PathGenerator pathGenerator(context);
    149 
    150  bool success = false;
    151  if (HasAttr(nsGkAtoms::values)) {
    152    // Generate path based on our values array
    153    mPathSourceType = ePathSourceType_ValuesAttr;
    154    nsAttrValueOrString valuesVal(GetAttr(nsGkAtoms::values));
    155    SVGMotionSMILPathUtils::MotionValueParser parser(&pathGenerator,
    156                                                     &mPathVertices);
    157    success = SMILParserUtils::ParseValuesGeneric(valuesVal.String(), parser);
    158  } else if (HasAttr(nsGkAtoms::to) || HasAttr(nsGkAtoms::by)) {
    159    // Apply 'from' value (or a dummy 0,0 'from' value)
    160    if (HasAttr(nsGkAtoms::from)) {
    161      nsAttrValueOrString fromVal(GetAttr(nsGkAtoms::from));
    162      success = pathGenerator.MoveToAbsolute(fromVal.String());
    163      if (!mPathVertices.AppendElement(0.0, fallible)) {
    164        success = false;
    165      }
    166    } else {
    167      // Create dummy 'from' value at 0,0, if we're doing by-animation.
    168      // (NOTE: We don't add the dummy 0-point to our list for *to-animation*,
    169      // because the SMILAnimationFunction logic for to-animation doesn't
    170      // expect a dummy value. It only expects one value: the final 'to' value.)
    171      pathGenerator.MoveToOrigin();
    172      success = true;
    173      if (!HasAttr(nsGkAtoms::to)) {
    174        if (!mPathVertices.AppendElement(0.0, fallible)) {
    175          success = false;
    176        }
    177      }
    178    }
    179 
    180    // Apply 'to' or 'by' value
    181    if (success) {
    182      double dist;
    183      if (HasAttr(nsGkAtoms::to)) {
    184        mPathSourceType = ePathSourceType_ToAttr;
    185        nsAttrValueOrString toVal(GetAttr(nsGkAtoms::to));
    186        success = pathGenerator.LineToAbsolute(toVal.String(), dist);
    187      } else {  // HasAttr(nsGkAtoms::by)
    188        mPathSourceType = ePathSourceType_ByAttr;
    189        nsAttrValueOrString byVal(GetAttr(nsGkAtoms::by));
    190        success = pathGenerator.LineToRelative(byVal.String(), dist);
    191      }
    192      if (success) {
    193        if (!mPathVertices.AppendElement(dist, fallible)) {
    194          success = false;
    195        }
    196      }
    197    }
    198  }
    199  if (success) {
    200    mPath = pathGenerator.GetResultingPath();
    201  } else {
    202    // Parse failure. Leave path as null, and clear path-related member data.
    203    mPathVertices.Clear();
    204  }
    205 }
    206 
    207 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromMpathElem(
    208    SVGMPathElement* aMpathElem) {
    209  mPathSourceType = ePathSourceType_Mpath;
    210 
    211  // Use the shape that's the target of our chosen <mpath> child.
    212  SVGGeometryElement* shape = aMpathElem->GetReferencedPath();
    213  if (!shape || !shape->HasValidDimensions()) {
    214    return;
    215  }
    216  if (!shape->GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices)) {
    217    mPathVertices.Clear();
    218    return;
    219  }
    220  if (mPathVertices.IsEmpty()) {
    221    return;
    222  }
    223  mPath = shape->GetOrBuildPathForMeasuring();
    224  if (!mPath) {
    225    mPathVertices.Clear();
    226    return;
    227  }
    228 }
    229 
    230 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr() {
    231  nsString pathSpec(nsAttrValueOrString(GetAttr(nsGkAtoms::path)).String());
    232  mPathSourceType = ePathSourceType_PathAttr;
    233 
    234  // Generate Path from |path| attr
    235  SVGPathData path{NS_ConvertUTF16toUTF8(pathSpec)};
    236 
    237  // We must explicitly check that the parse produces at least one path segment
    238  // (if the path data doesn't begin with a valid "M", then it's invalid).
    239  if (path.IsEmpty()) {
    240    return;
    241  }
    242 
    243  mPath = path.BuildPathForMeasuring(1.0f);
    244  bool ok = path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
    245  if (!ok || mPathVertices.IsEmpty() || !mPath) {
    246    mPath = nullptr;
    247    mPathVertices.Clear();
    248  }
    249 }
    250 
    251 // Helper to regenerate our path representation & its list of vertices
    252 void SVGMotionSMILAnimationFunction::RebuildPathAndVertices(
    253    const nsIContent* aTargetElement) {
    254  MOZ_ASSERT(mIsPathStale, "rebuilding path when it isn't stale");
    255 
    256  // Clear stale data
    257  mPath = nullptr;
    258  mPathVertices.Clear();
    259  mPathSourceType = ePathSourceType_None;
    260 
    261  // Do we have a mpath child? if so, it trumps everything. Otherwise, we look
    262  // through our list of path-defining attributes, in order of priority.
    263  SVGMPathElement* firstMpathChild = GetFirstMPathChild(mAnimationElement);
    264 
    265  if (firstMpathChild) {
    266    RebuildPathAndVerticesFromMpathElem(firstMpathChild);
    267    mValueNeedsReparsingEverySample = false;
    268  } else if (HasAttr(nsGkAtoms::path)) {
    269    RebuildPathAndVerticesFromPathAttr();
    270    mValueNeedsReparsingEverySample = false;
    271  } else {
    272    // Get path & vertices from basic SMIL attrs: from/by/to/values
    273    RebuildPathAndVerticesFromBasicAttrs(aTargetElement);
    274    mValueNeedsReparsingEverySample = true;
    275  }
    276  mIsPathStale = false;
    277 }
    278 
    279 nsresult SVGMotionSMILAnimationFunction::GenerateValuesForPathAndPoints(
    280    Path* aPath, bool aIsKeyPoints, FallibleTArray<double>& aPointDistances,
    281    SMILValueArray& aResult) {
    282  MOZ_ASSERT(aResult.IsEmpty(), "outparam is non-empty");
    283 
    284  // If we're using "keyPoints" as our list of input distances, then we need
    285  // to de-normalize from the [0, 1] scale to the [0, totalPathLen] scale.
    286  double distanceMultiplier = aIsKeyPoints ? aPath->ComputeLength() : 1.0;
    287  if (!std::isfinite(distanceMultiplier)) {
    288    return NS_ERROR_FAILURE;
    289  }
    290  const uint32_t numPoints = aPointDistances.Length();
    291  for (uint32_t i = 0; i < numPoints; ++i) {
    292    double curDist = aPointDistances[i] * distanceMultiplier;
    293    if (!std::isfinite(curDist)) {
    294      return NS_ERROR_FAILURE;
    295    }
    296    if (!aResult.AppendElement(SVGMotionSMILType::ConstructSMILValue(
    297                                   aPath, curDist, mRotateType, mRotateAngle),
    298                               fallible)) {
    299      return NS_ERROR_OUT_OF_MEMORY;
    300    }
    301  }
    302  return NS_OK;
    303 }
    304 
    305 nsresult SVGMotionSMILAnimationFunction::GetValues(const SMILAttr& aSMILAttr,
    306                                                   SMILValueArray& aResult) {
    307  if (mIsPathStale) {
    308    RebuildPathAndVertices(aSMILAttr.GetTargetNode());
    309  }
    310  MOZ_ASSERT(!mIsPathStale, "Forgot to clear 'is path stale' state");
    311 
    312  if (!mPath) {
    313    // This could be due to e.g. a parse error.
    314    MOZ_ASSERT(mPathVertices.IsEmpty(), "have vertices but no path");
    315    return NS_ERROR_FAILURE;
    316  }
    317  MOZ_ASSERT(!mPathVertices.IsEmpty(), "have a path but no vertices");
    318 
    319  // Now: Make the actual list of SMILValues (using keyPoints, if set)
    320  bool isUsingKeyPoints = !mKeyPoints.IsEmpty();
    321  return GenerateValuesForPathAndPoints(
    322      mPath, isUsingKeyPoints, isUsingKeyPoints ? mKeyPoints : mPathVertices,
    323      aResult);
    324 }
    325 
    326 void SVGMotionSMILAnimationFunction::CheckValueListDependentAttrs(
    327    uint32_t aNumValues) {
    328  // Call superclass method.
    329  SMILAnimationFunction::CheckValueListDependentAttrs(aNumValues);
    330 
    331  // Added behavior: Do checks specific to keyPoints.
    332  CheckKeyPoints();
    333 }
    334 
    335 bool SVGMotionSMILAnimationFunction::IsToAnimation() const {
    336  // Rely on inherited method, but not if we have an <mpath> child or a |path|
    337  // attribute, because they'll override any 'to' attr we might have.
    338  // NOTE: We can't rely on mPathSourceType, because it might not have been
    339  // set to a useful value yet (or it might be stale).
    340  return !GetFirstMPathChild(mAnimationElement) && !HasAttr(nsGkAtoms::path) &&
    341         SMILAnimationFunction::IsToAnimation();
    342 }
    343 
    344 void SVGMotionSMILAnimationFunction::CheckKeyPoints() {
    345  if (!HasAttr(nsGkAtoms::keyPoints)) return;
    346 
    347  // attribute is ignored for calcMode="paced" (even if it's got errors)
    348  if (GetCalcMode() == CALC_PACED) {
    349    SetKeyPointsErrorFlag(false);
    350    return;
    351  }
    352 
    353  if (mKeyPoints.Length() != mKeyTimes.Length()) {
    354    // there must be exactly as many keyPoints as keyTimes
    355    SetKeyPointsErrorFlag(true);
    356    return;
    357  }
    358 
    359  // Nothing else to check -- we can catch all keyPoints errors elsewhere.
    360  // -  Formatting & range issues will be caught in SetKeyPoints, and will
    361  //  result in an empty mKeyPoints array, which will drop us into the error
    362  //  case above.
    363  SetKeyPointsErrorFlag(false);
    364 }
    365 
    366 nsresult SVGMotionSMILAnimationFunction::SetKeyPoints(
    367    const nsAString& aKeyPoints, nsAttrValue& aResult) {
    368  mKeyPoints.Clear();
    369  aResult.SetTo(aKeyPoints);
    370 
    371  mHasChanged = true;
    372 
    373  if (!SMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyPoints, false,
    374                                                            mKeyPoints)) {
    375    mKeyPoints.Clear();
    376    return NS_ERROR_FAILURE;
    377  }
    378 
    379  return NS_OK;
    380 }
    381 
    382 void SVGMotionSMILAnimationFunction::UnsetKeyPoints() {
    383  mKeyPoints.Clear();
    384  SetKeyPointsErrorFlag(false);
    385  mHasChanged = true;
    386 }
    387 
    388 nsresult SVGMotionSMILAnimationFunction::SetRotate(const nsAString& aRotate,
    389                                                   nsAttrValue& aResult) {
    390  mHasChanged = true;
    391 
    392  aResult.SetTo(aRotate);
    393  if (aRotate.EqualsLiteral("auto")) {
    394    mRotateType = eRotateType_Auto;
    395  } else if (aRotate.EqualsLiteral("auto-reverse")) {
    396    mRotateType = eRotateType_AutoReverse;
    397  } else {
    398    mRotateType = eRotateType_Explicit;
    399 
    400    uint16_t angleUnit;
    401    if (!SVGAnimatedOrient::GetValueFromString(aRotate, mRotateAngle,
    402                                               &angleUnit)) {
    403      mRotateAngle = 0.0f;  // set default rotate angle
    404      // XXX report to console?
    405      return NS_ERROR_DOM_SYNTAX_ERR;
    406    }
    407 
    408    // Convert to radian units, if we're not already in radians.
    409    if (angleUnit != SVG_ANGLETYPE_RAD) {
    410      mRotateAngle *= SVGAnimatedOrient::GetDegreesPerUnit(angleUnit) /
    411                      SVGAnimatedOrient::GetDegreesPerUnit(SVG_ANGLETYPE_RAD);
    412    }
    413  }
    414  return NS_OK;
    415 }
    416 
    417 void SVGMotionSMILAnimationFunction::UnsetRotate() {
    418  mRotateAngle = 0.0f;  // default value
    419  mRotateType = eRotateType_Explicit;
    420  mHasChanged = true;
    421 }
    422 
    423 }  // namespace mozilla