forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			459 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			459 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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/. */
 | |
| 
 | |
| #include "SVGMotionSMILAnimationFunction.h"
 | |
| #include "mozilla/dom/SVGAnimationElement.h"
 | |
| #include "mozilla/dom/SVGPathElement.h" // for nsSVGPathList
 | |
| #include "mozilla/dom/SVGMPathElement.h"
 | |
| #include "mozilla/gfx/2D.h"
 | |
| #include "nsAttrValue.h"
 | |
| #include "nsAttrValueInlines.h"
 | |
| #include "nsSMILParserUtils.h"
 | |
| #include "nsSVGAngle.h"
 | |
| #include "nsSVGPathDataParser.h"
 | |
| #include "SVGMotionSMILType.h"
 | |
| #include "SVGMotionSMILPathUtils.h"
 | |
| 
 | |
| using namespace mozilla::dom;
 | |
| using namespace mozilla::dom::SVGAngleBinding;
 | |
| using namespace mozilla::gfx;
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| SVGMotionSMILAnimationFunction::SVGMotionSMILAnimationFunction()
 | |
|   : mRotateType(eRotateType_Explicit),
 | |
|     mRotateAngle(0.0f),
 | |
|     mPathSourceType(ePathSourceType_None),
 | |
|     mIsPathStale(true)  // Try to initialize path on first GetValues call
 | |
| {
 | |
| }
 | |
| 
 | |
| void
 | |
| SVGMotionSMILAnimationFunction::MarkStaleIfAttributeAffectsPath(nsAtom* aAttribute)
 | |
| {
 | |
|   bool isAffected;
 | |
|   if (aAttribute == nsGkAtoms::path) {
 | |
|     isAffected = (mPathSourceType <= ePathSourceType_PathAttr);
 | |
|   } else if (aAttribute == nsGkAtoms::values) {
 | |
|     isAffected = (mPathSourceType <= ePathSourceType_ValuesAttr);
 | |
|   } else if (aAttribute == nsGkAtoms::from ||
 | |
|              aAttribute == nsGkAtoms::to) {
 | |
|     isAffected = (mPathSourceType <= ePathSourceType_ToAttr);
 | |
|   } else if (aAttribute == nsGkAtoms::by) {
 | |
|     isAffected = (mPathSourceType <= ePathSourceType_ByAttr);
 | |
|   } else {
 | |
|     NS_NOTREACHED("Should only call this method for path-describing attrs");
 | |
|     isAffected = false;
 | |
|   }
 | |
| 
 | |
|   if (isAffected) {
 | |
|     mIsPathStale = true;
 | |
|     mHasChanged = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool
 | |
| SVGMotionSMILAnimationFunction::SetAttr(nsAtom* aAttribute,
 | |
|                                         const nsAString& aValue,
 | |
|                                         nsAttrValue& aResult,
 | |
|                                         nsresult* aParseResult)
 | |
| {
 | |
|   // Handle motion-specific attrs
 | |
|   if (aAttribute == nsGkAtoms::keyPoints) {
 | |
|     nsresult rv = SetKeyPoints(aValue, aResult);
 | |
|     if (aParseResult) {
 | |
|       *aParseResult = rv;
 | |
|     }
 | |
|   } else if (aAttribute == nsGkAtoms::rotate) {
 | |
|     nsresult rv = SetRotate(aValue, aResult);
 | |
|     if (aParseResult) {
 | |
|       *aParseResult = rv;
 | |
|     }
 | |
|   } else if (aAttribute == nsGkAtoms::path ||
 | |
|              aAttribute == nsGkAtoms::by ||
 | |
|              aAttribute == nsGkAtoms::from ||
 | |
|              aAttribute == nsGkAtoms::to ||
 | |
|              aAttribute == nsGkAtoms::values) {
 | |
|     aResult.SetTo(aValue);
 | |
|     MarkStaleIfAttributeAffectsPath(aAttribute);
 | |
|     if (aParseResult) {
 | |
|       *aParseResult = NS_OK;
 | |
|     }
 | |
|   } else {
 | |
|     // Defer to superclass method
 | |
|     return nsSMILAnimationFunction::SetAttr(aAttribute, aValue,
 | |
|                                             aResult, aParseResult);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool
 | |
| SVGMotionSMILAnimationFunction::UnsetAttr(nsAtom* aAttribute)
 | |
| {
 | |
|   if (aAttribute == nsGkAtoms::keyPoints) {
 | |
|     UnsetKeyPoints();
 | |
|   } else if (aAttribute == nsGkAtoms::rotate) {
 | |
|     UnsetRotate();
 | |
|   } else if (aAttribute == nsGkAtoms::path ||
 | |
|              aAttribute == nsGkAtoms::by ||
 | |
|              aAttribute == nsGkAtoms::from ||
 | |
|              aAttribute == nsGkAtoms::to ||
 | |
|              aAttribute == nsGkAtoms::values) {
 | |
|     MarkStaleIfAttributeAffectsPath(aAttribute);
 | |
|   } else {
 | |
|     // Defer to superclass method
 | |
|     return nsSMILAnimationFunction::UnsetAttr(aAttribute);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| nsSMILAnimationFunction::nsSMILCalcMode
 | |
| SVGMotionSMILAnimationFunction::GetCalcMode() const
 | |
| {
 | |
|   const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode);
 | |
|   if (!value) {
 | |
|     return CALC_PACED; // animateMotion defaults to calcMode="paced"
 | |
|   }
 | |
| 
 | |
|   return nsSMILCalcMode(value->GetEnumValue());
 | |
| }
 | |
| 
 | |
| //----------------------------------------------------------------------
 | |
| // Helpers for GetValues
 | |
| 
 | |
| /*
 | |
|  * Returns the first <mpath> child of the given element
 | |
|  */
 | |
| 
 | |
| static SVGMPathElement*
 | |
| GetFirstMPathChild(nsIContent* aElem)
 | |
| {
 | |
|   for (nsIContent* child = aElem->GetFirstChild();
 | |
|        child;
 | |
|        child = child->GetNextSibling()) {
 | |
|     if (child->IsSVGElement(nsGkAtoms::mpath)) {
 | |
|       return static_cast<SVGMPathElement*>(child);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void
 | |
| SVGMotionSMILAnimationFunction::
 | |
|   RebuildPathAndVerticesFromBasicAttrs(const nsIContent* aContextElem)
 | |
| {
 | |
|   MOZ_ASSERT(!HasAttr(nsGkAtoms::path),
 | |
|              "Should be using |path| attr if we have it");
 | |
|   MOZ_ASSERT(!mPath, "regenerating when we already have path");
 | |
|   MOZ_ASSERT(mPathVertices.IsEmpty(),
 | |
|              "regenerating when we already have vertices");
 | |
| 
 | |
|   if (!aContextElem->IsSVGElement()) {
 | |
|     NS_ERROR("Uh oh, SVG animateMotion element targeting a non-SVG node");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   SVGMotionSMILPathUtils::PathGenerator
 | |
|     pathGenerator(static_cast<const nsSVGElement*>(aContextElem));
 | |
| 
 | |
|   bool success = false;
 | |
|   if (HasAttr(nsGkAtoms::values)) {
 | |
|     // Generate path based on our values array
 | |
|     mPathSourceType = ePathSourceType_ValuesAttr;
 | |
|     const nsAString& valuesStr = GetAttr(nsGkAtoms::values)->GetStringValue();
 | |
|     SVGMotionSMILPathUtils::MotionValueParser parser(&pathGenerator,
 | |
|                                                      &mPathVertices);
 | |
|     success = nsSMILParserUtils::ParseValuesGeneric(valuesStr, parser);
 | |
|   } else if (HasAttr(nsGkAtoms::to) || HasAttr(nsGkAtoms::by)) {
 | |
|     // Apply 'from' value (or a dummy 0,0 'from' value)
 | |
|     if (HasAttr(nsGkAtoms::from)) {
 | |
|       const nsAString& fromStr = GetAttr(nsGkAtoms::from)->GetStringValue();
 | |
|       success = pathGenerator.MoveToAbsolute(fromStr);
 | |
|       mPathVertices.AppendElement(0.0, fallible);
 | |
|     } else {
 | |
|       // Create dummy 'from' value at 0,0, if we're doing by-animation.
 | |
|       // (NOTE: We don't add the dummy 0-point to our list for *to-animation*,
 | |
|       // because the nsSMILAnimationFunction logic for to-animation doesn't
 | |
|       // expect a dummy value. It only expects one value: the final 'to' value.)
 | |
|       pathGenerator.MoveToOrigin();
 | |
|       if (!HasAttr(nsGkAtoms::to)) {
 | |
|         mPathVertices.AppendElement(0.0, fallible);
 | |
|       }
 | |
|       success = true;
 | |
|     }
 | |
| 
 | |
|     // Apply 'to' or 'by' value
 | |
|     if (success) {
 | |
|       double dist;
 | |
|       if (HasAttr(nsGkAtoms::to)) {
 | |
|         mPathSourceType = ePathSourceType_ToAttr;
 | |
|         const nsAString& toStr = GetAttr(nsGkAtoms::to)->GetStringValue();
 | |
|         success = pathGenerator.LineToAbsolute(toStr, dist);
 | |
|       } else { // HasAttr(nsGkAtoms::by)
 | |
|         mPathSourceType = ePathSourceType_ByAttr;
 | |
|         const nsAString& byStr = GetAttr(nsGkAtoms::by)->GetStringValue();
 | |
|         success = pathGenerator.LineToRelative(byStr, dist);
 | |
|       }
 | |
|       if (success) {
 | |
|         mPathVertices.AppendElement(dist, fallible);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (success) {
 | |
|     mPath = pathGenerator.GetResultingPath();
 | |
|   } else {
 | |
|     // Parse failure. Leave path as null, and clear path-related member data.
 | |
|     mPathVertices.Clear();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| SVGMotionSMILAnimationFunction::
 | |
|   RebuildPathAndVerticesFromMpathElem(SVGMPathElement* aMpathElem)
 | |
| {
 | |
|   mPathSourceType = ePathSourceType_Mpath;
 | |
| 
 | |
|   // Use the path that's the target of our chosen <mpath> child.
 | |
|   SVGPathElement* pathElem = aMpathElem->GetReferencedPath();
 | |
|   if (pathElem) {
 | |
|     const SVGPathData &path = pathElem->GetAnimPathSegList()->GetAnimValue();
 | |
|     // Path data must contain of at least one path segment (if the path data
 | |
|     // doesn't begin with a valid "M", then it's invalid).
 | |
|     if (path.Length()) {
 | |
|       bool ok =
 | |
|         path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
 | |
|       if (ok && mPathVertices.Length()) {
 | |
|         mPath = pathElem->GetOrBuildPathForMeasuring();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr()
 | |
| {
 | |
|   const nsAString& pathSpec = GetAttr(nsGkAtoms::path)->GetStringValue();
 | |
|   mPathSourceType = ePathSourceType_PathAttr;
 | |
| 
 | |
|   // Generate Path from |path| attr
 | |
|   SVGPathData path;
 | |
|   nsSVGPathDataParser pathParser(pathSpec, &path);
 | |
| 
 | |
|   // We ignore any failure returned from Parse() since the SVG spec says to
 | |
|   // accept all segments up to the first invalid token. Instead we must
 | |
|   // explicitly check that the parse produces at least one path segment (if
 | |
|   // the path data doesn't begin with a valid "M", then it's invalid).
 | |
|   pathParser.Parse();
 | |
|   if (!path.Length()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mPath = path.BuildPathForMeasuring();
 | |
|   bool ok = path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
 | |
|   if (!ok || !mPathVertices.Length()) {
 | |
|     mPath = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Helper to regenerate our path representation & its list of vertices
 | |
| void
 | |
| SVGMotionSMILAnimationFunction::
 | |
|   RebuildPathAndVertices(const nsIContent* aTargetElement)
 | |
| {
 | |
|   MOZ_ASSERT(mIsPathStale, "rebuilding path when it isn't stale");
 | |
| 
 | |
|   // Clear stale data
 | |
|   mPath = nullptr;
 | |
|   mPathVertices.Clear();
 | |
|   mPathSourceType = ePathSourceType_None;
 | |
| 
 | |
|   // Do we have a mpath child? if so, it trumps everything. Otherwise, we look
 | |
|   // through our list of path-defining attributes, in order of priority.
 | |
|   SVGMPathElement* firstMpathChild = GetFirstMPathChild(mAnimationElement);
 | |
| 
 | |
|   if (firstMpathChild) {
 | |
|     RebuildPathAndVerticesFromMpathElem(firstMpathChild);
 | |
|     mValueNeedsReparsingEverySample = false;
 | |
|   } else if (HasAttr(nsGkAtoms::path)) {
 | |
|     RebuildPathAndVerticesFromPathAttr();
 | |
|     mValueNeedsReparsingEverySample = false;
 | |
|   } else {
 | |
|     // Get path & vertices from basic SMIL attrs: from/by/to/values
 | |
| 
 | |
|     RebuildPathAndVerticesFromBasicAttrs(aTargetElement);
 | |
|     mValueNeedsReparsingEverySample = true;
 | |
|   }
 | |
|   mIsPathStale = false;
 | |
| }
 | |
| 
 | |
| bool
 | |
| SVGMotionSMILAnimationFunction::
 | |
|   GenerateValuesForPathAndPoints(Path* aPath,
 | |
|                                  bool aIsKeyPoints,
 | |
|                                  FallibleTArray<double>& aPointDistances,
 | |
|                                  nsSMILValueArray& aResult)
 | |
| {
 | |
|   MOZ_ASSERT(aResult.IsEmpty(), "outparam is non-empty");
 | |
| 
 | |
|   // If we're using "keyPoints" as our list of input distances, then we need
 | |
|   // to de-normalize from the [0, 1] scale to the [0, totalPathLen] scale.
 | |
|   double distanceMultiplier = aIsKeyPoints ? aPath->ComputeLength() : 1.0;
 | |
|   const uint32_t numPoints = aPointDistances.Length();
 | |
|   for (uint32_t i = 0; i < numPoints; ++i) {
 | |
|     double curDist = aPointDistances[i] * distanceMultiplier;
 | |
|     if (!aResult.AppendElement(
 | |
|           SVGMotionSMILType::ConstructSMILValue(aPath, curDist,
 | |
|                                                 mRotateType, mRotateAngle),
 | |
|           fallible)) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| SVGMotionSMILAnimationFunction::GetValues(const nsISMILAttr& aSMILAttr,
 | |
|                                           nsSMILValueArray& aResult)
 | |
| {
 | |
|   if (mIsPathStale) {
 | |
|     RebuildPathAndVertices(aSMILAttr.GetTargetNode());
 | |
|   }
 | |
|   MOZ_ASSERT(!mIsPathStale, "Forgot to clear 'is path stale' state");
 | |
| 
 | |
|   if (!mPath) {
 | |
|     // This could be due to e.g. a parse error.
 | |
|     MOZ_ASSERT(mPathVertices.IsEmpty(), "have vertices but no path");
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   MOZ_ASSERT(!mPathVertices.IsEmpty(), "have a path but no vertices");
 | |
| 
 | |
|   // Now: Make the actual list of nsSMILValues (using keyPoints, if set)
 | |
|   bool isUsingKeyPoints = !mKeyPoints.IsEmpty();
 | |
|   bool success = GenerateValuesForPathAndPoints(mPath, isUsingKeyPoints,
 | |
|                                                 isUsingKeyPoints ?
 | |
|                                                   mKeyPoints : mPathVertices,
 | |
|                                                 aResult);
 | |
|   if (!success) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void
 | |
| SVGMotionSMILAnimationFunction::
 | |
|   CheckValueListDependentAttrs(uint32_t aNumValues)
 | |
| {
 | |
|   // Call superclass method.
 | |
|   nsSMILAnimationFunction::CheckValueListDependentAttrs(aNumValues);
 | |
| 
 | |
|   // Added behavior: Do checks specific to keyPoints.
 | |
|   CheckKeyPoints();
 | |
| }
 | |
| 
 | |
| bool
 | |
| SVGMotionSMILAnimationFunction::IsToAnimation() const
 | |
| {
 | |
|   // Rely on inherited method, but not if we have an <mpath> child or a |path|
 | |
|   // attribute, because they'll override any 'to' attr we might have.
 | |
|   // NOTE: We can't rely on mPathSourceType, because it might not have been
 | |
|   // set to a useful value yet (or it might be stale).
 | |
|   return !GetFirstMPathChild(mAnimationElement) &&
 | |
|     !HasAttr(nsGkAtoms::path) &&
 | |
|     nsSMILAnimationFunction::IsToAnimation();
 | |
| }
 | |
| 
 | |
| void
 | |
| SVGMotionSMILAnimationFunction::CheckKeyPoints()
 | |
| {
 | |
|   if (!HasAttr(nsGkAtoms::keyPoints))
 | |
|     return;
 | |
| 
 | |
|   // attribute is ignored for calcMode="paced" (even if it's got errors)
 | |
|   if (GetCalcMode() == CALC_PACED) {
 | |
|     SetKeyPointsErrorFlag(false);
 | |
|   }
 | |
| 
 | |
|   if (mKeyPoints.Length() != mKeyTimes.Length()) {
 | |
|     // there must be exactly as many keyPoints as keyTimes
 | |
|     SetKeyPointsErrorFlag(true);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Nothing else to check -- we can catch all keyPoints errors elsewhere.
 | |
|   // -  Formatting & range issues will be caught in SetKeyPoints, and will
 | |
|   //  result in an empty mKeyPoints array, which will drop us into the error
 | |
|   //  case above.
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| SVGMotionSMILAnimationFunction::SetKeyPoints(const nsAString& aKeyPoints,
 | |
|                                              nsAttrValue& aResult)
 | |
| {
 | |
|   mKeyPoints.Clear();
 | |
|   aResult.SetTo(aKeyPoints);
 | |
| 
 | |
|   mHasChanged = true;
 | |
| 
 | |
|   if (!nsSMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyPoints, false,
 | |
|                                                               mKeyPoints)) {
 | |
|     mKeyPoints.Clear();
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void
 | |
| SVGMotionSMILAnimationFunction::UnsetKeyPoints()
 | |
| {
 | |
|   mKeyPoints.Clear();
 | |
|   SetKeyPointsErrorFlag(false);
 | |
|   mHasChanged = true;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| SVGMotionSMILAnimationFunction::SetRotate(const nsAString& aRotate,
 | |
|                                           nsAttrValue& aResult)
 | |
| {
 | |
|   mHasChanged = true;
 | |
| 
 | |
|   aResult.SetTo(aRotate);
 | |
|   if (aRotate.EqualsLiteral("auto")) {
 | |
|     mRotateType = eRotateType_Auto;
 | |
|   } else if (aRotate.EqualsLiteral("auto-reverse")) {
 | |
|     mRotateType = eRotateType_AutoReverse;
 | |
|   } else {
 | |
|     mRotateType = eRotateType_Explicit;
 | |
| 
 | |
|     uint16_t angleUnit;
 | |
|     if (!nsSVGAngle::GetValueFromString(aRotate, mRotateAngle, &angleUnit)) {
 | |
|       mRotateAngle = 0.0f; // set default rotate angle
 | |
|       // XXX report to console?
 | |
|       return NS_ERROR_DOM_SYNTAX_ERR;
 | |
|     }
 | |
| 
 | |
|     // Convert to radian units, if we're not already in radians.
 | |
|     if (angleUnit != SVG_ANGLETYPE_RAD) {
 | |
|       mRotateAngle *= nsSVGAngle::GetDegreesPerUnit(angleUnit) /
 | |
|         nsSVGAngle::GetDegreesPerUnit(SVG_ANGLETYPE_RAD);
 | |
|     }
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void
 | |
| SVGMotionSMILAnimationFunction::UnsetRotate()
 | |
| {
 | |
|   mRotateAngle = 0.0f; // default value
 | |
|   mRotateType = eRotateType_Explicit;
 | |
|   mHasChanged = true;
 | |
| }
 | |
| 
 | |
| } // namespace mozilla
 | 
