SVGMotionSMILAnimationFunction.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "SVGMotionSMILAnimationFunction.h"
  6. #include "mozilla/dom/SVGAnimationElement.h"
  7. #include "mozilla/dom/SVGPathElement.h" // for nsSVGPathList
  8. #include "mozilla/dom/SVGMPathElement.h"
  9. #include "mozilla/gfx/2D.h"
  10. #include "nsAttrValue.h"
  11. #include "nsAttrValueInlines.h"
  12. #include "nsSMILParserUtils.h"
  13. #include "nsSVGAngle.h"
  14. #include "nsSVGPathDataParser.h"
  15. #include "SVGMotionSMILType.h"
  16. #include "SVGMotionSMILPathUtils.h"
  17. using namespace mozilla::dom;
  18. using namespace mozilla::gfx;
  19. namespace mozilla {
  20. SVGMotionSMILAnimationFunction::SVGMotionSMILAnimationFunction()
  21. : mRotateType(eRotateType_Explicit),
  22. mRotateAngle(0.0f),
  23. mPathSourceType(ePathSourceType_None),
  24. mIsPathStale(true) // Try to initialize path on first GetValues call
  25. {
  26. }
  27. void
  28. SVGMotionSMILAnimationFunction::MarkStaleIfAttributeAffectsPath(nsIAtom* aAttribute)
  29. {
  30. bool isAffected;
  31. if (aAttribute == nsGkAtoms::path) {
  32. isAffected = (mPathSourceType <= ePathSourceType_PathAttr);
  33. } else if (aAttribute == nsGkAtoms::values) {
  34. isAffected = (mPathSourceType <= ePathSourceType_ValuesAttr);
  35. } else if (aAttribute == nsGkAtoms::from ||
  36. aAttribute == nsGkAtoms::to) {
  37. isAffected = (mPathSourceType <= ePathSourceType_ToAttr);
  38. } else if (aAttribute == nsGkAtoms::by) {
  39. isAffected = (mPathSourceType <= ePathSourceType_ByAttr);
  40. } else {
  41. NS_NOTREACHED("Should only call this method for path-describing attrs");
  42. isAffected = false;
  43. }
  44. if (isAffected) {
  45. mIsPathStale = true;
  46. mHasChanged = true;
  47. }
  48. }
  49. bool
  50. SVGMotionSMILAnimationFunction::SetAttr(nsIAtom* aAttribute,
  51. const nsAString& aValue,
  52. nsAttrValue& aResult,
  53. nsresult* aParseResult)
  54. {
  55. // Handle motion-specific attrs
  56. if (aAttribute == nsGkAtoms::keyPoints) {
  57. nsresult rv = SetKeyPoints(aValue, aResult);
  58. if (aParseResult) {
  59. *aParseResult = rv;
  60. }
  61. } else if (aAttribute == nsGkAtoms::rotate) {
  62. nsresult rv = SetRotate(aValue, aResult);
  63. if (aParseResult) {
  64. *aParseResult = rv;
  65. }
  66. } else if (aAttribute == nsGkAtoms::path ||
  67. aAttribute == nsGkAtoms::by ||
  68. aAttribute == nsGkAtoms::from ||
  69. aAttribute == nsGkAtoms::to ||
  70. aAttribute == nsGkAtoms::values) {
  71. aResult.SetTo(aValue);
  72. MarkStaleIfAttributeAffectsPath(aAttribute);
  73. if (aParseResult) {
  74. *aParseResult = NS_OK;
  75. }
  76. } else {
  77. // Defer to superclass method
  78. return nsSMILAnimationFunction::SetAttr(aAttribute, aValue,
  79. aResult, aParseResult);
  80. }
  81. return true;
  82. }
  83. bool
  84. SVGMotionSMILAnimationFunction::UnsetAttr(nsIAtom* aAttribute)
  85. {
  86. if (aAttribute == nsGkAtoms::keyPoints) {
  87. UnsetKeyPoints();
  88. } else if (aAttribute == nsGkAtoms::rotate) {
  89. UnsetRotate();
  90. } else if (aAttribute == nsGkAtoms::path ||
  91. aAttribute == nsGkAtoms::by ||
  92. aAttribute == nsGkAtoms::from ||
  93. aAttribute == nsGkAtoms::to ||
  94. aAttribute == nsGkAtoms::values) {
  95. MarkStaleIfAttributeAffectsPath(aAttribute);
  96. } else {
  97. // Defer to superclass method
  98. return nsSMILAnimationFunction::UnsetAttr(aAttribute);
  99. }
  100. return true;
  101. }
  102. nsSMILAnimationFunction::nsSMILCalcMode
  103. SVGMotionSMILAnimationFunction::GetCalcMode() const
  104. {
  105. const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode);
  106. if (!value) {
  107. return CALC_PACED; // animateMotion defaults to calcMode="paced"
  108. }
  109. return nsSMILCalcMode(value->GetEnumValue());
  110. }
  111. //----------------------------------------------------------------------
  112. // Helpers for GetValues
  113. /*
  114. * Returns the first <mpath> child of the given element
  115. */
  116. static SVGMPathElement*
  117. GetFirstMPathChild(nsIContent* aElem)
  118. {
  119. for (nsIContent* child = aElem->GetFirstChild();
  120. child;
  121. child = child->GetNextSibling()) {
  122. if (child->IsSVGElement(nsGkAtoms::mpath)) {
  123. return static_cast<SVGMPathElement*>(child);
  124. }
  125. }
  126. return nullptr;
  127. }
  128. void
  129. SVGMotionSMILAnimationFunction::
  130. RebuildPathAndVerticesFromBasicAttrs(const nsIContent* aContextElem)
  131. {
  132. MOZ_ASSERT(!HasAttr(nsGkAtoms::path),
  133. "Should be using |path| attr if we have it");
  134. MOZ_ASSERT(!mPath, "regenerating when we aleady have path");
  135. MOZ_ASSERT(mPathVertices.IsEmpty(),
  136. "regenerating when we already have vertices");
  137. if (!aContextElem->IsSVGElement()) {
  138. NS_ERROR("Uh oh, SVG animateMotion element targeting a non-SVG node");
  139. return;
  140. }
  141. SVGMotionSMILPathUtils::PathGenerator
  142. pathGenerator(static_cast<const nsSVGElement*>(aContextElem));
  143. bool success = false;
  144. if (HasAttr(nsGkAtoms::values)) {
  145. // Generate path based on our values array
  146. mPathSourceType = ePathSourceType_ValuesAttr;
  147. const nsAString& valuesStr = GetAttr(nsGkAtoms::values)->GetStringValue();
  148. SVGMotionSMILPathUtils::MotionValueParser parser(&pathGenerator,
  149. &mPathVertices);
  150. success = nsSMILParserUtils::ParseValuesGeneric(valuesStr, parser);
  151. } else if (HasAttr(nsGkAtoms::to) || HasAttr(nsGkAtoms::by)) {
  152. // Apply 'from' value (or a dummy 0,0 'from' value)
  153. if (HasAttr(nsGkAtoms::from)) {
  154. const nsAString& fromStr = GetAttr(nsGkAtoms::from)->GetStringValue();
  155. success = pathGenerator.MoveToAbsolute(fromStr);
  156. mPathVertices.AppendElement(0.0, fallible);
  157. } else {
  158. // Create dummy 'from' value at 0,0, if we're doing by-animation.
  159. // (NOTE: We don't add the dummy 0-point to our list for *to-animation*,
  160. // because the nsSMILAnimationFunction logic for to-animation doesn't
  161. // expect a dummy value. It only expects one value: the final 'to' value.)
  162. pathGenerator.MoveToOrigin();
  163. if (!HasAttr(nsGkAtoms::to)) {
  164. mPathVertices.AppendElement(0.0, fallible);
  165. }
  166. success = true;
  167. }
  168. // Apply 'to' or 'by' value
  169. if (success) {
  170. double dist;
  171. if (HasAttr(nsGkAtoms::to)) {
  172. mPathSourceType = ePathSourceType_ToAttr;
  173. const nsAString& toStr = GetAttr(nsGkAtoms::to)->GetStringValue();
  174. success = pathGenerator.LineToAbsolute(toStr, dist);
  175. } else { // HasAttr(nsGkAtoms::by)
  176. mPathSourceType = ePathSourceType_ByAttr;
  177. const nsAString& byStr = GetAttr(nsGkAtoms::by)->GetStringValue();
  178. success = pathGenerator.LineToRelative(byStr, dist);
  179. }
  180. if (success) {
  181. mPathVertices.AppendElement(dist, fallible);
  182. }
  183. }
  184. }
  185. if (success) {
  186. mPath = pathGenerator.GetResultingPath();
  187. } else {
  188. // Parse failure. Leave path as null, and clear path-related member data.
  189. mPathVertices.Clear();
  190. }
  191. }
  192. void
  193. SVGMotionSMILAnimationFunction::
  194. RebuildPathAndVerticesFromMpathElem(SVGMPathElement* aMpathElem)
  195. {
  196. mPathSourceType = ePathSourceType_Mpath;
  197. // Use the path that's the target of our chosen <mpath> child.
  198. SVGPathElement* pathElem = aMpathElem->GetReferencedPath();
  199. if (pathElem) {
  200. const SVGPathData &path = pathElem->GetAnimPathSegList()->GetAnimValue();
  201. // Path data must contain of at least one path segment (if the path data
  202. // doesn't begin with a valid "M", then it's invalid).
  203. if (path.Length()) {
  204. bool ok =
  205. path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
  206. if (ok && mPathVertices.Length()) {
  207. mPath = pathElem->GetOrBuildPathForMeasuring();
  208. }
  209. }
  210. }
  211. }
  212. void
  213. SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr()
  214. {
  215. const nsAString& pathSpec = GetAttr(nsGkAtoms::path)->GetStringValue();
  216. mPathSourceType = ePathSourceType_PathAttr;
  217. // Generate Path from |path| attr
  218. SVGPathData path;
  219. nsSVGPathDataParser pathParser(pathSpec, &path);
  220. // We ignore any failure returned from Parse() since the SVG spec says to
  221. // accept all segments up to the first invalid token. Instead we must
  222. // explicitly check that the parse produces at least one path segment (if
  223. // the path data doesn't begin with a valid "M", then it's invalid).
  224. pathParser.Parse();
  225. if (!path.Length()) {
  226. return;
  227. }
  228. mPath = path.BuildPathForMeasuring();
  229. bool ok = path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
  230. if (!ok || !mPathVertices.Length()) {
  231. mPath = nullptr;
  232. }
  233. }
  234. // Helper to regenerate our path representation & its list of vertices
  235. void
  236. SVGMotionSMILAnimationFunction::
  237. RebuildPathAndVertices(const nsIContent* aTargetElement)
  238. {
  239. MOZ_ASSERT(mIsPathStale, "rebuilding path when it isn't stale");
  240. // Clear stale data
  241. mPath = nullptr;
  242. mPathVertices.Clear();
  243. mPathSourceType = ePathSourceType_None;
  244. // Do we have a mpath child? if so, it trumps everything. Otherwise, we look
  245. // through our list of path-defining attributes, in order of priority.
  246. SVGMPathElement* firstMpathChild = GetFirstMPathChild(mAnimationElement);
  247. if (firstMpathChild) {
  248. RebuildPathAndVerticesFromMpathElem(firstMpathChild);
  249. mValueNeedsReparsingEverySample = false;
  250. } else if (HasAttr(nsGkAtoms::path)) {
  251. RebuildPathAndVerticesFromPathAttr();
  252. mValueNeedsReparsingEverySample = false;
  253. } else {
  254. // Get path & vertices from basic SMIL attrs: from/by/to/values
  255. RebuildPathAndVerticesFromBasicAttrs(aTargetElement);
  256. mValueNeedsReparsingEverySample = true;
  257. }
  258. mIsPathStale = false;
  259. }
  260. bool
  261. SVGMotionSMILAnimationFunction::
  262. GenerateValuesForPathAndPoints(Path* aPath,
  263. bool aIsKeyPoints,
  264. FallibleTArray<double>& aPointDistances,
  265. nsSMILValueArray& aResult)
  266. {
  267. MOZ_ASSERT(aResult.IsEmpty(), "outparam is non-empty");
  268. // If we're using "keyPoints" as our list of input distances, then we need
  269. // to de-normalize from the [0, 1] scale to the [0, totalPathLen] scale.
  270. double distanceMultiplier = aIsKeyPoints ? aPath->ComputeLength() : 1.0;
  271. const uint32_t numPoints = aPointDistances.Length();
  272. for (uint32_t i = 0; i < numPoints; ++i) {
  273. double curDist = aPointDistances[i] * distanceMultiplier;
  274. if (!aResult.AppendElement(
  275. SVGMotionSMILType::ConstructSMILValue(aPath, curDist,
  276. mRotateType, mRotateAngle),
  277. fallible)) {
  278. return false;
  279. }
  280. }
  281. return true;
  282. }
  283. nsresult
  284. SVGMotionSMILAnimationFunction::GetValues(const nsISMILAttr& aSMILAttr,
  285. nsSMILValueArray& aResult)
  286. {
  287. if (mIsPathStale) {
  288. RebuildPathAndVertices(aSMILAttr.GetTargetNode());
  289. }
  290. MOZ_ASSERT(!mIsPathStale, "Forgot to clear 'is path stale' state");
  291. if (!mPath) {
  292. // This could be due to e.g. a parse error.
  293. MOZ_ASSERT(mPathVertices.IsEmpty(), "have vertices but no path");
  294. return NS_ERROR_FAILURE;
  295. }
  296. MOZ_ASSERT(!mPathVertices.IsEmpty(), "have a path but no vertices");
  297. // Now: Make the actual list of nsSMILValues (using keyPoints, if set)
  298. bool isUsingKeyPoints = !mKeyPoints.IsEmpty();
  299. bool success = GenerateValuesForPathAndPoints(mPath, isUsingKeyPoints,
  300. isUsingKeyPoints ?
  301. mKeyPoints : mPathVertices,
  302. aResult);
  303. if (!success) {
  304. return NS_ERROR_OUT_OF_MEMORY;
  305. }
  306. return NS_OK;
  307. }
  308. void
  309. SVGMotionSMILAnimationFunction::
  310. CheckValueListDependentAttrs(uint32_t aNumValues)
  311. {
  312. // Call superclass method.
  313. nsSMILAnimationFunction::CheckValueListDependentAttrs(aNumValues);
  314. // Added behavior: Do checks specific to keyPoints.
  315. CheckKeyPoints();
  316. }
  317. bool
  318. SVGMotionSMILAnimationFunction::IsToAnimation() const
  319. {
  320. // Rely on inherited method, but not if we have an <mpath> child or a |path|
  321. // attribute, because they'll override any 'to' attr we might have.
  322. // NOTE: We can't rely on mPathSourceType, because it might not have been
  323. // set to a useful value yet (or it might be stale).
  324. return !GetFirstMPathChild(mAnimationElement) &&
  325. !HasAttr(nsGkAtoms::path) &&
  326. nsSMILAnimationFunction::IsToAnimation();
  327. }
  328. void
  329. SVGMotionSMILAnimationFunction::CheckKeyPoints()
  330. {
  331. if (!HasAttr(nsGkAtoms::keyPoints))
  332. return;
  333. // attribute is ignored for calcMode="paced" (even if it's got errors)
  334. if (GetCalcMode() == CALC_PACED) {
  335. SetKeyPointsErrorFlag(false);
  336. }
  337. if (mKeyPoints.Length() != mKeyTimes.Length()) {
  338. // there must be exactly as many keyPoints as keyTimes
  339. SetKeyPointsErrorFlag(true);
  340. return;
  341. }
  342. // Nothing else to check -- we can catch all keyPoints errors elsewhere.
  343. // - Formatting & range issues will be caught in SetKeyPoints, and will
  344. // result in an empty mKeyPoints array, which will drop us into the error
  345. // case above.
  346. }
  347. nsresult
  348. SVGMotionSMILAnimationFunction::SetKeyPoints(const nsAString& aKeyPoints,
  349. nsAttrValue& aResult)
  350. {
  351. mKeyPoints.Clear();
  352. aResult.SetTo(aKeyPoints);
  353. mHasChanged = true;
  354. if (!nsSMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyPoints, false,
  355. mKeyPoints)) {
  356. mKeyPoints.Clear();
  357. return NS_ERROR_FAILURE;
  358. }
  359. return NS_OK;
  360. }
  361. void
  362. SVGMotionSMILAnimationFunction::UnsetKeyPoints()
  363. {
  364. mKeyPoints.Clear();
  365. SetKeyPointsErrorFlag(false);
  366. mHasChanged = true;
  367. }
  368. nsresult
  369. SVGMotionSMILAnimationFunction::SetRotate(const nsAString& aRotate,
  370. nsAttrValue& aResult)
  371. {
  372. mHasChanged = true;
  373. aResult.SetTo(aRotate);
  374. if (aRotate.EqualsLiteral("auto")) {
  375. mRotateType = eRotateType_Auto;
  376. } else if (aRotate.EqualsLiteral("auto-reverse")) {
  377. mRotateType = eRotateType_AutoReverse;
  378. } else {
  379. mRotateType = eRotateType_Explicit;
  380. // Parse numeric angle string, with the help of a temp nsSVGAngle.
  381. nsSVGAngle svgAngle;
  382. svgAngle.Init();
  383. nsresult rv = svgAngle.SetBaseValueString(aRotate, nullptr, false);
  384. if (NS_FAILED(rv)) { // Parse error
  385. mRotateAngle = 0.0f; // set default rotate angle
  386. // XXX report to console?
  387. return rv;
  388. }
  389. mRotateAngle = svgAngle.GetBaseValInSpecifiedUnits();
  390. // Convert to radian units, if we're not already in radians.
  391. uint8_t angleUnit = svgAngle.GetBaseValueUnit();
  392. if (angleUnit != SVG_ANGLETYPE_RAD) {
  393. mRotateAngle *= nsSVGAngle::GetDegreesPerUnit(angleUnit) /
  394. nsSVGAngle::GetDegreesPerUnit(SVG_ANGLETYPE_RAD);
  395. }
  396. }
  397. return NS_OK;
  398. }
  399. void
  400. SVGMotionSMILAnimationFunction::UnsetRotate()
  401. {
  402. mRotateAngle = 0.0f; // default value
  403. mRotateType = eRotateType_Explicit;
  404. mHasChanged = true;
  405. }
  406. } // namespace mozilla