summaryrefslogtreecommitdiff
path: root/dom/svg/SVGMotionSMILAnimationFunction.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/SVGMotionSMILAnimationFunction.cpp')
-rw-r--r--dom/svg/SVGMotionSMILAnimationFunction.cpp464
1 files changed, 464 insertions, 0 deletions
diff --git a/dom/svg/SVGMotionSMILAnimationFunction.cpp b/dom/svg/SVGMotionSMILAnimationFunction.cpp
new file mode 100644
index 0000000000..972d1b6270
--- /dev/null
+++ b/dom/svg/SVGMotionSMILAnimationFunction.cpp
@@ -0,0 +1,464 @@
+/* -*- 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::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(nsIAtom* 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(nsIAtom* 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(nsIAtom* 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 aleady 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;
+
+ // Parse numeric angle string, with the help of a temp nsSVGAngle.
+ nsSVGAngle svgAngle;
+ svgAngle.Init();
+ nsresult rv = svgAngle.SetBaseValueString(aRotate, nullptr, false);
+ if (NS_FAILED(rv)) { // Parse error
+ mRotateAngle = 0.0f; // set default rotate angle
+ // XXX report to console?
+ return rv;
+ }
+
+ mRotateAngle = svgAngle.GetBaseValInSpecifiedUnits();
+
+ // Convert to radian units, if we're not already in radians.
+ uint8_t angleUnit = svgAngle.GetBaseValueUnit();
+ 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