diff --git a/content/svg/content/src/SVGPathData.cpp b/content/svg/content/src/SVGPathData.cpp index eadf5eb796a..ccfef313387 100644 --- a/content/svg/content/src/SVGPathData.cpp +++ b/content/svg/content/src/SVGPathData.cpp @@ -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 *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 *aLengths) const PRBool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray *aOutput) const { - double distRunningTotal = 0.0; SVGPathTraversalState state; aOutput->Clear(); @@ -186,6 +186,7 @@ SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray *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 *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]); diff --git a/content/svg/content/src/SVGPathData.h b/content/svg/content/src/SVGPathData.h index 64d885744f4..03c147d7b20 100644 --- a/content/svg/content/src/SVGPathData.h +++ b/content/svg/content/src/SVGPathData.h @@ -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 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 diff --git a/content/svg/content/src/SVGPathSegListSMILType.cpp b/content/svg/content/src/SVGPathSegListSMILType.cpp index b98f0c15d45..ddd0ecca777 100644 --- a/content/svg/content/src/SVGPathSegListSMILType.cpp +++ b/content/svg/content/src/SVGPathSegListSMILType.cpp @@ -89,6 +89,240 @@ SVGPathSegListSMILType::IsEqual(const nsSMILValue& aLeft, *static_cast(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(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(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; } - diff --git a/content/svg/content/src/SVGPathSegUtils.cpp b/content/svg/content/src/SVGPathSegUtils.cpp index d9786494f93..26b5c452622 100644 --- a/content/svg/content/src/SVGPathSegUtils.cpp +++ b/content/svg/content/src/SVGPathSegUtils.cpp @@ -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); } - diff --git a/content/svg/content/src/SVGPathSegUtils.h b/content/svg/content/src/SVGPathSegUtils.h index b011e7ec934..cba21eddc39 100644 --- a/content/svg/content/src/SVGPathSegUtils.h +++ b/content/svg/content/src/SVGPathSegUtils.h @@ -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 diff --git a/content/svg/content/test/Makefile.in b/content/svg/content/test/Makefile.in index 8c166ee2f4e..ea5c953c86d 100644 --- a/content/svg/content/test/Makefile.in +++ b/content/svg/content/test/Makefile.in @@ -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) diff --git a/content/svg/content/test/test_pathAnimInterpolation.xhtml b/content/svg/content/test/test_pathAnimInterpolation.xhtml new file mode 100644 index 00000000000..03ee27e261c --- /dev/null +++ b/content/svg/content/test/test_pathAnimInterpolation.xhtml @@ -0,0 +1,345 @@ + + + + Test interpolation between different path segment types + + + + + +Mozilla Bug 619498 + + + +