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"
20 using namespace mozilla::dom
;
21 using namespace mozilla::dom::SVGAngle_Binding
;
22 using namespace mozilla::gfx
;
26 SVGMotionSMILAnimationFunction::SVGMotionSMILAnimationFunction()
27 : mRotateType(eRotateType_Explicit
),
29 mPathSourceType(ePathSourceType_None
),
30 mIsPathStale(true) // Try to initialize path on first GetValues call
33 void SVGMotionSMILAnimationFunction::MarkStaleIfAttributeAffectsPath(
36 if (aAttribute
== nsGkAtoms::path
) {
37 isAffected
= (mPathSourceType
<= ePathSourceType_PathAttr
);
38 } else if (aAttribute
== nsGkAtoms::values
) {
39 isAffected
= (mPathSourceType
<= ePathSourceType_ValuesAttr
);
40 } else if (aAttribute
== nsGkAtoms::from
|| aAttribute
== nsGkAtoms::to
) {
41 isAffected
= (mPathSourceType
<= ePathSourceType_ToAttr
);
42 } else if (aAttribute
== nsGkAtoms::by
) {
43 isAffected
= (mPathSourceType
<= ePathSourceType_ByAttr
);
45 MOZ_ASSERT_UNREACHABLE(
46 "Should only call this method for path-describing "
57 bool SVGMotionSMILAnimationFunction::SetAttr(nsAtom
* aAttribute
,
58 const nsAString
& aValue
,
60 nsresult
* aParseResult
) {
61 // Handle motion-specific attrs
62 if (aAttribute
== nsGkAtoms::keyPoints
) {
63 nsresult rv
= SetKeyPoints(aValue
, aResult
);
67 } else if (aAttribute
== nsGkAtoms::rotate
) {
68 nsresult rv
= SetRotate(aValue
, aResult
);
72 } else if (aAttribute
== nsGkAtoms::path
|| aAttribute
== nsGkAtoms::by
||
73 aAttribute
== nsGkAtoms::from
|| aAttribute
== nsGkAtoms::to
||
74 aAttribute
== nsGkAtoms::values
) {
75 aResult
.SetTo(aValue
);
76 MarkStaleIfAttributeAffectsPath(aAttribute
);
78 *aParseResult
= NS_OK
;
81 // Defer to superclass method
82 return SMILAnimationFunction::SetAttr(aAttribute
, aValue
, aResult
,
89 bool SVGMotionSMILAnimationFunction::UnsetAttr(nsAtom
* aAttribute
) {
90 if (aAttribute
== nsGkAtoms::keyPoints
) {
92 } else if (aAttribute
== nsGkAtoms::rotate
) {
94 } else if (aAttribute
== nsGkAtoms::path
|| aAttribute
== nsGkAtoms::by
||
95 aAttribute
== nsGkAtoms::from
|| aAttribute
== nsGkAtoms::to
||
96 aAttribute
== nsGkAtoms::values
) {
97 MarkStaleIfAttributeAffectsPath(aAttribute
);
99 // Defer to superclass method
100 return SMILAnimationFunction::UnsetAttr(aAttribute
);
106 SMILAnimationFunction::SMILCalcMode
107 SVGMotionSMILAnimationFunction::GetCalcMode() const {
108 const nsAttrValue
* value
= GetAttr(nsGkAtoms::calcMode
);
110 return CALC_PACED
; // animateMotion defaults to calcMode="paced"
113 return SMILCalcMode(value
->GetEnumValue());
116 //----------------------------------------------------------------------
117 // Helpers for GetValues
120 * Returns the first <mpath> child of the given element
123 static SVGMPathElement
* GetFirstMPathChild(nsIContent
* aElem
) {
124 for (nsIContent
* child
= aElem
->GetFirstChild(); child
;
125 child
= child
->GetNextSibling()) {
126 if (child
->IsSVGElement(nsGkAtoms::mpath
)) {
127 return static_cast<SVGMPathElement
*>(child
);
134 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromBasicAttrs(
135 const nsIContent
* aContextElem
) {
136 MOZ_ASSERT(!HasAttr(nsGkAtoms::path
),
137 "Should be using |path| attr if we have it");
138 MOZ_ASSERT(!mPath
, "regenerating when we already have path");
139 MOZ_ASSERT(mPathVertices
.IsEmpty(),
140 "regenerating when we already have vertices");
142 const auto* context
= SVGElement::FromNode(aContextElem
);
144 NS_ERROR("Uh oh, SVG animateMotion element targeting a non-SVG node");
147 SVGMotionSMILPathUtils::PathGenerator
pathGenerator(context
);
149 bool success
= false;
150 if (HasAttr(nsGkAtoms::values
)) {
151 // Generate path based on our values array
152 mPathSourceType
= ePathSourceType_ValuesAttr
;
153 const nsAString
& valuesStr
= GetAttr(nsGkAtoms::values
)->GetStringValue();
154 SVGMotionSMILPathUtils::MotionValueParser
parser(&pathGenerator
,
156 success
= SMILParserUtils::ParseValuesGeneric(valuesStr
, parser
);
157 } else if (HasAttr(nsGkAtoms::to
) || HasAttr(nsGkAtoms::by
)) {
158 // Apply 'from' value (or a dummy 0,0 'from' value)
159 if (HasAttr(nsGkAtoms::from
)) {
160 const nsAString
& fromStr
= GetAttr(nsGkAtoms::from
)->GetStringValue();
161 success
= pathGenerator
.MoveToAbsolute(fromStr
);
162 if (!mPathVertices
.AppendElement(0.0, fallible
)) {
166 // Create dummy 'from' value at 0,0, if we're doing by-animation.
167 // (NOTE: We don't add the dummy 0-point to our list for *to-animation*,
168 // because the SMILAnimationFunction logic for to-animation doesn't
169 // expect a dummy value. It only expects one value: the final 'to' value.)
170 pathGenerator
.MoveToOrigin();
172 if (!HasAttr(nsGkAtoms::to
)) {
173 if (!mPathVertices
.AppendElement(0.0, fallible
)) {
179 // Apply 'to' or 'by' value
182 if (HasAttr(nsGkAtoms::to
)) {
183 mPathSourceType
= ePathSourceType_ToAttr
;
184 const nsAString
& toStr
= GetAttr(nsGkAtoms::to
)->GetStringValue();
185 success
= pathGenerator
.LineToAbsolute(toStr
, dist
);
186 } else { // HasAttr(nsGkAtoms::by)
187 mPathSourceType
= ePathSourceType_ByAttr
;
188 const nsAString
& byStr
= GetAttr(nsGkAtoms::by
)->GetStringValue();
189 success
= pathGenerator
.LineToRelative(byStr
, dist
);
192 if (!mPathVertices
.AppendElement(dist
, fallible
)) {
199 mPath
= pathGenerator
.GetResultingPath();
201 // Parse failure. Leave path as null, and clear path-related member data.
202 mPathVertices
.Clear();
206 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromMpathElem(
207 SVGMPathElement
* aMpathElem
) {
208 mPathSourceType
= ePathSourceType_Mpath
;
210 // Use the shape that's the target of our chosen <mpath> child.
211 SVGGeometryElement
* shapeElem
= aMpathElem
->GetReferencedPath();
212 if (shapeElem
&& shapeElem
->HasValidDimensions()) {
213 bool ok
= shapeElem
->GetDistancesFromOriginToEndsOfVisibleSegments(
216 mPathVertices
.Clear();
219 if (mPathVertices
.Length()) {
220 mPath
= shapeElem
->GetOrBuildPathForMeasuring();
225 void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr() {
226 const nsAString
& pathSpec
= GetAttr(nsGkAtoms::path
)->GetStringValue();
227 mPathSourceType
= ePathSourceType_PathAttr
;
229 // Generate Path from |path| attr
230 SVGPathData path
{NS_ConvertUTF16toUTF8(pathSpec
)};
232 // We must explicitly check that the parse produces at least one path segment
233 // (if the path data doesn't begin with a valid "M", then it's invalid).
234 if (path
.IsEmpty()) {
238 mPath
= path
.BuildPathForMeasuring(1.0f
);
239 bool ok
= path
.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices
);
240 if (!ok
|| !mPathVertices
.Length()) {
242 mPathVertices
.Clear();
246 // Helper to regenerate our path representation & its list of vertices
247 void SVGMotionSMILAnimationFunction::RebuildPathAndVertices(
248 const nsIContent
* aTargetElement
) {
249 MOZ_ASSERT(mIsPathStale
, "rebuilding path when it isn't stale");
253 mPathVertices
.Clear();
254 mPathSourceType
= ePathSourceType_None
;
256 // Do we have a mpath child? if so, it trumps everything. Otherwise, we look
257 // through our list of path-defining attributes, in order of priority.
258 SVGMPathElement
* firstMpathChild
= GetFirstMPathChild(mAnimationElement
);
260 if (firstMpathChild
) {
261 RebuildPathAndVerticesFromMpathElem(firstMpathChild
);
262 mValueNeedsReparsingEverySample
= false;
263 } else if (HasAttr(nsGkAtoms::path
)) {
264 RebuildPathAndVerticesFromPathAttr();
265 mValueNeedsReparsingEverySample
= false;
267 // Get path & vertices from basic SMIL attrs: from/by/to/values
269 RebuildPathAndVerticesFromBasicAttrs(aTargetElement
);
270 mValueNeedsReparsingEverySample
= true;
272 mIsPathStale
= false;
275 bool SVGMotionSMILAnimationFunction::GenerateValuesForPathAndPoints(
276 Path
* aPath
, bool aIsKeyPoints
, FallibleTArray
<double>& aPointDistances
,
277 SMILValueArray
& aResult
) {
278 MOZ_ASSERT(aResult
.IsEmpty(), "outparam is non-empty");
280 // If we're using "keyPoints" as our list of input distances, then we need
281 // to de-normalize from the [0, 1] scale to the [0, totalPathLen] scale.
282 double distanceMultiplier
= aIsKeyPoints
? aPath
->ComputeLength() : 1.0;
283 const uint32_t numPoints
= aPointDistances
.Length();
284 for (uint32_t i
= 0; i
< numPoints
; ++i
) {
285 double curDist
= aPointDistances
[i
] * distanceMultiplier
;
286 if (!aResult
.AppendElement(SVGMotionSMILType::ConstructSMILValue(
287 aPath
, curDist
, mRotateType
, mRotateAngle
),
295 nsresult
SVGMotionSMILAnimationFunction::GetValues(const SMILAttr
& aSMILAttr
,
296 SMILValueArray
& aResult
) {
298 RebuildPathAndVertices(aSMILAttr
.GetTargetNode());
300 MOZ_ASSERT(!mIsPathStale
, "Forgot to clear 'is path stale' state");
303 // This could be due to e.g. a parse error.
304 MOZ_ASSERT(mPathVertices
.IsEmpty(), "have vertices but no path");
305 return NS_ERROR_FAILURE
;
307 MOZ_ASSERT(!mPathVertices
.IsEmpty(), "have a path but no vertices");
309 // Now: Make the actual list of SMILValues (using keyPoints, if set)
310 bool isUsingKeyPoints
= !mKeyPoints
.IsEmpty();
311 bool success
= GenerateValuesForPathAndPoints(
312 mPath
, isUsingKeyPoints
, isUsingKeyPoints
? mKeyPoints
: mPathVertices
,
315 return NS_ERROR_OUT_OF_MEMORY
;
321 void SVGMotionSMILAnimationFunction::CheckValueListDependentAttrs(
322 uint32_t aNumValues
) {
323 // Call superclass method.
324 SMILAnimationFunction::CheckValueListDependentAttrs(aNumValues
);
326 // Added behavior: Do checks specific to keyPoints.
330 bool SVGMotionSMILAnimationFunction::IsToAnimation() const {
331 // Rely on inherited method, but not if we have an <mpath> child or a |path|
332 // attribute, because they'll override any 'to' attr we might have.
333 // NOTE: We can't rely on mPathSourceType, because it might not have been
334 // set to a useful value yet (or it might be stale).
335 return !GetFirstMPathChild(mAnimationElement
) && !HasAttr(nsGkAtoms::path
) &&
336 SMILAnimationFunction::IsToAnimation();
339 void SVGMotionSMILAnimationFunction::CheckKeyPoints() {
340 if (!HasAttr(nsGkAtoms::keyPoints
)) return;
342 // attribute is ignored for calcMode="paced" (even if it's got errors)
343 if (GetCalcMode() == CALC_PACED
) {
344 SetKeyPointsErrorFlag(false);
347 if (mKeyPoints
.Length() != mKeyTimes
.Length()) {
348 // there must be exactly as many keyPoints as keyTimes
349 SetKeyPointsErrorFlag(true);
353 // Nothing else to check -- we can catch all keyPoints errors elsewhere.
354 // - Formatting & range issues will be caught in SetKeyPoints, and will
355 // result in an empty mKeyPoints array, which will drop us into the error
359 nsresult
SVGMotionSMILAnimationFunction::SetKeyPoints(
360 const nsAString
& aKeyPoints
, nsAttrValue
& aResult
) {
362 aResult
.SetTo(aKeyPoints
);
366 if (!SMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyPoints
, false,
369 return NS_ERROR_FAILURE
;
375 void SVGMotionSMILAnimationFunction::UnsetKeyPoints() {
377 SetKeyPointsErrorFlag(false);
381 nsresult
SVGMotionSMILAnimationFunction::SetRotate(const nsAString
& aRotate
,
382 nsAttrValue
& aResult
) {
385 aResult
.SetTo(aRotate
);
386 if (aRotate
.EqualsLiteral("auto")) {
387 mRotateType
= eRotateType_Auto
;
388 } else if (aRotate
.EqualsLiteral("auto-reverse")) {
389 mRotateType
= eRotateType_AutoReverse
;
391 mRotateType
= eRotateType_Explicit
;
394 if (!SVGAnimatedOrient::GetValueFromString(aRotate
, mRotateAngle
,
396 mRotateAngle
= 0.0f
; // set default rotate angle
397 // XXX report to console?
398 return NS_ERROR_DOM_SYNTAX_ERR
;
401 // Convert to radian units, if we're not already in radians.
402 if (angleUnit
!= SVG_ANGLETYPE_RAD
) {
403 mRotateAngle
*= SVGAnimatedOrient::GetDegreesPerUnit(angleUnit
) /
404 SVGAnimatedOrient::GetDegreesPerUnit(SVG_ANGLETYPE_RAD
);
410 void SVGMotionSMILAnimationFunction::UnsetRotate() {
411 mRotateAngle
= 0.0f
; // default value
412 mRotateType
= eRotateType_Explicit
;
416 } // namespace mozilla