Bug 436418, patch E: SVG/SMIL animateMotion - add support for <animateMotion> element and its core logic. r=roc

This commit is contained in:
Daniel Holbert 2010-04-28 16:00:54 -07:00
parent 9907d2d6ff
commit ea5825f520
20 changed files with 1676 additions and 19 deletions

View File

@ -1281,6 +1281,7 @@ GK_ATOM(accumulate, "accumulate")
GK_ATOM(additive, "additive")
GK_ATOM(attributeName, "attributeName")
GK_ATOM(attributeType, "attributeType")
GK_ATOM(auto_reverse, "auto-reverse")
GK_ATOM(begin, "begin")
GK_ATOM(by, "by")
GK_ATOM(calcMode, "calcMode")
@ -1288,6 +1289,7 @@ GK_ATOM(css, "CSS")
GK_ATOM(dur, "dur")
GK_ATOM(keySplines, "keySplines")
GK_ATOM(keyTimes, "keyTimes")
GK_ATOM(mozAnimateMotionDummyAttr, "_mozAnimateMotionDummyAttr")
GK_ATOM(repeatCount, "repeatCount")
GK_ATOM(repeatDur, "repeatDur")
GK_ATOM(restart, "restart")

View File

@ -91,6 +91,7 @@ endif # ENABLE_TESTS
EXPORTS += \
nsISMILAnimationElement.h \
nsISMILAttr.h \
nsISMILType.h \
nsSMILAnimationController.h \
nsSMILCompositorTable.h \
nsSMILCSSProperty.h \

View File

@ -43,6 +43,7 @@
class nsSMILValue;
class nsISMILType;
class nsISMILAnimationElement;
class nsIContent;
////////////////////////////////////////////////////////////////////////
// nsISMILAttr: A variable targeted by SMIL for animation and can therefore have
@ -103,10 +104,20 @@ public:
*/
virtual nsresult SetAnimValue(const nsSMILValue& aValue) = 0;
/**
* Returns the targeted content node, for any nsISMILAttr implementations
* that want to expose that to the animation logic. Otherwise, returns
* null.
*
* @return the targeted content node, if this nsISMILAttr implementation
* wishes to make it avaiable. Otherwise, nsnull.
*/
virtual const nsIContent* GetTargetNode() const { return nsnull; }
/**
* Virtual destructor, to make sure subclasses can clean themselves up.
*/
virtual ~nsISMILAttr() {};
virtual ~nsISMILAttr() {}
};
#endif // NS_ISMILATTR_H_

View File

@ -255,7 +255,7 @@ nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr,
} else if (mLastValue) {
// Sampling last value
nsSMILValue last(values[values.Length() - 1]);
const nsSMILValue& last = values[values.Length() - 1];
result = last;
// See comment in AccumulateResult: to-animation does not accumulate

View File

@ -322,8 +322,10 @@ protected:
PRBool ParseAttr(nsIAtom* aAttName, const nsISMILAttr& aSMILAttr,
nsSMILValue& aResult, PRBool& aCanCacheSoFar) const;
nsresult GetValues(const nsISMILAttr& aSMILAttr,
nsSMILValueArray& aResult);
virtual nsresult GetValues(const nsISMILAttr& aSMILAttr,
nsSMILValueArray& aResult);
void CheckKeyTimes(PRUint32 aNumValues);
void CheckKeySplines(PRUint32 aNumValues);

View File

@ -132,6 +132,10 @@ CPPSRCS += nsSVGAnimateElement.cpp \
nsSVGSetElement.cpp \
nsSVGTransformSMILType.cpp \
nsSVGTransformSMILAttr.cpp \
SVGMotionSMILType.cpp \
SVGMotionSMILAttr.cpp \
SVGMotionSMILAnimationFunction.cpp \
SVGMotionSMILPathUtils.cpp \
SVGOrientSMILType.cpp \
SVGViewBoxSMILType.cpp \
$(NULL)

View File

@ -0,0 +1,335 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SVG project.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Holbert <dholbert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "SVGMotionSMILAnimationFunction.h"
#include "nsSMILParserUtils.h"
#include "nsSVGAngle.h"
#include "SVGMotionSMILType.h"
#include "SVGMotionSMILPathUtils.h"
namespace mozilla {
SVGMotionSMILAnimationFunction::SVGMotionSMILAnimationFunction()
: mRotateType(eRotateType_Explicit),
mRotateAngle(0.0f),
mPathSourceType(ePathSourceType_None),
mIsPathStale(PR_TRUE) // Try to initialize path on first GetValues call
{
}
void
SVGMotionSMILAnimationFunction::MarkStaleIfAttributeAffectsPath(nsIAtom* aAttribute)
{
PRBool 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 = PR_FALSE;
}
if (isAffected) {
mIsPathStale = PR_TRUE;
mHasChanged = PR_TRUE;
}
}
PRBool
SVGMotionSMILAnimationFunction::SetAttr(nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult,
nsresult* aParseResult)
{
if (aAttribute == nsGkAtoms::rotate) {
nsresult rv = SetRotate(aValue, aResult);
if (aParseResult) {
*aParseResult = rv;
}
} else if (aAttribute == nsGkAtoms::by ||
aAttribute == nsGkAtoms::from ||
aAttribute == nsGkAtoms::to ||
aAttribute == nsGkAtoms::values) {
MarkStaleIfAttributeAffectsPath(aAttribute);
} else {
// Defer to superclass method
return nsSMILAnimationFunction::SetAttr(aAttribute, aValue,
aResult, aParseResult);
}
return PR_TRUE;
}
PRBool
SVGMotionSMILAnimationFunction::UnsetAttr(nsIAtom* aAttribute)
{
if (aAttribute == nsGkAtoms::rotate) {
UnsetRotate();
} else if (aAttribute == nsGkAtoms::by ||
aAttribute == nsGkAtoms::from ||
aAttribute == nsGkAtoms::to ||
aAttribute == nsGkAtoms::values) {
MarkStaleIfAttributeAffectsPath(aAttribute);
} else {
// Defer to superclass method
return nsSMILAnimationFunction::UnsetAttr(aAttribute);
}
return PR_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
void
SVGMotionSMILAnimationFunction::
RebuildPathAndVerticesFromBasicAttrs(const nsIContent* aContextElem)
{
NS_ABORT_IF_FALSE(!mPath, "regenerating when we aleady have path");
NS_ABORT_IF_FALSE(mPathVertices.IsEmpty(),
"regenerating when we already have vertices");
if (aContextElem->GetNameSpaceID() != kNameSpaceID_SVG) {
NS_ERROR("Uh oh, SVG animateMotion element targeting a non-SVG node");
return;
}
// NOTE: We have to cast away constness on context node, since the
// nsSVGLength2 methods that need it for unit-conversion
// (e.g. nsSVGLength2::GetBaseValue) aren't const-correct.
nsSVGElement* svgCtx =
static_cast<nsSVGElement*>(const_cast<nsIContent*>(aContextElem));
SVGMotionSMILPathUtils::PathGenerator pathGenerator(svgCtx);
PRBool success = PR_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 =
NS_SUCCEEDED(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);
} 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);
}
success = PR_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);
}
}
}
if (success) {
mPath = pathGenerator.GetResultingPath();
} else {
// Parse failure. Leave path as null, and clear path-related member data.
mPathVertices.Clear();
}
}
// Helper to regenerate our path representation & its list of vertices
void
SVGMotionSMILAnimationFunction::
RebuildPathAndVertices(const nsIContent* aTargetElement)
{
NS_ABORT_IF_FALSE(mIsPathStale, "rebuilding path when it isn't stale");
// Clear stale data
mPath = nsnull;
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.
// XXXdholbert This is where we should check for mpath, in <mpath> patch.
// ...else, if no mpath child:
// XXXdholbert This is where we should check for path attr, in |path| patch.
// ...else {
{
// Get path & vertices from basic SMIL attrs: from/by/to/values
RebuildPathAndVerticesFromBasicAttrs(aTargetElement);
mValueNeedsReparsingEverySample = PR_TRUE;
}
mIsPathStale = PR_FALSE;
}
PRBool
SVGMotionSMILAnimationFunction::
GenerateValuesForPathAndPoints(gfxFlattenedPath* aPath,
nsTArray<double>& aPointDistances,
nsTArray<nsSMILValue>& aResult)
{
NS_ABORT_IF_FALSE(aResult.IsEmpty(), "outparam is non-empty");
const PRUint32 numPoints = aPointDistances.Length();
for (PRUint32 i = 0; i < numPoints; ++i) {
double curDist = aPointDistances[i];
if (!aResult.AppendElement(
SVGMotionSMILType::ConstructSMILValue(aPath, curDist,
mRotateType, mRotateAngle))) {
return PR_FALSE;
}
}
return PR_TRUE;
}
nsresult
SVGMotionSMILAnimationFunction::GetValues(const nsISMILAttr& aSMILAttr,
nsSMILValueArray& aResult)
{
if (mIsPathStale) {
RebuildPathAndVertices(aSMILAttr.GetTargetNode());
}
NS_ABORT_IF_FALSE(!mIsPathStale, "Forgot to clear 'is path stale' state");
if (!mPath) {
// This could be due to e.g. a parse error.
NS_ABORT_IF_FALSE(mPathVertices.IsEmpty(), "have vertices but no path");
return NS_ERROR_FAILURE;
}
NS_ABORT_IF_FALSE(!mPathVertices.IsEmpty(), "have a path but no vertices");
// Now: Make the actual list of nsSMILValues
PRBool success = GenerateValuesForPathAndPoints(mPath, mPathVertices,
aResult);
if (!success) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
nsresult
SVGMotionSMILAnimationFunction::SetRotate(const nsAString& aRotate,
nsAttrValue& aResult)
{
mHasChanged = PR_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, nsnull, PR_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.
PRUint8 angleUnit = svgAngle.GetBaseValueUnit();
if (angleUnit != nsIDOMSVGAngle::SVG_ANGLETYPE_RAD) {
mRotateAngle *= nsSVGAngle::GetDegreesPerUnit(angleUnit) /
nsSVGAngle::GetDegreesPerUnit(nsIDOMSVGAngle::SVG_ANGLETYPE_RAD);
}
}
return NS_OK;
}
void
SVGMotionSMILAnimationFunction::UnsetRotate()
{
mRotateAngle = 0.0f; // default value
mRotateType = eRotateType_Explicit;
mHasChanged = PR_TRUE;
}
PRBool
SVGMotionSMILAnimationFunction::TreatSingleValueAsStatic() const
{
// <animateMotion> has two more ways that we could be just sampling a single
// value -- via path attribute and the <mpath> element, with a path
// description that just includes a single "move" command.
return (mPathSourceType == ePathSourceType_ValuesAttr ||
mPathSourceType == ePathSourceType_PathAttr ||
mPathSourceType == ePathSourceType_Mpath);
}
} // namespace mozilla

View File

@ -0,0 +1,106 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SVG project.
*
* The Initial Developer of the Original Code is the Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Holbert <dholbert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef MOZILLA_SVGMOTIONSMILANIMATIONFUNCTION_H_
#define MOZILLA_SVGMOTIONSMILANIMATIONFUNCTION_H_
#include "nsSMILAnimationFunction.h"
#include "SVGMotionSMILType.h" // for RotateType
#include "gfxPath.h" // for gfxFlattenedPath
namespace mozilla {
//----------------------------------------------------------------------
// SVGMotionSMILAnimationFunction
//
// Subclass of nsSMILAnimationFunction to support a few extra features offered
// by the <animateMotion> element.
//
class SVGMotionSMILAnimationFunction : public nsSMILAnimationFunction
{
public:
SVGMotionSMILAnimationFunction();
NS_OVERRIDE virtual PRBool SetAttr(nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult,
nsresult* aParseResult = nsnull);
NS_OVERRIDE virtual PRBool UnsetAttr(nsIAtom* aAttribute);
protected:
enum PathSourceType {
// NOTE: Ordering matters here. Higher-priority path-descriptors should
// have higher enumerated values
ePathSourceType_None, // uninitialized or not applicable
ePathSourceType_ByAttr, // by or from-by animation
ePathSourceType_ToAttr, // to or from-to animation
ePathSourceType_ValuesAttr,
ePathSourceType_PathAttr,
ePathSourceType_Mpath
};
NS_OVERRIDE virtual nsSMILCalcMode GetCalcMode() const;
NS_OVERRIDE virtual nsresult GetValues(const nsISMILAttr& aSMILAttr,
nsSMILValueArray& aResult);
NS_OVERRIDE virtual PRBool TreatSingleValueAsStatic() const;
nsresult SetRotate(const nsAString& aRotate, nsAttrValue& aResult);
void UnsetRotate();
// Helpers for GetValues
void MarkStaleIfAttributeAffectsPath(nsIAtom* aAttribute);
nsresult SetPathVerticesFromPathString(const nsAString& aPathSpec);
void RebuildPathAndVertices(const nsIContent* aContextElem);
void RebuildPathAndVerticesFromBasicAttrs(const nsIContent* aContextElem);
PRBool GenerateValuesForPathAndPoints(gfxFlattenedPath* aPath,
nsTArray<double>& aPointDistances,
nsTArray<nsSMILValue>& aResult);
// Members
// -------
RotateType mRotateType; // auto, auto-reverse, or explicit.
float mRotateAngle; // the angle value, if explicit.
PathSourceType mPathSourceType; // source of our gfxFlattenedPath.
nsRefPtr<gfxFlattenedPath> mPath; // representation of motion path.
nsTArray<double> mPathVertices; // distances of vertices along path.
PRPackedBool mIsPathStale;
};
} // namespace mozilla
#endif // MOZILLA_SVGMOTIONSMILANIMATIONFUNCTION_H_

View File

@ -0,0 +1,90 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SVG project.
*
* The Initial Developer of the Original Code is the Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Holbert <dholbert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* representation of a dummy attribute targeted by <animateMotion> element */
#include "SVGMotionSMILAttr.h"
#include "SVGMotionSMILType.h"
#include "nsISMILAnimationElement.h"
#include "nsSMILValue.h"
#include "nsAttrValue.h"
#include "nsGkAtoms.h"
#include "nsDebug.h"
#include "nsCRT.h"
#include "nsSVGLength2.h"
#include "nsSMILParserUtils.h"
namespace mozilla {
nsresult
SVGMotionSMILAttr::ValueFromString(const nsAString& aStr,
const nsISMILAnimationElement* aSrcElement,
nsSMILValue& aValue,
PRBool& aCanCache) const
{
NS_NOTREACHED("Shouldn't using nsISMILAttr::ValueFromString for parsing "
"animateMotion's SMIL values.");
return NS_ERROR_FAILURE;
}
nsSMILValue
SVGMotionSMILAttr::GetBaseValue() const
{
return nsSMILValue(&SVGMotionSMILType::sSingleton);
}
void
SVGMotionSMILAttr::ClearAnimValue()
{
mSVGElement->SetAnimateMotionTransform(nsnull);
}
nsresult
SVGMotionSMILAttr::SetAnimValue(const nsSMILValue& aValue)
{
gfxMatrix matrix = SVGMotionSMILType::CreateMatrix(aValue);
mSVGElement->SetAnimateMotionTransform(&matrix);
return NS_OK;
}
const nsIContent*
SVGMotionSMILAttr::GetTargetNode() const
{
return mSVGElement;
}
} // namespace mozilla

View File

@ -0,0 +1,80 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SVG project.
*
* The Initial Developer of the Original Code is the Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Holbert <dholbert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* representation of a dummy attribute targeted by <animateMotion> element */
#ifndef MOZILLA_SVGMOTIONSMILATTR_H_
#define MOZILLA_SVGMOTIONSMILATTR_H_
#include "nsISMILAttr.h"
class nsSVGElement;
namespace mozilla {
/**
* SVGMotionSMILAttr: Implements the nsISMILAttr interface for SMIL animations
* from <animateMotion>.
*
* NOTE: Even though there's technically no "motion" attribute, we behave in
* many ways as if there were, for simplicity.
*/
class SVGMotionSMILAttr : public nsISMILAttr
{
public:
SVGMotionSMILAttr(nsSVGElement* aSVGElement)
: mSVGElement(aSVGElement) {}
// nsISMILAttr methods
virtual nsresult ValueFromString(const nsAString& aStr,
const nsISMILAnimationElement* aSrcElement,
nsSMILValue& aValue,
PRBool& aCanCache) const;
virtual nsSMILValue GetBaseValue() const;
virtual nsresult SetAnimValue(const nsSMILValue& aValue);
virtual void ClearAnimValue();
virtual const nsIContent* GetTargetNode() const;
protected:
// Raw pointers are OK here because this SVGMotionSMILAttr is both
// created & destroyed during a SMIL sample-step, during which time the DOM
// isn't modified.
nsSVGElement* mSVGElement;
};
} // namespace mozilla
#endif // MOZILLA_SVGMOTIONSMILATTR_H_

View File

@ -0,0 +1,201 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SVG project.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Holbert <dholbert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "SVGMotionSMILPathUtils.h"
#include "nsSVGElement.h"
#include "nsSVGLength2.h"
#include "nsContentCreatorFunctions.h" // For NS_NewSVGElement
namespace mozilla {
//----------------------------------------------------------------------
// PathGenerator methods
// For the dummy 'from' value used in pure by-animation & to-animation
void
SVGMotionSMILPathUtils::PathGenerator::
MoveToOrigin()
{
NS_ABORT_IF_FALSE(!mHaveReceivedCommands,
"Not expecting requests for mid-path MoveTo commands");
mHaveReceivedCommands = PR_TRUE;
mGfxContext.MoveTo(gfxPoint(0, 0));
}
// For 'from' and the first entry in 'values'.
PRBool
SVGMotionSMILPathUtils::PathGenerator::
MoveToAbsolute(const nsAString& aCoordPairStr)
{
NS_ABORT_IF_FALSE(!mHaveReceivedCommands,
"Not expecting requests for mid-path MoveTo commands");
mHaveReceivedCommands = PR_TRUE;
float xVal, yVal;
if (!ParseCoordinatePair(aCoordPairStr, xVal, yVal)) {
return PR_FALSE;
}
mGfxContext.MoveTo(gfxPoint(xVal, yVal));
return PR_TRUE;
}
// For 'to' and every entry in 'values' except the first.
PRBool
SVGMotionSMILPathUtils::PathGenerator::
LineToAbsolute(const nsAString& aCoordPairStr, double& aSegmentDistance)
{
mHaveReceivedCommands = PR_TRUE;
float xVal, yVal;
if (!ParseCoordinatePair(aCoordPairStr, xVal, yVal)) {
return PR_FALSE;
}
gfxPoint initialPoint = mGfxContext.CurrentPoint();
mGfxContext.LineTo(gfxPoint(xVal, yVal));
aSegmentDistance = NS_hypot(initialPoint.x - xVal, initialPoint.y -yVal);
return PR_TRUE;
}
// For 'by'.
PRBool
SVGMotionSMILPathUtils::PathGenerator::
LineToRelative(const nsAString& aCoordPairStr, double& aSegmentDistance)
{
mHaveReceivedCommands = PR_TRUE;
float xVal, yVal;
if (!ParseCoordinatePair(aCoordPairStr, xVal, yVal)) {
return PR_FALSE;
}
mGfxContext.LineTo(mGfxContext.CurrentPoint() + gfxPoint(xVal, yVal));
aSegmentDistance = NS_hypot(xVal, yVal);
return PR_TRUE;
}
already_AddRefed<gfxFlattenedPath>
SVGMotionSMILPathUtils::PathGenerator::GetResultingPath()
{
return mGfxContext.GetFlattenedPath();
}
//----------------------------------------------------------------------
// Helper / protected methods
static PRBool
ParseOneCoordinate(char** aRest, nsSVGLength2& aLengthVal)
{
aLengthVal.Init();
// Grab token, up to next delimiter
// XXXdholbert Ideally we'd like to know if the delimeter we found was a
// comma (and if so, fail if we come across any more commas before the next
// value). Current behavior will accept mutliple commas in between values,
// and that's not technically spec-correct, but it's simpler and it matches
// our behavior elsewhere where we use strtok with SVG_COMMA_WSP_DELIM.
char* token = nsCRT::strtok(*aRest, SVG_COMMA_WSP_DELIM, aRest);
if (!token) {
return PR_FALSE;
}
// Parse token into value + unit
nsresult rv = aLengthVal.SetBaseValueString(NS_ConvertASCIItoUTF16(token),
nsnull, PR_FALSE);
return NS_SUCCEEDED(rv);
}
PRBool
SVGMotionSMILPathUtils::PathGenerator::
ParseCoordinatePair(const nsAString& aCoordPairStr,
float& aXVal, float& aYVal)
{
nsSVGLength2 xLength, yLength;
char* str = ToNewCString(aCoordPairStr);
char* rest = str;
PRBool success = PR_FALSE;
if (ParseOneCoordinate(&rest, xLength) &&
ParseOneCoordinate(&rest, yLength)) {
// Check for any non-whitespace characters remaining at the end.
PRBool foundTrailingNonWhitespace = PR_FALSE;
while (*rest != '\0') {
if (!IsSVGWhitespace(*rest)) {
foundTrailingNonWhitespace = PR_TRUE;
break;
}
}
if (!foundTrailingNonWhitespace) {
success = PR_TRUE;
}
}
nsMemory::Free(str);
if (success) {
aXVal = xLength.GetBaseValue(mSVGElement);
aYVal = yLength.GetBaseValue(mSVGElement);
}
return success;
}
//----------------------------------------------------------------------
// MotionValueParser methods
nsresult
SVGMotionSMILPathUtils::MotionValueParser::
Parse(const nsAString& aValueStr)
{
PRBool success;
if (!mPathGenerator->HaveReceivedCommands()) {
// Interpret first value in "values" attribute as the path's initial MoveTo
success = mPathGenerator->MoveToAbsolute(aValueStr);
if (success) {
success = !!mPointDistances->AppendElement(0.0);
}
} else {
double dist;
success = mPathGenerator->LineToAbsolute(aValueStr, dist);
if (success) {
mDistanceSoFar += dist;
success = !!mPointDistances->AppendElement(mDistanceSoFar);
}
}
return success ? NS_OK : NS_ERROR_FAILURE;
}
} // namespace mozilla

View File

@ -0,0 +1,129 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SVG project.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Holbert <dholbert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* Helper class to help with generating anonymous path elements for
<animateMotion> elements to use. */
#ifndef MOZILLA_SVGMOTIONSMILPATHUTILS_H_
#define MOZILLA_SVGMOTIONSMILPATHUTILS_H_
#include "nsSMILParserUtils.h"
#include "nsAutoPtr.h"
#include "nsTArray.h"
#include "nsDebug.h"
#include "gfxContext.h"
#include "nsSVGUtils.h"
class nsSVGElement;
class nsIContent;
class nsIDocument;
class nsAString;
namespace mozilla {
class SVGMotionSMILPathUtils {
public:
// Class to assist in generating a gfxFlattenedPath, based on
// coordinates in the <animateMotion> from/by/to/values attributes.
class PathGenerator {
public:
PathGenerator(nsSVGElement* aSVGElement)
: mSVGElement(aSVGElement),
mGfxContext(nsSVGUtils::GetThebesComputationalSurface()),
mHaveReceivedCommands(PR_FALSE)
{}
// Methods for adding various path commands to output path.
// Note: aCoordPairStr is expected to be a whitespace and/or
// comma-separated x,y coordinate-pair -- see description of
// "the specified values for from, by, to, and values" at
// http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement
void MoveToOrigin();
PRBool MoveToAbsolute(const nsAString& aCoordPairStr);
PRBool LineToAbsolute(const nsAString& aCoordPairStr,
double& aSegmentDistance);
PRBool LineToRelative(const nsAString& aCoordPairStr,
double& aSegmentDistance);
// Accessor to let clients check if we've received any commands yet.
inline PRBool HaveReceivedCommands() { return mHaveReceivedCommands; }
// Accessor to get the finalized path
already_AddRefed<gfxFlattenedPath> GetResultingPath();
protected:
// Helper methods
PRBool ParseCoordinatePair(const nsAString& aStr,
float& aXVal, float& aYVal);
PRBool AppendCommand(const nsACString& aCommandStr,
const nsAString& aCoordPairStr);
// Member data
nsSVGElement* mSVGElement; // context for converting out of relative units
gfxContext mGfxContext;
PRPackedBool mHaveReceivedCommands;
};
// Class to assist in passing each subcomponent of a |values| attribute to
// a PathGenerator, for generating a corresponding gfxFlattenedPath.
class MotionValueParser : public nsSMILParserUtils::GenericValueParser
{
public:
MotionValueParser(PathGenerator* aPathGenerator,
nsTArray<double>* aPointDistances)
: mPathGenerator(aPathGenerator),
mPointDistances(aPointDistances),
mDistanceSoFar(0.0)
{
NS_ABORT_IF_FALSE(mPointDistances->IsEmpty(),
"expecting point distances array to start empty");
}
// nsSMILParserUtils::GenericValueParser interface
virtual nsresult Parse(const nsAString& aValueStr);
protected:
PathGenerator* mPathGenerator;
nsTArray<double>* mPointDistances;
double mDistanceSoFar;
};
};
} // namespace mozilla
#endif // MOZILLA_SVGMOTIONSMILPATHUTILS_H_

View File

@ -0,0 +1,523 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SVG project.
*
* The Initial Developer of the Original Code is the Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Holbert <dholbert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* implementation of nsISMILType for use by <animateMotion> element */
#include "SVGMotionSMILType.h"
#include "nsSMILValue.h"
#include "nsDebug.h"
#include "nsSVGTransform.h"
#include "nsSVGAngle.h"
#include "nsIDOMSVGAngle.h"
#include "nsSVGPathElement.h"
#include "nsSVGPathSeg.h"
#include "nsIDOMSVGPathSeg.h"
#include "nsIDOMSVGPathSegList.h"
#include "nsMathUtils.h"
#include <math.h>
namespace mozilla {
/*static*/ SVGMotionSMILType SVGMotionSMILType::sSingleton;
// Helper enum, for distinguishing between types of MotionSegment structs
enum SegmentType {
eSegmentType_Translation,
eSegmentType_PathPoint
};
// Helper Structs: containers for params to define our MotionSegment
// (either simple translation or point-on-a-path)
struct TranslationParams { // Simple translation
float mX;
float mY;
};
struct PathPointParams { // Point along a path
gfxFlattenedPath* mPath; // NOTE: Refcounted; need to AddRef/Release.
float mDistToPoint; // Distance from path start to the point on the path that
// we're interested in.
};
/**
* Helper Struct: MotionSegment
*
* Instances of this class represent the points that we move between during
* <animateMotion>. Each nsSMILValue will get a nsTArray of these (generally
* with at most 1 entry in the array, except for in SandwichAdd). (This
* matches our behavior in nsSVGTransformSMILType.)
*
* NOTE: In general, MotionSegments are represented as points on a path
* (eSegmentType_PathPoint), so that we can easily interpolate and compute
* distance *along their path*. However, Add() outputs MotionSegments as
* simple translations (eSegmentType_Translation), because adding two points
* from a path (e.g. when accumulating a repeated animation) will generally
* take you to an arbitrary point *off* of the path.
*/
struct MotionSegment
{
// Default constructor just locks us into being a Translation, and leaves
// other fields uninitialized (since client is presumably about to set them)
MotionSegment()
: mSegmentType(eSegmentType_Translation)
{ }
// Constructor for a translation
MotionSegment(float aX, float aY, float aRotateAngle)
: mRotateType(eRotateType_Explicit), mRotateAngle(aRotateAngle),
mSegmentType(eSegmentType_Translation)
{
mU.mTranslationParams.mX = aX;
mU.mTranslationParams.mY = aY;
}
// Constructor for a point on a path (NOTE: AddRef's)
MotionSegment(gfxFlattenedPath* aPath, float aDistToPoint,
RotateType aRotateType, float aRotateAngle)
: mRotateType(aRotateType), mRotateAngle(aRotateAngle),
mSegmentType(eSegmentType_PathPoint)
{
mU.mPathPointParams.mPath = aPath;
mU.mPathPointParams.mDistToPoint = aDistToPoint;
NS_ADDREF(mU.mPathPointParams.mPath); // Retain a reference to path
}
// Copy constructor (NOTE: AddRef's if we're eSegmentType_PathPoint)
MotionSegment(const MotionSegment& aOther)
: mRotateType(aOther.mRotateType), mRotateAngle(aOther.mRotateAngle),
mSegmentType(aOther.mSegmentType)
{
if (mSegmentType == eSegmentType_Translation) {
mU.mTranslationParams = aOther.mU.mTranslationParams;
} else { // mSegmentType == eSegmentType_PathPoint
mU.mPathPointParams = aOther.mU.mPathPointParams;
NS_ADDREF(mU.mPathPointParams.mPath); // Retain a reference to path
}
}
// Destructor (releases any reference we were holding onto)
~MotionSegment()
{
if (mSegmentType == eSegmentType_PathPoint) {
NS_RELEASE(mU.mPathPointParams.mPath);
}
}
// Comparison operators
PRBool operator==(const MotionSegment& aOther) const
{
// Compare basic params
if (mSegmentType != aOther.mSegmentType ||
mRotateType != aOther.mRotateType ||
(mRotateType == eRotateType_Explicit && // Technically, angle mismatch
mRotateAngle != aOther.mRotateAngle)) { // only matters for Explicit.
return PR_FALSE;
}
// Compare translation params, if we're a translation.
if (mSegmentType == eSegmentType_Translation) {
return mU.mTranslationParams.mX == aOther.mU.mTranslationParams.mX &&
mU.mTranslationParams.mY == aOther.mU.mTranslationParams.mY;
}
// Else, compare path-point params, if we're a path point.
return (mU.mPathPointParams.mPath == aOther.mU.mPathPointParams.mPath) &&
(mU.mPathPointParams.mDistToPoint ==
aOther.mU.mPathPointParams.mDistToPoint);
}
PRBool operator!=(const MotionSegment& aOther) const
{
return !(*this == aOther);
}
// Member Data
// -----------
RotateType mRotateType; // Explicit angle vs. auto vs. auto-reverse.
float mRotateAngle; // Only used if mRotateType == eRotateType_Explicit.
const SegmentType mSegmentType; // This determines how we interpret
// mU. (const for safety/sanity)
union { // Union to let us hold the params for either segment-type.
TranslationParams mTranslationParams;
PathPointParams mPathPointParams;
} mU;
};
typedef nsTArray<MotionSegment> MotionSegmentArray;
// Helper methods to cast nsSMILValue.mU.mPtr to the right pointer-type
static MotionSegmentArray&
ExtractMotionSegmentArray(nsSMILValue& aValue)
{
return *static_cast<MotionSegmentArray*>(aValue.mU.mPtr);
}
static const MotionSegmentArray&
ExtractMotionSegmentArray(const nsSMILValue& aValue)
{
return *static_cast<const MotionSegmentArray*>(aValue.mU.mPtr);
}
// nsISMILType Methods
// -------------------
void
SVGMotionSMILType::Init(nsSMILValue& aValue) const
{
NS_ABORT_IF_FALSE(aValue.IsNull(), "Unexpected SMIL type");
aValue.mType = this;
aValue.mU.mPtr = new MotionSegmentArray(1);
}
void
SVGMotionSMILType::Destroy(nsSMILValue& aValue) const
{
NS_ABORT_IF_FALSE(aValue.mType == this, "Unexpected SMIL type");
MotionSegmentArray* arr = static_cast<MotionSegmentArray*>(aValue.mU.mPtr);
delete arr;
aValue.mU.mPtr = nsnull;
aValue.mType = &nsSMILNullType::sSingleton;
}
nsresult
SVGMotionSMILType::Assign(nsSMILValue& aDest, const nsSMILValue& aSrc) const
{
NS_ABORT_IF_FALSE(aDest.mType == aSrc.mType, "Incompatible SMIL types");
NS_ABORT_IF_FALSE(aDest.mType == this, "Unexpected SMIL type");
const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aSrc);
MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest);
// Ensure we have sufficient memory.
if (!dstArr.SetCapacity(srcArr.Length())) {
return NS_ERROR_OUT_OF_MEMORY;
}
dstArr = srcArr; // Do the assignment.
return NS_OK;
}
PRBool
SVGMotionSMILType::IsEqual(const nsSMILValue& aLeft,
const nsSMILValue& aRight) const
{
NS_ABORT_IF_FALSE(aLeft.mType == aRight.mType, "Incompatible SMIL types");
NS_ABORT_IF_FALSE(aLeft.mType == this, "Unexpected SMIL type");
const MotionSegmentArray& leftArr = ExtractMotionSegmentArray(aLeft);
const MotionSegmentArray& rightArr = ExtractMotionSegmentArray(aRight);
// If array-lengths don't match, we're trivially non-equal.
if (leftArr.Length() != rightArr.Length()) {
return PR_FALSE;
}
// Array-lengths match -- check each array-entry for equality.
PRUint32 length = leftArr.Length(); // == rightArr->Length(), if we get here
for (PRUint32 i = 0; i < length; ++i) {
if (leftArr[i] != rightArr[i]) {
return PR_FALSE;
}
}
return PR_TRUE; // If we get here, we found no differences.
}
// Helper method for Add & CreateMatrix
inline static void
GetAngleAndPointAtDistance(gfxFlattenedPath* aPath, float aDistance,
RotateType aRotateType,
gfxFloat& aRotateAngle, // in & out-param.
gfxPoint& aPoint) // out-param.
{
gfxFloat tangentAngle;
// NOTE: "0.0" below is distance-off-the-path. (see FindPoint documentation)
aPoint = aPath->FindPoint(gfxPoint(aDistance, 0.0), &tangentAngle);
// Update aRotateAngle if it's auto/auto-reverse
switch (aRotateType) {
case eRotateType_Explicit:
// Leave aRotateAngle as-is.
break;
case eRotateType_Auto:
aRotateAngle = tangentAngle;
break;
case eRotateType_AutoReverse:
aRotateAngle = M_PI + tangentAngle;
break;
}
}
nsresult
SVGMotionSMILType::Add(nsSMILValue& aDest, const nsSMILValue& aValueToAdd,
PRUint32 aCount) const
{
NS_ABORT_IF_FALSE(aDest.mType == aValueToAdd.mType,
"Incompatible SMIL types");
NS_ABORT_IF_FALSE(aDest.mType == this, "Unexpected SMIL type");
MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest);
const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd);
// We're doing a simple add here (as opposed to a sandwich add below). We
// only do this when we're accumulating a repeat result.
// NOTE: In other nsISMILTypes, we use this method with a barely-initialized
// |aDest| value to assist with "by" animation. (In this case,
// "barely-initialized" would mean dstArr.Length() would be empty.) However,
// we don't do this for <animateMotion>, because we instead use our "by"
// value to construct an equivalent "path" attribute, and we use *that* for
// our actual animation.
NS_ABORT_IF_FALSE(srcArr.Length() == 1, "Invalid source segment arr to add");
NS_ABORT_IF_FALSE(dstArr.Length() == 1, "Invalid dest segment arr to add to");
const MotionSegment& srcSeg = srcArr[0];
const MotionSegment& dstSeg = dstArr[0];
NS_ABORT_IF_FALSE(srcSeg.mSegmentType == eSegmentType_PathPoint,
"expecting to be adding points from a motion path");
NS_ABORT_IF_FALSE(dstSeg.mSegmentType == eSegmentType_PathPoint,
"expecting to be adding points from a motion path");
const PathPointParams& srcParams = srcSeg.mU.mPathPointParams;
const PathPointParams& dstParams = dstSeg.mU.mPathPointParams;
NS_ABORT_IF_FALSE(srcSeg.mRotateType == dstSeg.mRotateType &&
srcSeg.mRotateAngle == dstSeg.mRotateAngle,
"unexpected angle mismatch");
NS_ABORT_IF_FALSE(srcParams.mPath == dstParams.mPath,
"unexpected path mismatch");
gfxFlattenedPath* path = srcParams.mPath;
// Use destination to get our rotate angle.
gfxFloat rotateAngle = dstSeg.mRotateAngle;
gfxPoint dstPt;
GetAngleAndPointAtDistance(path, dstParams.mDistToPoint, dstSeg.mRotateType,
rotateAngle, dstPt);
// NOTE: "0.0" below is distance-off-the-path. (see FindPoint documentation)
gfxPoint srcPt = path->FindPoint(gfxPoint(srcParams.mDistToPoint, 0.0));
float newX = dstPt.x + srcPt.x * aCount;
float newY = dstPt.y + srcPt.y * aCount;
// Replace destination's current value -- a point-on-a-path -- with the
// translation that results from our addition.
dstArr.Clear();
dstArr.AppendElement(MotionSegment(newX, newY, rotateAngle));
return NS_OK;
}
nsresult
SVGMotionSMILType::SandwichAdd(nsSMILValue& aDest,
const nsSMILValue& aValueToAdd) const
{
NS_ABORT_IF_FALSE(aDest.mType == aValueToAdd.mType,
"Incompatible SMIL types");
NS_ABORT_IF_FALSE(aDest.mType == this, "Unexpected SMIL type");
MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest);
const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd);
// We're only expecting to be adding 1 segment on to the list
NS_ABORT_IF_FALSE(srcArr.Length() == 1,
"Trying to do sandwich add of more than one value");
if (!dstArr.AppendElement(srcArr[0])) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
nsresult
SVGMotionSMILType::ComputeDistance(const nsSMILValue& aFrom,
const nsSMILValue& aTo,
double& aDistance) const
{
NS_ABORT_IF_FALSE(aFrom.mType == aTo.mType, "Incompatible SMIL types");
NS_ABORT_IF_FALSE(aFrom.mType == this, "Unexpected SMIL type");
const MotionSegmentArray& fromArr = ExtractMotionSegmentArray(aFrom);
const MotionSegmentArray& toArr = ExtractMotionSegmentArray(aTo);
// ComputeDistance is only used for calculating distances between single
// values in a values array. So we should only have one entry in each array.
NS_ABORT_IF_FALSE(fromArr.Length() == 1,
"Wrong number of elements in from value");
NS_ABORT_IF_FALSE(toArr.Length() == 1,
"Wrong number of elements in to value");
const MotionSegment& from = fromArr[0];
const MotionSegment& to = toArr[0];
NS_ABORT_IF_FALSE(from.mSegmentType == to.mSegmentType,
"Mismatched MotionSegment types");
if (from.mSegmentType == eSegmentType_PathPoint) {
const PathPointParams& fromParams = from.mU.mPathPointParams;
const PathPointParams& toParams = to.mU.mPathPointParams;
NS_ABORT_IF_FALSE(fromParams.mPath == toParams.mPath,
"Interpolation endpoints should be from same path");
NS_ABORT_IF_FALSE(fromParams.mDistToPoint <= toParams.mDistToPoint,
"To value shouldn't be before from value on path");
aDistance = fabs(toParams.mDistToPoint - fromParams.mDistToPoint);
} else {
const TranslationParams& fromParams = from.mU.mTranslationParams;
const TranslationParams& toParams = to.mU.mTranslationParams;
float dX = toParams.mX - fromParams.mX;
float dY = toParams.mY - fromParams.mY;
aDistance = NS_hypot(dX, dY);
}
return NS_OK;
}
// Helper method for Interpolate()
static inline float
InterpolateFloat(const float& aStartFlt, const float& aEndFlt,
const double& aUnitDistance)
{
return aStartFlt + aUnitDistance * (aEndFlt - aStartFlt);
}
nsresult
SVGMotionSMILType::Interpolate(const nsSMILValue& aStartVal,
const nsSMILValue& aEndVal,
double aUnitDistance,
nsSMILValue& aResult) const
{
NS_ABORT_IF_FALSE(aStartVal.mType == aEndVal.mType,
"Trying to interpolate different types");
NS_ABORT_IF_FALSE(aStartVal.mType == this,
"Unexpected types for interpolation");
NS_ABORT_IF_FALSE(aResult.mType == this, "Unexpected result type");
NS_ABORT_IF_FALSE(aUnitDistance >= 0.0 && aUnitDistance <= 1.0,
"unit distance value out of bounds");
const MotionSegmentArray& startArr = ExtractMotionSegmentArray(aStartVal);
const MotionSegmentArray& endArr = ExtractMotionSegmentArray(aEndVal);
MotionSegmentArray& resultArr = ExtractMotionSegmentArray(aResult);
NS_ABORT_IF_FALSE(startArr.Length() <= 1,
"Invalid start-point for animateMotion interpolation");
NS_ABORT_IF_FALSE(endArr.Length() == 1,
"Invalid end-point for animateMotion interpolation");
NS_ABORT_IF_FALSE(resultArr.IsEmpty(),
"Expecting result to be just-initialized w/ empty array");
const MotionSegment& endSeg = endArr[0];
NS_ABORT_IF_FALSE(endSeg.mSegmentType == eSegmentType_PathPoint,
"Expecting to be interpolating along a path");
const PathPointParams& endParams = endSeg.mU.mPathPointParams;
// NOTE: path & angle should match between start & end (since presumably
// start & end came from the same <animateMotion> element), unless start is
// empty. (as it would be for pure 'to' animation)
gfxFlattenedPath* path = endParams.mPath;
RotateType rotateType = endSeg.mRotateType;
float rotateAngle = endSeg.mRotateAngle;
float startDist;
if (startArr.IsEmpty()) {
startDist = 0.0f;
} else {
const MotionSegment& startSeg = startArr[0];
NS_ABORT_IF_FALSE(startSeg.mSegmentType == eSegmentType_PathPoint,
"Expecting to be interpolating along a path");
const PathPointParams& startParams = startSeg.mU.mPathPointParams;
NS_ABORT_IF_FALSE(startSeg.mRotateType == endSeg.mRotateType &&
startSeg.mRotateAngle == endSeg.mRotateAngle,
"unexpected angle mismatch");
NS_ABORT_IF_FALSE(startParams.mPath == endParams.mPath,
"unexpected path mismatch");
startDist = startParams.mDistToPoint;
}
// Get the interpolated distance along our path.
float resultDist = InterpolateFloat(startDist, endParams.mDistToPoint,
aUnitDistance);
// Construct the intermediate result segment, and put it in our outparam.
// AppendElement has guaranteed success here, since Init() allocates 1 slot.
resultArr.AppendElement(MotionSegment(path, resultDist,
rotateType, rotateAngle));
return NS_OK;
}
/* static */ gfxMatrix
SVGMotionSMILType::CreateMatrix(const nsSMILValue& aSMILVal)
{
const MotionSegmentArray& arr = ExtractMotionSegmentArray(aSMILVal);
gfxMatrix matrix;
PRUint32 length = arr.Length();
for (PRUint32 i = 0; i < length; i++) {
gfxPoint point; // initialized below
gfxFloat rotateAngle = arr[i].mRotateAngle; // might get updated below
if (arr[i].mSegmentType == eSegmentType_Translation) {
point.x = arr[i].mU.mTranslationParams.mX;
point.y = arr[i].mU.mTranslationParams.mY;
NS_ABORT_IF_FALSE(arr[i].mRotateType == eRotateType_Explicit,
"'auto'/'auto-reverse' should have been converted to "
"explicit angles when we generated this translation");
} else {
GetAngleAndPointAtDistance(arr[i].mU.mPathPointParams.mPath,
arr[i].mU.mPathPointParams.mDistToPoint,
arr[i].mRotateType,
rotateAngle, point);
}
matrix.Translate(point);
matrix.Rotate(rotateAngle);
}
return matrix;
}
/* static */ nsSMILValue
SVGMotionSMILType::ConstructSMILValue(gfxFlattenedPath* aPath,
float aDist,
RotateType aRotateType,
float aRotateAngle)
{
nsSMILValue smilVal(&SVGMotionSMILType::sSingleton);
MotionSegmentArray& arr = ExtractMotionSegmentArray(smilVal);
// AppendElement has guaranteed success here, since Init() allocates 1 slot.
arr.AppendElement(MotionSegment(aPath, aDist, aRotateType, aRotateAngle));
return smilVal;
}
} // namespace mozilla

View File

@ -0,0 +1,114 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SVG project.
*
* The Initial Developer of the Original Code is the Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Holbert <dholbert@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* implementation of nsISMILType for use by <animateMotion> element */
#ifndef MOZILLA_SVGMOTIONSMILTYPE_H_
#define MOZILLA_SVGMOTIONSMILTYPE_H_
#include "nsISMILType.h"
#include "gfxMatrix.h"
#include "nsTArray.h"
class nsSVGPathElement;
class nsSMILValue;
class gfxFlattenedPath;
namespace mozilla {
/**
* MotionRotateType: Enum to indicate the type of our "rotate" attribute.
*/
enum RotateType {
eRotateType_Explicit, // for e.g. rotate="45"/"45deg"/"0.785rad"
eRotateType_Auto, // for rotate="auto"
eRotateType_AutoReverse // for rotate="auto-reverse"
};
/**
* SVGMotionSMILType: Implements the nsISMILType interface for SMIL animations
* from <animateMotion>.
*
* NOTE: Even though there's technically no "motion" attribute, we behave in
* many ways as if there were, for simplicity.
*/
class SVGMotionSMILType : public nsISMILType
{
public:
// Singleton for nsSMILValue objects to hold onto.
static SVGMotionSMILType sSingleton;
protected:
// nsISMILType Methods
// -------------------
virtual void Init(nsSMILValue& aValue) const;
virtual void Destroy(nsSMILValue& aValue) const;
virtual nsresult Assign(nsSMILValue& aDest, const nsSMILValue& aSrc) const;
virtual PRBool IsEqual(const nsSMILValue& aLeft,
const nsSMILValue& aRight) const;
virtual nsresult Add(nsSMILValue& aDest,
const nsSMILValue& aValueToAdd,
PRUint32 aCount) const;
virtual nsresult SandwichAdd(nsSMILValue& aDest,
const nsSMILValue& aValueToAdd) const;
virtual nsresult ComputeDistance(const nsSMILValue& aFrom,
const nsSMILValue& aTo,
double& aDistance) const;
virtual nsresult Interpolate(const nsSMILValue& aStartVal,
const nsSMILValue& aEndVal,
double aUnitDistance,
nsSMILValue& aResult) const;
public:
// Used to generate a transform matrix from an <animateMotion> nsSMILValue.
static gfxMatrix CreateMatrix(const nsSMILValue& aSMILVal);
// Used to generate a nsSMILValue for the point at the given distance along
// the given path.
static nsSMILValue ConstructSMILValue(gfxFlattenedPath* aPath,
float aDist,
RotateType aRotateType,
float aRotateAngle);
private:
// Private constructor & destructor: prevent instances beyond my singleton,
// and prevent others from deleting my singleton.
SVGMotionSMILType() {}
~SVGMotionSMILType() {}
};
} // namespace mozilla
#endif // MOZILLA_SVGMOTIONSMILTYPE_H_

View File

@ -41,7 +41,7 @@
#include "nsSVGAnimationElement.h"
#include "nsIDOMSVGAnimateMotionElement.h"
#include "nsSVGEnum.h"
#include "nsSMILAnimationFunction.h"
#include "SVGMotionSMILAnimationFunction.h"
#include "nsCOMArray.h"
#include "nsIDOMSVGPathSeg.h"
@ -59,7 +59,7 @@ protected:
nsINodeInfo *aNodeInfo);
nsSVGAnimateMotionElement(nsINodeInfo* aNodeInfo);
nsSMILAnimationFunction mAnimationFunction;
mozilla::SVGMotionSMILAnimationFunction mAnimationFunction;
public:
// interfaces:
@ -76,6 +76,9 @@ public:
// nsISMILAnimationElement
virtual nsSMILAnimationFunction& AnimationFunction();
virtual nsIAtom* GetTargetAttributeName() const;
virtual nsSMILTargetAttrType GetTargetAttributeType() const;
};
NS_IMPL_NS_NEW_SVG_ELEMENT(AnimateMotion)
@ -117,3 +120,21 @@ nsSVGAnimateMotionElement::AnimationFunction()
{
return mAnimationFunction;
}
nsIAtom*
nsSVGAnimateMotionElement::GetTargetAttributeName() const
{
// <animateMotion> doesn't take an attributeName, since it doesn't target an
// 'attribute' per se. We'll use a unique dummy attribute-name so that our
// nsSMILTargetIdentifier logic (which requires a attribute name) still works.
return nsGkAtoms::mozAnimateMotionDummyAttr;
}
nsSMILTargetAttrType
nsSVGAnimateMotionElement::GetTargetAttributeType() const
{
// <animateMotion> doesn't take an attributeType, since it doesn't target an
// 'attribute' per se. We'll just return 'XML' for simplicity. (This just
// needs to match what we expect in nsSVGElement::GetAnimAttr.)
return eSMILTargetAttrType_XML;
}

View File

@ -94,6 +94,7 @@
#include "nsSMILMappedAttribute.h"
#include "nsSVGTransformSMILAttr.h"
#include "nsSVGAnimatedTransformList.h"
#include "SVGMotionSMILAttr.h"
#include "nsIDOMSVGTransformable.h"
#endif // MOZ_SMIL
@ -1954,6 +1955,11 @@ nsSVGElement::GetAnimatedAttr(nsIAtom* aName)
return new nsSVGTransformSMILAttr(list, this);
}
// Motion (fake 'attribute' for animateMotion)
if (aName == nsGkAtoms::mozAnimateMotionDummyAttr) {
return new mozilla::SVGMotionSMILAttr(this);
}
// Lengths:
LengthAttributesInfo info = GetLengthInfo();
for (PRUint32 i = 0; i < info.mLengthCount; i++) {

View File

@ -145,6 +145,11 @@ public:
*/
virtual gfxMatrix PrependLocalTransformTo(const gfxMatrix &aMatrix);
// Setter for to set the current <animateMotion> transformation
// Only visible for nsSVGGraphicElement, so it's a no-op here, and that
// subclass has the useful implementation.
virtual void SetAnimateMotionTransform(const gfxMatrix* aMatrix) {/*no-op*/}
virtual void DidChangeLength(PRUint8 aAttrEnum, PRBool aDoSetAttr);
virtual void DidChangeNumber(PRUint8 aAttrEnum, PRBool aDoSetAttr);
virtual void DidChangeInteger(PRUint8 aAttrEnum, PRBool aDoSetAttr);

View File

@ -185,21 +185,37 @@ nsSVGGraphicElement::IsEventName(nsIAtom* aName)
gfxMatrix
nsSVGGraphicElement::PrependLocalTransformTo(const gfxMatrix &aMatrix)
{
if (!mTransforms)
return aMatrix;
gfxMatrix result(aMatrix);
nsresult rv;
nsCOMPtr<nsIDOMSVGTransformList> transforms;
rv = mTransforms->GetAnimVal(getter_AddRefs(transforms));
NS_ENSURE_SUCCESS(rv, aMatrix);
PRUint32 count;
transforms->GetNumberOfItems(&count);
if (count == 0)
return aMatrix;
// animateMotion's resulting transform is supposed to apply *on top of*
// any transformations from the |transform| attribute. So since we're
// PRE-multiplying, we need to apply the animateMotion transform *first*.
if (mAnimateMotionTransform) {
result.PreMultiply(*mAnimateMotionTransform);
}
nsCOMPtr<nsIDOMSVGMatrix> matrix =
nsSVGTransformList::GetConsolidationMatrix(transforms);
return gfxMatrix(aMatrix).PreMultiply(nsSVGUtils::ConvertSVGMatrixToThebes(matrix));
if (mTransforms) {
nsresult rv;
nsCOMPtr<nsIDOMSVGTransformList> transforms;
rv = mTransforms->GetAnimVal(getter_AddRefs(transforms));
NS_ENSURE_SUCCESS(rv, aMatrix);
PRUint32 count;
transforms->GetNumberOfItems(&count);
if (count > 0) {
nsCOMPtr<nsIDOMSVGMatrix> matrix =
nsSVGTransformList::GetConsolidationMatrix(transforms);
result.PreMultiply(nsSVGUtils::ConvertSVGMatrixToThebes(matrix));
}
}
return result;
}
void
nsSVGGraphicElement::SetAnimateMotionTransform(const gfxMatrix* aMatrix)
{
mAnimateMotionTransform = aMatrix ? new gfxMatrix(*aMatrix) : nsnull;
DidAnimateTransform();
}
nsresult

View File

@ -63,6 +63,7 @@ public:
NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* aAttribute) const;
virtual gfxMatrix PrependLocalTransformTo(const gfxMatrix &aMatrix);
virtual void SetAnimateMotionTransform(const gfxMatrix* aMatrix);
protected:
// nsSVGElement overrides
@ -73,6 +74,9 @@ protected:
nsCOMPtr<nsIDOMSVGAnimatedTransformList> mTransforms;
// XXX maybe move this to property table, to save space on un-animated elems?
nsAutoPtr<gfxMatrix> mAnimateMotionTransform;
// helper
nsresult CreateTransformList();
};

View File

@ -118,6 +118,13 @@ class nsSVGDisplayContainerFrame;
#define SVG_WSP_DELIM "\x20\x9\xD\xA"
#define SVG_COMMA_WSP_DELIM "," SVG_WSP_DELIM
inline PRBool
IsSVGWhitespace(char aChar)
{
return aChar == '\x20' || aChar == '\x9' ||
aChar == '\xD' || aChar == '\xA';
}
/*
* Checks the svg enable preference and if a renderer could
* successfully be created. Declared as a function instead of a