Bug 619498 - Part 2: Support interpolation and addition of similar SVG path segment types (v4) r=dholbert

This commit is contained in:
Cameron McCormack 2011-04-08 10:17:36 +12:00
parent f7c58f9131
commit 6f9c7ae6e8
7 changed files with 956 additions and 278 deletions

View File

@ -124,18 +124,17 @@ SVGPathData::AppendSeg(PRUint32 aType, ...)
float
SVGPathData::GetPathLength() const
{
float length = 0.0;
SVGPathTraversalState state;
PRUint32 i = 0;
while (i < mData.Length()) {
length += SVGPathSegUtils::GetLength(&mData[i], state);
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
}
NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt");
return length;
return state.length;
}
#ifdef DEBUG
@ -163,7 +162,9 @@ SVGPathData::GetSegmentLengths(nsTArray<double> *aLengths) const
PRUint32 i = 0;
while (i < mData.Length()) {
if (!aLengths->AppendElement(SVGPathSegUtils::GetLength(&mData[i], state))) {
state.length = 0.0;
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
if (!aLengths->AppendElement(state.length)) {
aLengths->Clear();
return PR_FALSE;
}
@ -178,7 +179,6 @@ SVGPathData::GetSegmentLengths(nsTArray<double> *aLengths) const
PRBool
SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray<double> *aOutput) const
{
double distRunningTotal = 0.0;
SVGPathTraversalState state;
aOutput->Clear();
@ -186,6 +186,7 @@ SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray<double> *aOu
PRUint32 i = 0;
while (i < mData.Length()) {
PRUint32 segType = SVGPathSegUtils::DecodeType(mData[i]);
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
// We skip all moveto commands except an initial moveto. See the text 'A
// "move to" command does not count as an additional point when dividing up
@ -199,8 +200,7 @@ SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray<double> *aOu
if (i == 0 || (segType != nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS &&
segType != nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL)) {
distRunningTotal += SVGPathSegUtils::GetLength(&mData[i], state);
if (!aOutput->AppendElement(distRunningTotal)) {
if (!aOutput->AppendElement(state.length)) {
return PR_FALSE;
}
}
@ -220,13 +220,12 @@ SVGPathData::GetPathSegAtLength(float aDistance) const
// Return -1? Throwing would better help authors avoid tricky bugs (DOM
// could do that if we return -1).
double distRunningTotal = 0.0;
PRUint32 i = 0, segIndex = 0;
SVGPathTraversalState state;
while (i < mData.Length()) {
distRunningTotal += SVGPathSegUtils::GetLength(&mData[i], state);
if (distRunningTotal >= aDistance) {
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
if (state.length >= aDistance) {
return segIndex;
}
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);

View File

@ -105,6 +105,7 @@ class SVGPathData
// are responsible for that!
public:
typedef const float* const_iterator;
SVGPathData(){}
~SVGPathData(){}
@ -178,6 +179,9 @@ public:
void ConstructPath(gfxContext *aCtx) const;
const_iterator begin() const { return mData.Elements(); }
const_iterator end() const { return mData.Elements() + mData.Length(); }
// Access to methods that can modify objects of this type is deliberately
// limited. This is to reduce the chances of someone modifying objects of
// this type without taking the necessary steps to keep DOM wrappers in sync.
@ -186,6 +190,7 @@ public:
// can take care of keeping DOM wrappers in sync.
protected:
typedef float* iterator;
/**
* This may fail on OOM if the internal capacity needs to be increased, in
@ -221,6 +226,9 @@ protected:
nsresult AppendSeg(PRUint32 aType, ...); // variable number of float args
iterator begin() { return mData.Elements(); }
iterator end() { return mData.Elements() + mData.Length(); }
nsTArray<float> mData;
};
@ -236,7 +244,6 @@ protected:
class SVGPathDataAndOwner : public SVGPathData
{
public:
SVGPathDataAndOwner(nsSVGElement *aElement = nsnull)
: mElement(aElement)
{}
@ -260,8 +267,13 @@ public:
* SetElement() when using this method!
*/
using SVGPathData::CopyFrom;
// Exposed since SVGPathData objects can be modified.
using SVGPathData::iterator;
using SVGPathData::operator[];
using SVGPathData::SetLength;
using SVGPathData::begin;
using SVGPathData::end;
private:
// We must keep a strong reference to our element because we may belong to a

View File

@ -89,6 +89,240 @@ SVGPathSegListSMILType::IsEqual(const nsSMILValue& aLeft,
*static_cast<const SVGPathDataAndOwner*>(aRight.mU.mPtr);
}
static PRBool
ArcFlagsDiffer(SVGPathDataAndOwner::const_iterator aPathData1,
SVGPathDataAndOwner::const_iterator aPathData2)
{
NS_ABORT_IF_FALSE
(SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData1[0])),
"ArcFlagsDiffer called with non-arc segment");
NS_ABORT_IF_FALSE
(SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData2[0])),
"ArcFlagsDiffer called with non-arc segment");
return aPathData1[4] != aPathData2[4] || // large arc flag
aPathData1[5] != aPathData2[5]; // sweep flag
}
enum PathInterpolationResult {
eCannotInterpolate,
eRequiresConversion,
eCanInterpolate
};
static PathInterpolationResult
CanInterpolate(const SVGPathDataAndOwner& aStart,
const SVGPathDataAndOwner& aEnd)
{
if (aStart.IsEmpty()) {
return eCanInterpolate;
}
if (aStart.Length() != aEnd.Length()) {
return eCannotInterpolate;
}
PathInterpolationResult result = eCanInterpolate;
SVGPathDataAndOwner::const_iterator pStart = aStart.begin();
SVGPathDataAndOwner::const_iterator pEnd = aEnd.begin();
SVGPathDataAndOwner::const_iterator pStartDataEnd = aStart.end();
SVGPathDataAndOwner::const_iterator pEndDataEnd = aEnd.end();
while (pStart < pStartDataEnd && pEnd < pEndDataEnd) {
PRUint32 startType = SVGPathSegUtils::DecodeType(*pStart);
PRUint32 endType = SVGPathSegUtils::DecodeType(*pEnd);
if (SVGPathSegUtils::IsArcType(startType) &&
SVGPathSegUtils::IsArcType(endType) &&
ArcFlagsDiffer(pStart, pEnd)) {
return eCannotInterpolate;
}
if (startType != endType) {
if (!SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType)) {
return eCannotInterpolate;
}
result = eRequiresConversion;
}
pStart += 1 + SVGPathSegUtils::ArgCountForType(startType);
pEnd += 1 + SVGPathSegUtils::ArgCountForType(endType);
}
NS_ABORT_IF_FALSE(pStart <= pStartDataEnd && pEnd <= pEndDataEnd,
"Iterated past end of buffer! (Corrupt path data?)");
if (pStart != pStartDataEnd || pEnd != pEndDataEnd) {
return eCannotInterpolate;
}
return result;
}
static void
InterpolatePathSegmentData(SVGPathDataAndOwner::const_iterator& aStart,
SVGPathDataAndOwner::const_iterator& aEnd,
SVGPathDataAndOwner::iterator& aResult,
float aUnitDistance)
{
PRUint32 startType = SVGPathSegUtils::DecodeType(*aStart);
PRUint32 endType = SVGPathSegUtils::DecodeType(*aEnd);
NS_ABORT_IF_FALSE
(startType == endType,
"InterpolatePathSegmentData expects segment types to be the same!");
NS_ABORT_IF_FALSE
(!(SVGPathSegUtils::IsArcType(startType) && ArcFlagsDiffer(aStart, aEnd)),
"InterpolatePathSegmentData cannot interpolate arc segments with different flag values!");
PRUint32 argCount = SVGPathSegUtils::ArgCountForType(startType);
// Copy over segment type.
*aResult++ = *aStart++;
++aEnd;
// Interpolate the arguments.
SVGPathDataAndOwner::const_iterator startSegEnd = aStart + argCount;
while (aStart != startSegEnd) {
*aResult = *aStart + (*aEnd - *aStart) * aUnitDistance;
++aStart;
++aEnd;
++aResult;
}
}
enum RelativenessAdjustmentType {
eAbsoluteToRelative,
eRelativeToAbsolute
};
static inline void
AdjustSegmentForRelativeness(RelativenessAdjustmentType aAdjustmentType,
const SVGPathDataAndOwner::iterator& aSegmentToAdjust,
const SVGPathTraversalState& aState)
{
if (aAdjustmentType == eAbsoluteToRelative) {
aSegmentToAdjust[0] -= aState.pos.x;
aSegmentToAdjust[1] -= aState.pos.y;
} else {
aSegmentToAdjust[0] += aState.pos.x;
aSegmentToAdjust[1] += aState.pos.y;
}
}
static void
ConvertPathSegmentData(SVGPathDataAndOwner::const_iterator& aStart,
SVGPathDataAndOwner::const_iterator& aEnd,
SVGPathDataAndOwner::iterator& aResult,
SVGPathTraversalState& aState)
{
PRUint32 startType = SVGPathSegUtils::DecodeType(*aStart);
PRUint32 endType = SVGPathSegUtils::DecodeType(*aEnd);
PRUint32 segmentLengthIncludingType =
1 + SVGPathSegUtils::ArgCountForType(startType);
SVGPathDataAndOwner::const_iterator pResultSegmentBegin = aResult;
if (startType == endType) {
// No conversion need, just directly copy aStart.
aEnd += segmentLengthIncludingType;
while (segmentLengthIncludingType) {
*aResult++ = *aStart++;
--segmentLengthIncludingType;
}
SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin, aState);
return;
}
NS_ABORT_IF_FALSE
(SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType),
"Incompatible path segment types passed to ConvertPathSegmentData!");
RelativenessAdjustmentType adjustmentType =
SVGPathSegUtils::IsRelativeType(startType) ? eRelativeToAbsolute
: eAbsoluteToRelative;
NS_ABORT_IF_FALSE
(segmentLengthIncludingType ==
1 + SVGPathSegUtils::ArgCountForType(endType),
"Compatible path segment types for interpolation had different lengths!");
aResult[0] = aEnd[0];
switch (endType) {
case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_ABS:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_REL:
aResult[1] = aStart[1] +
(adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.x;
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_ABS:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_REL:
aResult[1] = aStart[1] +
(adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.y;
break;
case nsIDOMSVGPathSeg::PATHSEG_ARC_ABS:
case nsIDOMSVGPathSeg::PATHSEG_ARC_REL:
aResult[1] = aStart[1];
aResult[2] = aStart[2];
aResult[3] = aStart[3];
aResult[4] = aStart[4];
aResult[5] = aStart[5];
aResult[6] = aStart[6];
aResult[7] = aStart[7];
AdjustSegmentForRelativeness(adjustmentType, aResult + 6, aState);
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL:
aResult[5] = aStart[5];
aResult[6] = aStart[6];
AdjustSegmentForRelativeness(adjustmentType, aResult + 5, aState);
// fall through
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
aResult[3] = aStart[3];
aResult[4] = aStart[4];
AdjustSegmentForRelativeness(adjustmentType, aResult + 3, aState);
// fall through
case nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS:
case nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_ABS:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_REL:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
aResult[1] = aStart[1];
aResult[2] = aStart[2];
AdjustSegmentForRelativeness(adjustmentType, aResult + 1, aState);
break;
}
SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin, aState);
aStart += segmentLengthIncludingType;
aEnd += segmentLengthIncludingType;
aResult += segmentLengthIncludingType;
}
static void
ConvertAllPathSegmentData(SVGPathDataAndOwner::const_iterator aStart,
SVGPathDataAndOwner::const_iterator aStartDataEnd,
SVGPathDataAndOwner::const_iterator aEnd,
SVGPathDataAndOwner::const_iterator aEndDataEnd,
SVGPathDataAndOwner::iterator aResult)
{
SVGPathTraversalState state;
state.mode = SVGPathTraversalState::eUpdateOnlyStartAndCurrentPos;
while (aStart < aStartDataEnd && aEnd < aEndDataEnd) {
ConvertPathSegmentData(aStart, aEnd, aResult, state);
}
NS_ABORT_IF_FALSE(aStart == aStartDataEnd && aEnd == aEndDataEnd,
"Failed to convert all path segment data! (Corrupt?)");
}
nsresult
SVGPathSegListSMILType::Add(nsSMILValue& aDest,
const nsSMILValue& aValueToAdd,
@ -102,36 +336,33 @@ SVGPathSegListSMILType::Add(nsSMILValue& aDest,
const SVGPathDataAndOwner& valueToAdd =
*static_cast<const SVGPathDataAndOwner*>(aValueToAdd.mU.mPtr);
if (dest.Length() != valueToAdd.Length()) {
// Allow addition to empty dest:
if (dest.Length() == 0) {
return dest.CopyFrom(valueToAdd);
}
// For now we only support animation to a list with the same number of
// items (and with the same segment types).
// nsSVGUtils::ReportToConsole
// Allow addition to empty dest.
if (dest.IsEmpty()) {
return dest.CopyFrom(valueToAdd);
}
PathInterpolationResult check = CanInterpolate(dest, valueToAdd);
if (check == eCannotInterpolate) {
// nsSVGUtils::ReportToConsole - can't add path segment lists with different
// numbers of segments, with arcs with different flag values, or with
// incompatible segment types.
return NS_ERROR_FAILURE;
}
if (check == eRequiresConversion) {
ConvertAllPathSegmentData(dest.begin(), dest.end(),
valueToAdd.begin(), valueToAdd.end(),
dest.begin());
}
PRUint32 i = 0;
while (i < dest.Length()) {
PRUint32 type = SVGPathSegUtils::DecodeType(dest[i]);
if (type != SVGPathSegUtils::DecodeType(valueToAdd[i])) {
// nsSVGUtils::ReportToConsole - can't yet animate between different
// types, although it would make sense to allow animation between
// some.
return NS_ERROR_FAILURE;
}
i++;
if ((type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL) &&
(dest[i+3] != valueToAdd[i+3] || dest[i+4] != valueToAdd[i+4])) {
// boolean args largeArcFlag and sweepFlag must be the same
return NS_ERROR_FAILURE;
}
PRUint32 segEnd = i + SVGPathSegUtils::ArgCountForType(type);
for (; i < segEnd; ++i) {
dest[i] += valueToAdd[i];
dest[i] += valueToAdd[i] * aCount;
}
}
@ -175,10 +406,12 @@ SVGPathSegListSMILType::Interpolate(const nsSMILValue& aStartVal,
SVGPathDataAndOwner& result =
*static_cast<SVGPathDataAndOwner*>(aResult.mU.mPtr);
if (start.Length() != end.Length() && start.Length() != 0) {
// For now we only support animation to a list with the same number of
// items (and with the same segment types).
// nsSVGUtils::ReportToConsole
PathInterpolationResult check = CanInterpolate(start, end);
if (check == eCannotInterpolate) {
// nsSVGUtils::ReportToConsole - can't interpolate path segment lists with
// different numbers of segments, with arcs with different flag values, or
// with incompatible segment types.
return NS_ERROR_FAILURE;
}
@ -186,16 +419,14 @@ SVGPathSegListSMILType::Interpolate(const nsSMILValue& aStartVal,
return NS_ERROR_OUT_OF_MEMORY;
}
PRUint32 i = 0;
if (start.Length() == 0) { // identity path
if (start.IsEmpty()) { // identity path
PRUint32 i = 0;
while (i < end.Length()) {
PRUint32 type = SVGPathSegUtils::DecodeType(end[i]);
result[i] = end[i];
i++;
PRUint32 segEnd = i + SVGPathSegUtils::ArgCountForType(type);
if ((type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL)) {
if (SVGPathSegUtils::IsArcType(type)) {
result[i] = end[i] * aUnitDistance;
result[i+1] = end[i+1] * aUnitDistance;
result[i+2] = end[i+2] * aUnitDistance;
@ -211,32 +442,30 @@ SVGPathSegListSMILType::Interpolate(const nsSMILValue& aStartVal,
}
}
}
NS_ABORT_IF_FALSE(i == end.Length() && i == result.Length(),
"Very, very bad - path data corrupt");
} else {
while (i < end.Length()) {
PRUint32 type = SVGPathSegUtils::DecodeType(end[i]);
if (type != SVGPathSegUtils::DecodeType(start[i])) {
// nsSVGUtils::ReportToConsole - can't yet interpolate between different
// types, although it would make sense to allow interpolation between
// some.
return NS_ERROR_FAILURE;
}
result[i] = end[i];
i++;
if ((type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL) &&
(start[i+3] != end[i+3] || start[i+4] != end[i+4])) {
// boolean args largeArcFlag and sweepFlag must be the same
return NS_ERROR_FAILURE;
}
PRUint32 segEnd = i + SVGPathSegUtils::ArgCountForType(type);
for (; i < segEnd; ++i) {
result[i] = start[i] + (end[i] - start[i]) * aUnitDistance;
}
}
}
SVGPathDataAndOwner::const_iterator pStart = start.begin();
SVGPathDataAndOwner::const_iterator pStartDataEnd = start.end();
SVGPathDataAndOwner::const_iterator pEnd = end.begin();
SVGPathDataAndOwner::const_iterator pEndDataEnd = end.end();
SVGPathDataAndOwner::iterator pResult = result.begin();
NS_ABORT_IF_FALSE(i == end.Length(), "Very, very bad - path data corrupt");
if (check == eRequiresConversion) {
ConvertAllPathSegmentData(pStart, pStartDataEnd, pEnd, pEndDataEnd,
pResult);
pStart = pResult;
pStartDataEnd = result.end();
}
while (pStart != pStartDataEnd && pEnd != pEndDataEnd) {
InterpolatePathSegmentData(pStart, pEnd, pResult, aUnitDistance);
}
NS_ABORT_IF_FALSE(pStart == pStartDataEnd && pEnd == pEndDataEnd &&
pResult == result.end(),
"Very, very bad - path data corrupt");
}
return NS_OK;
}

View File

@ -57,14 +57,15 @@ static const PRUint32 MAX_RECURSION = 10;
SVGPathSegUtils::GetValueAsString(const float* aSeg, nsAString& aValue)
{
// Adding new seg type? Is the formatting below acceptable for the new types?
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL);
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_MAX_ARGS == 7);
PRUint32 type = DecodeType(aSeg[0]);
PRUnichar typeAsChar = GetPathSegTypeAsLetter(type);
// Special case arcs:
if (type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL) {
if (IsArcType(type)) {
PRBool largeArcFlag = aSeg[4] != 0.0f;
PRBool sweepFlag = aSeg[5] != 0.0f;
nsTextFormatter::ssprintf(aValue,
@ -201,261 +202,284 @@ CalcLengthOfQuadraticBezier(const gfxPoint& aPos, const gfxPoint& aCP,
}
static float GetLengthOfClosePath(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseClosePath(const float* aArgs, SVGPathTraversalState& aState)
{
float dist = CalcDistanceBetweenPoints(aState.pos, aState.start);
aState.pos = aState.cp1 = aState.cp2 = aState.start;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += CalcDistanceBetweenPoints(aState.pos, aState.start);
aState.cp1 = aState.cp2 = aState.start;
}
aState.pos = aState.start;
}
static float GetLengthOfMovetoAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseMovetoAbs(const float* aArgs, SVGPathTraversalState& aState)
{
aState.start = aState.pos = aState.cp1 = aState.cp2 = gfxPoint(aArgs[0], aArgs[1]);
return 0.0;
aState.start = aState.pos = gfxPoint(aArgs[0], aArgs[1]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
// aState.length is unchanged, since move commands don't affect path length.
aState.cp1 = aState.cp2 = aState.start;
}
}
static float GetLengthOfMovetoRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseMovetoRel(const float* aArgs, SVGPathTraversalState& aState)
{
// aState.pos must be second from right due to +=
aState.start = aState.cp1 = aState.cp2 = aState.pos += gfxPoint(aArgs[0], aArgs[1]);
return 0.0;
aState.start = aState.pos += gfxPoint(aArgs[0], aArgs[1]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
// aState.length is unchanged, since move commands don't affect path length.
aState.cp1 = aState.cp2 = aState.start;
}
}
static float GetLengthOfLinetoAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to(aArgs[0], aArgs[1]);
float dist = CalcDistanceBetweenPoints(aState.pos, to);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += CalcDistanceBetweenPoints(aState.pos, to);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static float GetLengthOfLinetoRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoRel(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
float dist = CalcDistanceBetweenPoints(aState.pos, to);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += CalcDistanceBetweenPoints(aState.pos, to);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static float
GetLengthOfLinetoHorizontalAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoHorizontalAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to(aArgs[0], aState.pos.y);
float dist = fabs(to.x - aState.pos.x);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += fabs(to.x - aState.pos.x);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static float
GetLengthOfLinetoHorizontalRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoHorizontalRel(const float* aArgs, SVGPathTraversalState& aState)
{
aState.cp1 = aState.cp2 = aState.pos += gfxPoint(aArgs[0], 0.0);
return fabs(aArgs[0]);
aState.pos.x += aArgs[0];
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += fabs(aArgs[0]);
aState.cp1 = aState.cp2 = aState.pos;
}
}
static float
GetLengthOfLinetoVerticalAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoVerticalAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to(aState.pos.x, aArgs[0]);
float dist = fabs(to.y - aState.pos.y);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += fabs(to.y - aState.pos.y);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static float
GetLengthOfLinetoVerticalRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoVerticalRel(const float* aArgs, SVGPathTraversalState& aState)
{
aState.cp1 = aState.cp2 = aState.pos += gfxPoint(0.0, aArgs[0]);
return fabs(aArgs[0]);
aState.pos.y += aArgs[0];
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += fabs(aArgs[0]);
aState.cp1 = aState.cp2 = aState.pos;
}
}
static float GetLengthOfCurvetoCubicAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseCurvetoCubicAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint cp1(aArgs[0], aArgs[1]);
gfxPoint cp2(aArgs[2], aArgs[3]);
gfxPoint to(aArgs[4], aArgs[5]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp1(aArgs[0], aArgs[1]);
gfxPoint cp2(aArgs[2], aArgs[3]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static float
GetLengthOfCurvetoCubicSmoothAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseCurvetoCubicSmoothAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
gfxPoint cp2(aArgs[0], aArgs[1]);
gfxPoint to(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
gfxPoint cp2(aArgs[0], aArgs[1]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static float
GetLengthOfCurvetoCubicRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseCurvetoCubicRel(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint cp1 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint cp2 = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
gfxPoint to = aState.pos + gfxPoint(aArgs[4], aArgs[5]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
gfxPoint to = aState.pos + gfxPoint(aArgs[4], aArgs[5]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp1 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint cp2 = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static float
GetLengthOfCurvetoCubicSmoothRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseCurvetoCubicSmoothRel(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
gfxPoint cp2 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint to = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
}
static float
GetLengthOfCurvetoQuadraticAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp(aArgs[0], aArgs[1]);
gfxPoint to(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
}
static float
GetLengthOfCurvetoQuadraticSmoothAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
gfxPoint to(aArgs[0], aArgs[1]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
}
static float
GetLengthOfCurvetoQuadraticRel(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint to = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
gfxPoint cp2 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static float
GetLengthOfCurvetoQuadraticSmoothRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseCurvetoQuadraticAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to(aArgs[2], aArgs[3]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp(aArgs[0], aArgs[1]);
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.cp2 = to;
}
aState.pos = to;
}
static void
TraverseCurvetoQuadraticSmoothAbs(const float* aArgs,
SVGPathTraversalState& aState)
{
gfxPoint to(aArgs[0], aArgs[1]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.cp2 = to;
}
aState.pos = to;
}
static void
TraverseCurvetoQuadraticRel(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.cp2 = to;
}
aState.pos = to;
}
static void
TraverseCurvetoQuadraticSmoothRel(const float* aArgs,
SVGPathTraversalState& aState)
{
gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
gfxPoint to = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.cp2 = to;
}
aState.pos = to;
}
static float
GetLengthOfArcAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseArcAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint radii(aArgs[0], aArgs[1]);
gfxPoint to(aArgs[5], aArgs[6]);
gfxPoint bez[4] = { aState.pos, gfxPoint(0,0), gfxPoint(0,0), gfxPoint(0,0) };
nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
aArgs[3] != 0, aArgs[4] != 0);
float dist = 0;
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3]))
{
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
bez[0] = bez[3];
if (aState.ShouldUpdateLengthAndControlPoints()) {
float dist = 0;
gfxPoint radii(aArgs[0], aArgs[1]);
gfxPoint bez[4] = { aState.pos, gfxPoint(0, 0),
gfxPoint(0, 0), gfxPoint(0, 0) };
nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
aArgs[3] != 0, aArgs[4] != 0);
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
bez[0] = bez[3];
}
aState.length += dist;
aState.cp1 = aState.cp2 = to;
}
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
aState.pos = to;
}
static float
GetLengthOfArcRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseArcRel(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint radii(aArgs[0], aArgs[1]);
gfxPoint to = aState.pos + gfxPoint(aArgs[5], aArgs[6]);
gfxPoint bez[4] = { aState.pos, gfxPoint(0,0), gfxPoint(0,0), gfxPoint(0,0) };
nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
aArgs[3] != 0, aArgs[4] != 0);
float dist = 0;
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3]))
{
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
bez[0] = bez[3];
if (aState.ShouldUpdateLengthAndControlPoints()) {
float dist = 0;
gfxPoint radii(aArgs[0], aArgs[1]);
gfxPoint bez[4] = { aState.pos, gfxPoint(0, 0),
gfxPoint(0, 0), gfxPoint(0, 0) };
nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
aArgs[3] != 0, aArgs[4] != 0);
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
bez[0] = bez[3];
}
aState.length += dist;
aState.cp1 = aState.cp2 = to;
}
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
aState.pos = to;
}
typedef float (*getLengthFunc)(const float*, SVGPathTraversalState&);
typedef void (*TraverseFunc)(const float*, SVGPathTraversalState&);
/* static */ float
SVGPathSegUtils::GetLength(const float *seg, SVGPathTraversalState &aState)
static TraverseFunc gTraverseFuncTable[NS_SVG_PATH_SEG_TYPE_COUNT] = {
nsnull, // 0 == PATHSEG_UNKNOWN
TraverseClosePath,
TraverseMovetoAbs,
TraverseMovetoRel,
TraverseLinetoAbs,
TraverseLinetoRel,
TraverseCurvetoCubicAbs,
TraverseCurvetoCubicRel,
TraverseCurvetoQuadraticAbs,
TraverseCurvetoQuadraticRel,
TraverseArcAbs,
TraverseArcRel,
TraverseLinetoHorizontalAbs,
TraverseLinetoHorizontalRel,
TraverseLinetoVerticalAbs,
TraverseLinetoVerticalRel,
TraverseCurvetoCubicSmoothAbs,
TraverseCurvetoCubicSmoothRel,
TraverseCurvetoQuadraticSmoothAbs,
TraverseCurvetoQuadraticSmoothRel
};
/* static */ void
SVGPathSegUtils::TraversePathSegment(const float* aData,
SVGPathTraversalState& aState)
{
PRUint32 type = DecodeType(seg[0]);
static getLengthFunc lengthFuncTable[20] = {
nsnull, // 0 == PATHSEG_UNKNOWN
GetLengthOfClosePath,
GetLengthOfMovetoAbs,
GetLengthOfMovetoRel,
GetLengthOfLinetoAbs,
GetLengthOfLinetoRel,
GetLengthOfCurvetoCubicAbs,
GetLengthOfCurvetoCubicRel,
GetLengthOfCurvetoQuadraticAbs,
GetLengthOfCurvetoQuadraticRel,
GetLengthOfArcAbs,
GetLengthOfArcRel,
GetLengthOfLinetoHorizontalAbs,
GetLengthOfLinetoHorizontalRel,
GetLengthOfLinetoVerticalAbs,
GetLengthOfLinetoVerticalRel,
GetLengthOfCurvetoCubicSmoothAbs,
GetLengthOfCurvetoCubicSmoothRel,
GetLengthOfCurvetoQuadraticSmoothAbs,
GetLengthOfCurvetoQuadraticSmoothRel
};
NS_ABORT_IF_FALSE(IsValidType(type), "Seg type not recognized");
NS_ABORT_IF_FALSE(type > 0 && type < NS_ARRAY_LENGTH(lengthFuncTable),
"Seg type not recognized");
return lengthFuncTable[type](seg + 1, aState);
PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gTraverseFuncTable) ==
NS_SVG_PATH_SEG_TYPE_COUNT);
PRUint32 type = DecodeType(aData[0]);
gTraverseFuncTable[type](aData + 1, aState);
}

View File

@ -43,7 +43,10 @@
#include "nsContentUtils.h"
#include "gfxPoint.h"
#define NS_SVG_PATH_SEG_MAX_ARGS 7
#define NS_SVG_PATH_SEG_MAX_ARGS 7
#define NS_SVG_PATH_SEG_FIRST_VALID_TYPE nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH
#define NS_SVG_PATH_SEG_LAST_VALID_TYPE nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
#define NS_SVG_PATH_SEG_TYPE_COUNT (NS_SVG_PATH_SEG_LAST_VALID_TYPE + 1)
namespace mozilla {
@ -54,13 +57,22 @@ namespace mozilla {
*/
struct SVGPathTraversalState
{
enum TraversalMode {
eUpdateAll,
eUpdateOnlyStartAndCurrentPos
};
SVGPathTraversalState()
: start(0.0, 0.0)
, pos(0.0, 0.0)
, cp1(0.0, 0.0)
, cp2(0.0, 0.0)
, length(0.0)
, mode(eUpdateAll)
{}
PRBool ShouldUpdateLengthAndControlPoints() { return mode == eUpdateAll; }
gfxPoint start; // start point of current sub path (reset each moveto)
gfxPoint pos; // current position (end point of previous segment)
@ -72,6 +84,10 @@ struct SVGPathTraversalState
gfxPoint cp2; // cubic control point - if the previous segment was a cubic
// bezier curve then this is set to the absolute position of
// its second control point, otherwise it's set to pos
float length; // accumulated path length
TraversalMode mode; // indicates what to track while traversing a path
};
@ -144,6 +160,7 @@ public:
PRUnichar('T'), // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
PRUnichar('t') // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
};
PR_STATIC_ASSERT(NS_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT);
return table[aType];
}
@ -173,6 +190,7 @@ public:
2, // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
2 // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
};
PR_STATIC_ASSERT(NS_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT);
return table[aType];
}
@ -185,31 +203,81 @@ public:
return ArgCountForType(DecodeType(aType));
}
static inline PRBool IsValidType(PRUint32 aType) {
return aType >= nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH &&
aType <= nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
static PRBool IsValidType(PRUint32 aType) {
return aType >= NS_SVG_PATH_SEG_FIRST_VALID_TYPE &&
aType <= NS_SVG_PATH_SEG_LAST_VALID_TYPE;
}
static inline PRBool IsCubicType(PRUint32 aType) {
static PRBool IsCubicType(PRUint32 aType) {
return aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
}
static inline PRBool IsQuadraticType(PRUint32 aType) {
static PRBool IsQuadraticType(PRUint32 aType) {
return aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
}
/**
* Returns the user unit length of tracing along the path segment.
*/
static float GetLength(const float *aSeg, SVGPathTraversalState &aState);
static PRBool IsArcType(PRUint32 aType) {
return aType == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
aType == nsIDOMSVGPathSeg::PATHSEG_ARC_REL;
}
static void ToString(const float *aSeg, nsAString& aValue);
static PRBool IsRelativeOrAbsoluteType(PRUint32 aType) {
NS_ABORT_IF_FALSE(IsValidType(aType), "Seg type not recognized");
// When adding a new path segment type, ensure that the returned condition
// below is still correct.
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL);
return aType >= nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS;
}
static PRBool IsRelativeType(PRUint32 aType) {
NS_ABORT_IF_FALSE
(IsRelativeOrAbsoluteType(aType),
"IsRelativeType called with segment type that does not come in relative and absolute forms");
// When adding a new path segment type, ensure that the returned condition
// below is still correct.
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL);
return aType & 1;
}
static PRUint32 RelativeVersionOfType(PRUint32 aType) {
NS_ABORT_IF_FALSE
(IsRelativeOrAbsoluteType(aType),
"RelativeVersionOfType called with segment type that does not come in relative and absolute forms");
// When adding a new path segment type, ensure that the returned condition
// below is still correct.
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL);
return aType | 1;
}
static PRUint32 SameTypeModuloRelativeness(PRUint32 aType1, PRUint32 aType2) {
if (!IsRelativeOrAbsoluteType(aType1)) {
return aType1 == aType2;
}
return RelativeVersionOfType(aType1) == RelativeVersionOfType(aType2);
}
/**
* Traverse the given path segment and update the SVGPathTraversalState
* object.
*/
static void TraversePathSegment(const float* aData,
SVGPathTraversalState& aState);
};
} // namespace mozilla

View File

@ -49,6 +49,12 @@ include $(topsrcdir)/config/rules.mk
# test_length.xhtml \
_TEST_FILES = \
test_a_href_01.xhtml \
test_a_href_02.xhtml \
a_href_destination.svg \
a_href_helper_01.svg \
a_href_helper_02_03.svg \
a_href_helper_04.svg \
test_animLengthObjectIdentity.xhtml \
test_animLengthReadonly.xhtml \
test_animLengthRelativeUnits.xhtml \
@ -64,6 +70,7 @@ _TEST_FILES = \
getSubStringLength-helper.svg \
test_isSupported.xhtml \
test_nonAnimStrings.xhtml \
test_pathAnimInterpolation.xhtml \
test_pathSeg.xhtml \
test_pointer-events.xhtml \
test_pointer-events-2.xhtml \
@ -88,12 +95,6 @@ _TEST_FILES = \
test_viewport.html \
zoom-helper.svg \
test_zoom.xhtml \
test_a_href_01.xhtml \
test_a_href_02.xhtml \
a_href_destination.svg \
a_href_helper_01.svg \
a_href_helper_02_03.svg \
a_href_helper_04.svg \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -0,0 +1,345 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=619498
-->
<head>
<title>Test interpolation between different path segment types</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=619498">Mozilla Bug 619498</a>
<svg xmlns="http://www.w3.org/2000/svg" id="svg" visibility="hidden"
onload="this.pauseAnimations()"/>
<script type="application/javascript;version=1.8"><![CDATA[
SimpleTest.waitForExplicitFinish();
var gSVG = document.getElementById("svg");
// Array of all subtests to run. This is populated by addTest.
var gTests = [];
// Array of all path segment types.
var gTypes = "zMmLlCcQqAaHhVvSsTt".split("");
// Property names on the SVGPathSeg objects for the given segment type, in the
// order that they would appear in a path data string.
var gArgumentNames = {
Z: [],
M: ['x', 'y'],
L: ['x', 'y'],
C: ['x1', 'y1', 'x2', 'y2', 'x', 'y'],
Q: ['x1', 'y1', 'x', 'y'],
A: ['r1', 'r2', 'angle', 'largeArcFlag', 'sweepFlag', 'x', 'y'],
H: ['x'],
V: ['y'],
S: ['x2', 'y2', 'x', 'y'],
T: ['x', 'y']
};
// All of these prefixes leave the current point at 100,100. Some of them
// affect the implied control point if followed by a smooth quadratic or
// cubic segment, but no valid interpolations depend on those control points.
var gPrefixes = [
[1, "M100,100"],
[2, "M50,50 M100,100"],
[2, "M50,50 m50,50"],
[2, "M50,50 L100,100"],
[2, "M50,50 l50,50"],
[3, "M50,50 H100 V100"],
[3, "M50,50 h50 V100"],
[3, "M50,50 H100 v50"],
[2, "M50,50 A10,10,10,0,0,100,100"],
[2, "M50,50 a10,10,10,0,0,50,50"],
[4, "M50,50 l50,50 z m50,50"],
// These leave the quadratic implied control point at 125,125.
[2, "M50,50 Q75,75,100,100"],
[2, "M50,50 q25,25,50,50"],
[2, "M75,75 T100,100"],
[2, "M75,75 t25,25"],
[3, "M50,50 T62.5,62.5 t37.5,37.5"],
[3, "M50,50 T62.5,62.5 T100,100"],
[3, "M50,50 t12.5,12.5 t37.5,37.5"],
[3, "M50,50 t12.5,12.5 T100,100"],
[3, "M50,50 Q50,50,62.5,62.5 t37.5,37.5"],
[3, "M50,50 Q50,50,62.5,62.5 T100,100"],
[3, "M50,50 q0,0,12.5,12.5 t37.5,37.5"],
[3, "M50,50 q0,0,12.5,12.5 T100,100"],
// These leave the cubic implied control point at 125,125.
[2, "M50,50 C10,10,75,75,100,100"],
[2, "M50,50 c10,10,25,25,50,50"],
[2, "M50,50 S75,75,100,100"],
[2, "M50,50 s25,25,50,50"],
[3, "M50,50 S10,10,75,75 S75,75,100,100"],
[3, "M50,50 S10,10,75,75 s0,0,25,25"],
[3, "M50,50 s10,10,25,25 S75,75,100,100"],
[3, "M50,50 s10,10,25,25 s0,0,25,25"],
[3, "M50,50 C10,10,20,20,75,75 S75,75,100,100"],
[3, "M50,50 C10,10,20,20,75,75 s0,0,25,25"],
[3, "M50,50 c10,10,20,20,25,25 S75,75,100,100"],
[3, "M50,50 c10,10,20,20,25,25 s0,0,25,25"]
];
// These are all of the suffixes whose result is not dependent on whether the
// preceding segment types are quadratic or cubic types. Each entry is:
//
// "<fromType><toType>": [fromArguments,
// toArguments,
// expectedArguments,
// expectedArgumentsAdditive]
//
// As an example:
//
// "Mm": [[10, 20], [30, 40], [-30, -20], [-120, -100]]
//
// This will testing interpolating between "M10,20" and "m30,40". All of the
// these tests assume that the current point is left at 100,100. So the above
// entry represents two kinds of tests, one where additive and one not:
//
// <path d="... M10,20">
// <animate attributeName="d" from="... M10,20" to="... m30,40"/>
// </path>
//
// and
//
// <path d="... M10,20">
// <animate attributeName="d" from="... M10,20" to="... m30,40"
// additive="sum"/>
// </path>
//
// where the "..." is some prefix that leaves the current point at 100,100.
// Each of the suffixes here in gSuffixes will be paired with each of the
// prefixes in gPrefixes, all of which leave the current point at 100,100.
// (Thus the above two tests for interpolating between "M" and "m" will be
// performed many times, with different preceding commands.)
//
// The expected result of the non-additive test is "m-30,-20". Since the
// animation is from an absolute moveto to a relative moveto, we first
// convert the "M10,20" into its relative form, which is "m-90,-80" due to the
// current point being 100,100. Half way through the animation between
// "m-90,-80" and "m30,40" is thus "m-30,-20".
//
// The expected result of the additive test is "m-120,-100". We take the
// halfway value of the animation, "m-30,-20" and add it on to the underlying
// value. Since the underlying value "M10,20" is an absolute moveto, we first
// convert it to relative, "m-90,-80", and then add the "m-30,-20" to it,
// giving us the result "m-120,-100".
var gSuffixes = {
// Same path segment type, no conversion required.
MM: [[10, 20], [30, 40], [20, 30], [30, 50]],
mm: [[10, 20], [30, 40], [20, 30], [30, 50]],
LL: [[10, 20], [30, 40], [20, 30], [30, 50]],
ll: [[10, 20], [30, 40], [20, 30], [30, 50]],
CC: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120],
[40, 50, 60, 70, 80, 90], [50, 70, 90, 110, 130, 150]],
cc: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120],
[40, 50, 60, 70, 80, 90], [50, 70, 90, 110, 130, 150]],
QQ: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]],
qq: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]],
AA: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100],
[35, 45, 55, 0, 0, 65, 75], [45, 65, 85, 0, 0, 105, 125]],
aa: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100],
[35, 45, 55, 0, 0, 65, 75], [45, 65, 85, 0, 0, 105, 125]],
HH: [[10], [20], [15], [25]],
hh: [[10], [20], [15], [25]],
VV: [[10], [20], [15], [25]],
vv: [[10], [20], [15], [25]],
SS: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]],
ss: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]],
TT: [[10, 20], [30, 40], [20, 30], [30, 50]],
tt: [[10, 20], [30, 40], [20, 30], [30, 50]],
// Relative <-> absolute conversion.
Mm: [[10, 20], [30, 40], [-30, -20], [-120, -100]],
mM: [[10, 20], [30, 40], [70, 80], [180, 200]],
Ll: [[10, 20], [30, 40], [-30, -20], [-120, -100]],
lL: [[10, 20], [30, 40], [70, 80], [180, 200]],
Cc: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120],
[-10, 0, 10, 20, 30, 40], [-100, -80, -60, -40, -20, 0]],
cC: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120],
[90, 100, 110, 120, 130, 140], [200, 220, 240, 260, 280, 300]],
Qq: [[10, 20, 30, 40], [50, 60, 70, 80],
[-20, -10, 0, 10], [-110, -90, -70, -50]],
qQ: [[10, 20, 30, 40], [50, 60, 70, 80],
[80, 90, 100, 110], [190, 210, 230, 250]],
Aa: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100],
[35, 45, 55, 0, 0, 15, 25], [45, 65, 85, 0, 0, -45, -25]],
aA: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100],
[35, 45, 55, 0, 0, 115, 125], [45, 65, 85, 0, 0, 255, 275]],
Hh: [[10], [20], [-35], [-125]],
hH: [[10], [20], [65], [175]],
Vv: [[10], [20], [-35], [-125]],
vV: [[10], [20], [65], [175]],
Tt: [[10, 20], [30, 40], [-30,-20], [-120, -100]],
tT: [[10, 20], [30, 40], [70, 80], [180, 200]],
Ss: [[10, 20, 30, 40], [50, 60, 70, 80],
[-20, -10, 0, 10], [-110, -90, -70, -50]],
sS: [[10, 20, 30, 40], [50, 60, 70, 80],
[80, 90, 100, 110], [190, 210, 230, 250]]
};
// Returns an array of property names that exist on an SVGPathSeg object
// corresponding to the given segment type, in the order that they would
// be present in a path data string.
function argumentNames(aType)
{
return gArgumentNames[aType.toUpperCase()];
}
// Creates and returns a new element and sets some attributes on it.
function newElement(aNamespaceURI, aLocalName, aAttributes)
{
var e = document.createElementNS(aNamespaceURI, aLocalName);
if (aAttributes) {
for (let [name, value] in Iterator(aAttributes)) {
e.setAttribute(name, value);
}
}
return e;
}
// Creates and returns a new SVG element and sets some attributes on it.
function newSVGElement(aLocalName, aAttributes)
{
return newElement("http://www.w3.org/2000/svg", aLocalName, aAttributes);
}
// Creates a subtest and adds it to the document.
//
// * aPrefixLength/aPrefix the prefix to use
// * aFromType/aFromArguments the segment to interpolate from
// * aToType/aToArguments the segment to interpolate to
// * aExpectedType/aExpectedArguments the expected result of the interpolated
// segment half way through the animation
// duration
// * aAdditive whether the subtest is for an additive
// animation
function addTest(aPrefixLength, aPrefix, aFromType, aFromArguments,
aToType, aToArguments, aExpectedType, aExpectedArguments,
aAdditive)
{
var fromPath = aPrefix + aFromType + aFromArguments,
toPath = aPrefix + aToType + aToArguments;
var path = newSVGElement("path", { d: fromPath });
var animate =
newSVGElement("animate", { attributeName: "d",
from: fromPath,
to: toPath,
dur: "8s",
additive: aAdditive ? "sum" : "replace" });
path.appendChild(animate);
gSVG.appendChild(path);
gTests.push({ element: path,
prefixLength: aPrefixLength,
from: fromPath,
to: toPath,
toType: aToType,
expectedType: aExpectedType,
expected: aExpectedArguments,
usesAddition: aAdditive });
}
// Generates an array of path segment arguments for the given type. aOffset
// is a number to add on to all non-Boolean segment arguments.
function generatePathSegmentArguments(aType, aOffset)
{
var args = new Array(argumentNames(aType).length);
for (let i = 0; i < args.length; i++) {
args[i] = i * 10 + aOffset;
}
if (aType == "A" || aType == "a") {
args[3] = 0;
args[4] = 0;
}
return args;
}
// Returns whether interpolating between the two given types is valid.
function isValidInterpolation(aFromType, aToType)
{
return aFromType.toUpperCase() == aToType.toUpperCase();
}
// Runs the test.
function run()
{
for each (let additive in [false, true]) {
let indexOfExpectedArguments = additive ? 3 : 2;
// Add subtests for each combination of prefix and suffix, and additive
// or not.
for (let [typePair, suffixEntry] in Iterator(gSuffixes)) {
let fromType = typePair[0],
toType = typePair[1],
fromArguments = suffixEntry[0],
toArguments = suffixEntry[1],
expectedArguments = suffixEntry[indexOfExpectedArguments];
for each (let prefixEntry in gPrefixes) {
let [prefixLength, prefix] = prefixEntry;
addTest(prefixLength, prefix, fromType, fromArguments,
toType, toArguments, toType, expectedArguments, additive);
}
}
// Test that differences in arc flag parameters cause the
// interpolation/addition not to occur.
addTest(1, "M100,100",
"A", [10, 20, 30, 0, 0, 40, 50],
"a", [60, 70, 80, 0, 1, 90, 100],
"a", [60, 70, 80, 0, 1, 90, 100], additive);
addTest(1, "M100,100",
"A", [10, 20, 30, 0, 0, 40, 50],
"a", [60, 70, 80, 1, 0, 90, 100],
"a", [60, 70, 80, 1, 0, 90, 100], additive);
// Test all pairs of segment types that cannot be interpolated between.
for each (let fromType in gTypes) {
let fromArguments = generatePathSegmentArguments(fromType, 0);
for each (let toType in gTypes) {
if (!isValidInterpolation(fromType, toType)) {
let toArguments = generatePathSegmentArguments(toType, 1000);
addTest(1, "M100,100", fromType, fromArguments,
toType, toArguments, toType, toArguments, additive);
}
}
}
}
// Move the document time to half way through the animations.
gSVG.setCurrentTime(4);
// Inspect the results of each subtest.
for each (let test in gTests) {
let list = test.element.animatedPathSegList;
is(list.numberOfItems, test.prefixLength + 1,
"Length of animatedPathSegList for interpolation " +
(test.usesAddition ? "with addition " : "") +
" from " + test.from + " to " + test.to);
let seg = list.getItem(list.numberOfItems - 1);
let propertyNames = argumentNames(test.expectedType);
let actual = [];
for (let i = 0; i < test.expected.length; i++) {
actual.push(+seg[propertyNames[i]]);
}
is(seg.pathSegTypeAsLetter + actual, test.expectedType + test.expected,
"Path segment for interpolation " +
(test.usesAddition ? "with addition " : "") +
" from " + test.from + " to " + test.to);
}
SimpleTest.finish();
}
window.addEventListener("load", run, false);
]]></script>
</body>
</html>