123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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
|