mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 619498 - Part 2: Support interpolation and addition of similar SVG path segment types (v4) r=dholbert
This commit is contained in:
parent
f7c58f9131
commit
6f9c7ae6e8
@ -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]);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
345
content/svg/content/test/test_pathAnimInterpolation.xhtml
Normal file
345
content/svg/content/test/test_pathAnimInterpolation.xhtml
Normal 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>
|
Loading…
Reference in New Issue
Block a user