LCOV - code coverage report
Current view: top level - dom/svg - SVGMotionSMILAnimationFunction.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 216 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 19 0.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13