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