Bug 1889091 - Part 6: Remove "scratch" register parameter from emitPushArguments...
[gecko.git] / dom / svg / SVGMotionSMILAnimationFunction.cpp
blobd950617f0f7e145c6d04eccf907d346355eb1ce3
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/. */
7 #include "SVGMotionSMILAnimationFunction.h"
9 #include "mozilla/dom/SVGAnimationElement.h"
10 #include "mozilla/dom/SVGPathElement.h"
11 #include "mozilla/dom/SVGMPathElement.h"
12 #include "mozilla/gfx/2D.h"
13 #include "mozilla/SMILParserUtils.h"
14 #include "nsAttrValue.h"
15 #include "nsAttrValueInlines.h"
16 #include "SVGAnimatedOrient.h"
17 #include "SVGMotionSMILPathUtils.h"
18 #include "SVGMotionSMILType.h"
19 #include "SVGPathDataParser.h"
21 using namespace mozilla::dom;
22 using namespace mozilla::dom::SVGAngle_Binding;
23 using namespace mozilla::gfx;
25 namespace mozilla {
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
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;
52 if (isAffected) {
53 mIsPathStale = true;
54 mHasChanged = true;
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;
68 } else if (aAttribute == nsGkAtoms::rotate) {
69 nsresult rv = SetRotate(aValue, aResult);
70 if (aParseResult) {
71 *aParseResult = rv;
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;
81 } else {
82 // Defer to superclass method
83 return SMILAnimationFunction::SetAttr(aAttribute, aValue, aResult,
84 aParseResult);
87 return true;
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);
104 return true;
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"
114 return SMILCalcMode(value->GetEnumValue());
117 //----------------------------------------------------------------------
118 // Helpers for GetValues
121 * Returns the first <mpath> child of the given element
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);
132 return nullptr;
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");
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;
148 SVGMotionSMILPathUtils::PathGenerator pathGenerator(context);
150 bool success = false;
151 if (HasAttr(nsGkAtoms::values)) {
152 // Generate path based on our values array
153 mPathSourceType = ePathSourceType_ValuesAttr;
154 const nsAString& valuesStr = GetAttr(nsGkAtoms::values)->GetStringValue();
155 SVGMotionSMILPathUtils::MotionValueParser parser(&pathGenerator,
156 &mPathVertices);
157 success = SMILParserUtils::ParseValuesGeneric(valuesStr, 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 const nsAString& fromStr = GetAttr(nsGkAtoms::from)->GetStringValue();
162 success = pathGenerator.MoveToAbsolute(fromStr);
163 if (!mPathVertices.AppendElement(0.0, fallible)) {
164 success = false;
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;
180 // Apply 'to' or 'by' value
181 if (success) {
182 double dist;
183 if (HasAttr(nsGkAtoms::to)) {
184 mPathSourceType = ePathSourceType_ToAttr;
185 const nsAString& toStr = GetAttr(nsGkAtoms::to)->GetStringValue();
186 success = pathGenerator.LineToAbsolute(toStr, dist);
187 } else { // HasAttr(nsGkAtoms::by)
188 mPathSourceType = ePathSourceType_ByAttr;
189 const nsAString& byStr = GetAttr(nsGkAtoms::by)->GetStringValue();
190 success = pathGenerator.LineToRelative(byStr, dist);
192 if (success) {
193 if (!mPathVertices.AppendElement(dist, fallible)) {
194 success = false;
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();
207 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromMpathElem(
208 SVGMPathElement* aMpathElem) {
209 mPathSourceType = ePathSourceType_Mpath;
211 // Use the shape that's the target of our chosen <mpath> child.
212 SVGGeometryElement* shapeElem = aMpathElem->GetReferencedPath();
213 if (shapeElem && shapeElem->HasValidDimensions()) {
214 bool ok = shapeElem->GetDistancesFromOriginToEndsOfVisibleSegments(
215 &mPathVertices);
216 if (ok && mPathVertices.Length()) {
217 mPath = shapeElem->GetOrBuildPathForMeasuring();
222 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr() {
223 const nsAString& pathSpec = GetAttr(nsGkAtoms::path)->GetStringValue();
224 mPathSourceType = ePathSourceType_PathAttr;
226 // Generate Path from |path| attr
227 SVGPathData path;
228 SVGPathDataParser pathParser(pathSpec, &path);
230 // We ignore any failure returned from Parse() since the SVG spec says to
231 // accept all segments up to the first invalid token. Instead we must
232 // explicitly check that the parse produces at least one path segment (if
233 // the path data doesn't begin with a valid "M", then it's invalid).
234 pathParser.Parse();
235 if (!path.Length()) {
236 return;
239 mPath = path.BuildPathForMeasuring();
240 bool ok = path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
241 if (!ok || !mPathVertices.Length()) {
242 mPath = nullptr;
243 mPathVertices.Clear();
247 // Helper to regenerate our path representation & its list of vertices
248 void SVGMotionSMILAnimationFunction::RebuildPathAndVertices(
249 const nsIContent* aTargetElement) {
250 MOZ_ASSERT(mIsPathStale, "rebuilding path when it isn't stale");
252 // Clear stale data
253 mPath = nullptr;
254 mPathVertices.Clear();
255 mPathSourceType = ePathSourceType_None;
257 // Do we have a mpath child? if so, it trumps everything. Otherwise, we look
258 // through our list of path-defining attributes, in order of priority.
259 SVGMPathElement* firstMpathChild = GetFirstMPathChild(mAnimationElement);
261 if (firstMpathChild) {
262 RebuildPathAndVerticesFromMpathElem(firstMpathChild);
263 mValueNeedsReparsingEverySample = false;
264 } else if (HasAttr(nsGkAtoms::path)) {
265 RebuildPathAndVerticesFromPathAttr();
266 mValueNeedsReparsingEverySample = false;
267 } else {
268 // Get path & vertices from basic SMIL attrs: from/by/to/values
270 RebuildPathAndVerticesFromBasicAttrs(aTargetElement);
271 mValueNeedsReparsingEverySample = true;
273 mIsPathStale = false;
276 bool SVGMotionSMILAnimationFunction::GenerateValuesForPathAndPoints(
277 Path* aPath, bool aIsKeyPoints, FallibleTArray<double>& aPointDistances,
278 SMILValueArray& aResult) {
279 MOZ_ASSERT(aResult.IsEmpty(), "outparam is non-empty");
281 // If we're using "keyPoints" as our list of input distances, then we need
282 // to de-normalize from the [0, 1] scale to the [0, totalPathLen] scale.
283 double distanceMultiplier = aIsKeyPoints ? aPath->ComputeLength() : 1.0;
284 const uint32_t numPoints = aPointDistances.Length();
285 for (uint32_t i = 0; i < numPoints; ++i) {
286 double curDist = aPointDistances[i] * distanceMultiplier;
287 if (!aResult.AppendElement(SVGMotionSMILType::ConstructSMILValue(
288 aPath, curDist, mRotateType, mRotateAngle),
289 fallible)) {
290 return false;
293 return true;
296 nsresult SVGMotionSMILAnimationFunction::GetValues(const SMILAttr& aSMILAttr,
297 SMILValueArray& aResult) {
298 if (mIsPathStale) {
299 RebuildPathAndVertices(aSMILAttr.GetTargetNode());
301 MOZ_ASSERT(!mIsPathStale, "Forgot to clear 'is path stale' state");
303 if (!mPath) {
304 // This could be due to e.g. a parse error.
305 MOZ_ASSERT(mPathVertices.IsEmpty(), "have vertices but no path");
306 return NS_ERROR_FAILURE;
308 MOZ_ASSERT(!mPathVertices.IsEmpty(), "have a path but no vertices");
310 // Now: Make the actual list of SMILValues (using keyPoints, if set)
311 bool isUsingKeyPoints = !mKeyPoints.IsEmpty();
312 bool success = GenerateValuesForPathAndPoints(
313 mPath, isUsingKeyPoints, isUsingKeyPoints ? mKeyPoints : mPathVertices,
314 aResult);
315 if (!success) {
316 return NS_ERROR_OUT_OF_MEMORY;
319 return NS_OK;
322 void SVGMotionSMILAnimationFunction::CheckValueListDependentAttrs(
323 uint32_t aNumValues) {
324 // Call superclass method.
325 SMILAnimationFunction::CheckValueListDependentAttrs(aNumValues);
327 // Added behavior: Do checks specific to keyPoints.
328 CheckKeyPoints();
331 bool SVGMotionSMILAnimationFunction::IsToAnimation() const {
332 // Rely on inherited method, but not if we have an <mpath> child or a |path|
333 // attribute, because they'll override any 'to' attr we might have.
334 // NOTE: We can't rely on mPathSourceType, because it might not have been
335 // set to a useful value yet (or it might be stale).
336 return !GetFirstMPathChild(mAnimationElement) && !HasAttr(nsGkAtoms::path) &&
337 SMILAnimationFunction::IsToAnimation();
340 void SVGMotionSMILAnimationFunction::CheckKeyPoints() {
341 if (!HasAttr(nsGkAtoms::keyPoints)) return;
343 // attribute is ignored for calcMode="paced" (even if it's got errors)
344 if (GetCalcMode() == CALC_PACED) {
345 SetKeyPointsErrorFlag(false);
348 if (mKeyPoints.Length() != mKeyTimes.Length()) {
349 // there must be exactly as many keyPoints as keyTimes
350 SetKeyPointsErrorFlag(true);
351 return;
354 // Nothing else to check -- we can catch all keyPoints errors elsewhere.
355 // - Formatting & range issues will be caught in SetKeyPoints, and will
356 // result in an empty mKeyPoints array, which will drop us into the error
357 // case above.
360 nsresult SVGMotionSMILAnimationFunction::SetKeyPoints(
361 const nsAString& aKeyPoints, nsAttrValue& aResult) {
362 mKeyPoints.Clear();
363 aResult.SetTo(aKeyPoints);
365 mHasChanged = true;
367 if (!SMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyPoints, false,
368 mKeyPoints)) {
369 mKeyPoints.Clear();
370 return NS_ERROR_FAILURE;
373 return NS_OK;
376 void SVGMotionSMILAnimationFunction::UnsetKeyPoints() {
377 mKeyPoints.Clear();
378 SetKeyPointsErrorFlag(false);
379 mHasChanged = true;
382 nsresult SVGMotionSMILAnimationFunction::SetRotate(const nsAString& aRotate,
383 nsAttrValue& aResult) {
384 mHasChanged = true;
386 aResult.SetTo(aRotate);
387 if (aRotate.EqualsLiteral("auto")) {
388 mRotateType = eRotateType_Auto;
389 } else if (aRotate.EqualsLiteral("auto-reverse")) {
390 mRotateType = eRotateType_AutoReverse;
391 } else {
392 mRotateType = eRotateType_Explicit;
394 uint16_t angleUnit;
395 if (!SVGAnimatedOrient::GetValueFromString(aRotate, mRotateAngle,
396 &angleUnit)) {
397 mRotateAngle = 0.0f; // set default rotate angle
398 // XXX report to console?
399 return NS_ERROR_DOM_SYNTAX_ERR;
402 // Convert to radian units, if we're not already in radians.
403 if (angleUnit != SVG_ANGLETYPE_RAD) {
404 mRotateAngle *= SVGAnimatedOrient::GetDegreesPerUnit(angleUnit) /
405 SVGAnimatedOrient::GetDegreesPerUnit(SVG_ANGLETYPE_RAD);
408 return NS_OK;
411 void SVGMotionSMILAnimationFunction::UnsetRotate() {
412 mRotateAngle = 0.0f; // default value
413 mRotateType = eRotateType_Explicit;
414 mHasChanged = true;
417 } // namespace mozilla