Bug 1714238 - Use a single SVG path representation. r=boris,longsonr,webidl,smaug

This removes a bunch of special and complex code from the SVG path code,
and simplifies fixing stuff like bug 1903361.

Differential Revision: https://phabricator.services.mozilla.com/D214411
This commit is contained in:
Emilio Cobos Álvarez 2024-06-21 16:16:01 +00:00
Родитель 7ec8891f51
Коммит b1a45fd599
24 изменённых файлов: 203 добавлений и 2190 удалений

Просмотреть файл

@ -35,8 +35,8 @@ class CanvasPath final : public nsWrapperCache {
static already_AddRefed<CanvasPath> Constructor(const GlobalObject& aGlobal);
static already_AddRefed<CanvasPath> Constructor(const GlobalObject& aGlobal,
CanvasPath& aCanvasPath);
static already_AddRefed<CanvasPath> Constructor(const GlobalObject& aGlobal,
const nsAString& aPathString);
static already_AddRefed<CanvasPath> Constructor(
const GlobalObject& aGlobal, const nsACString& aPathString);
void ClosePath();
void MoveTo(double x, double y);

Просмотреть файл

@ -6714,7 +6714,7 @@ already_AddRefed<CanvasPath> CanvasPath::Constructor(
}
already_AddRefed<CanvasPath> CanvasPath::Constructor(
const GlobalObject& aGlobal, const nsAString& aPathString) {
const GlobalObject& aGlobal, const nsACString& aPathString) {
RefPtr<gfx::Path> tempPath = SVGContentUtils::GetPath(aPathString);
if (!tempPath) {
return Constructor(aGlobal);

Просмотреть файл

@ -20,18 +20,10 @@ using namespace mozilla::dom;
namespace mozilla {
nsresult SVGAnimatedPathSegList::SetBaseValueString(const nsAString& aValue) {
SVGPathData newBaseValue;
// The spec says that the path data is parsed and accepted up to the first
// error encountered, so we don't return early if an error occurs. However,
// we do want to throw any error code from setAttribute if there's a problem.
nsresult rv = newBaseValue.SetValueFromString(aValue);
// We don't need to call DidChange* here - we're only called by
// SVGElement::ParseAttribute under Element::SetAttr,
// which takes care of notifying.
mBaseVal.SwapWith(newBaseValue);
return rv;
return mBaseVal.SetValueFromString(NS_ConvertUTF16toUTF8(aValue));
}
void SVGAnimatedPathSegList::ClearBaseValue() {
@ -52,14 +44,9 @@ nsresult SVGAnimatedPathSegList::SetAnimValue(const SVGPathData& aNewAnimValue,
if (!mAnimVal) {
mAnimVal = MakeUnique<SVGPathData>();
}
nsresult rv = mAnimVal->CopyFrom(aNewAnimValue);
if (NS_FAILED(rv)) {
// OOM. We clear the animation and, importantly, ClearAnimValue() ensures
// that mAnimVal's DOM wrapper (if any) is kept in sync!
ClearAnimValue(aElement);
}
*mAnimVal = aNewAnimValue;
aElement->DidAnimatePathSegList();
return rv;
return NS_OK;
}
void SVGAnimatedPathSegList::ClearAnimValue(SVGElement* aElement) {
@ -80,7 +67,7 @@ nsresult SVGAnimatedPathSegList::SMILAnimatedPathSegList::ValueFromString(
SMILValue& aValue, bool& aPreventCachingOfSandwich) const {
SMILValue val(SVGPathSegListSMILType::Singleton());
SVGPathDataAndInfo* list = static_cast<SVGPathDataAndInfo*>(val.mU.mPtr);
nsresult rv = list->SetValueFromString(aStr);
nsresult rv = list->SetValueFromString(NS_ConvertUTF16toUTF8(aStr));
if (NS_SUCCEEDED(rv)) {
list->SetElement(mElement);
aValue = std::move(val);
@ -93,16 +80,11 @@ SMILValue SVGAnimatedPathSegList::SMILAnimatedPathSegList::GetBaseValue()
// To benefit from Return Value Optimization and avoid copy constructor calls
// due to our use of return-by-value, we must return the exact same object
// from ALL return points. This function must only return THIS variable:
SMILValue val;
SMILValue tmp(SVGPathSegListSMILType::Singleton());
auto* list = static_cast<SVGPathDataAndInfo*>(tmp.mU.mPtr);
nsresult rv = list->CopyFrom(mVal->mBaseVal);
if (NS_SUCCEEDED(rv)) {
list->SetElement(mElement);
val = std::move(tmp);
}
return val;
list->CopyFrom(mVal->mBaseVal);
list->SetElement(mElement);
return tmp;
}
nsresult SVGAnimatedPathSegList::SMILAnimatedPathSegList::SetAnimValue(

Просмотреть файл

@ -38,10 +38,6 @@ class SVGElement;
* to know or worry about wrappers (or forget about them!) for the most part.
*/
class SVGAnimatedPathSegList final {
// friends so that they can get write access to mBaseVal and mAnimVal
friend class dom::DOMSVGPathSeg;
friend class dom::DOMSVGPathSegList;
public:
SVGAnimatedPathSegList() = default;

Просмотреть файл

@ -60,7 +60,9 @@ void SVGAttrValueWrapper::ToString(const SVGAnimatedNumberPair* aNumberPair,
/*static*/
void SVGAttrValueWrapper::ToString(const SVGPathData* aPathData,
nsAString& aResult) {
aPathData->GetValueAsString(aResult);
nsAutoCString result;
aPathData->GetValueAsString(result);
CopyUTF8toUTF16(result, aResult);
}
/*static*/

Просмотреть файл

@ -873,10 +873,9 @@ float SVGContentUtils::CoordToFloat(const SVGElement* aContent,
}
already_AddRefed<gfx::Path> SVGContentUtils::GetPath(
const nsAString& aPathString) {
SVGPathData pathData;
SVGPathDataParser parser(aPathString, &pathData);
if (!parser.Parse()) {
const nsACString& aPathString) {
SVGPathData pathData(aPathString);
if (pathData.IsEmpty()) {
return nullptr;
}

Просмотреть файл

@ -338,7 +338,7 @@ class SVGContentUtils {
* string formatted as an SVG path
*/
static already_AddRefed<mozilla::gfx::Path> GetPath(
const nsAString& aPathString);
const nsACString& aPathString);
/**
* Returns true if aContent is one of the elements whose stroke is guaranteed

Просмотреть файл

@ -1108,19 +1108,11 @@ bool SVGElement::UpdateDeclarationBlockFromPath(
const SVGPathData& pathData =
aValToUse == ValToUse::Anim ? aPath.GetAnimValue() : aPath.GetBaseValue();
// SVGPathData::mData is fallible but rust binding accepts nsTArray only, so
// we need to point to one or the other. Fortunately, fallible and infallible
// array types can be implicitly converted provided they are const.
//
// FIXME: here we just convert the data structure from cpp verion into rust
// version. We don't do any normalization for the path data from d attribute.
// Based on the current discussion of https://github.com/w3c/svgwg/issues/321,
// we may have to convert the relative commands into absolute commands.
// The normalization should be fixed in Bug 1489392. Besides, Bug 1714238
// will use the same data structure, so we may simplify this more.
const nsTArray<float>& asInFallibleArray = pathData.RawData();
// The normalization should be fixed in Bug 1489392.
Servo_DeclarationBlock_SetPathValue(&aBlock, eCSSProperty_d,
&asInFallibleArray);
&pathData.RawData());
return true;
}

Просмотреть файл

@ -224,15 +224,11 @@ void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr() {
mPathSourceType = ePathSourceType_PathAttr;
// Generate Path from |path| attr
SVGPathData path;
SVGPathDataParser pathParser(pathSpec, &path);
SVGPathData path{NS_ConvertUTF16toUTF8(pathSpec)};
// We ignore any failure returned from Parse() since the SVG spec says to
// accept all segments up to the first invalid token. Instead we must
// explicitly check that the parse produces at least one path segment (if
// the path data doesn't begin with a valid "M", then it's invalid).
pathParser.Parse();
if (!path.Length()) {
// We must explicitly check that the parse produces at least one path segment
// (if the path data doesn't begin with a valid "M", then it's invalid).
if (path.IsEmpty()) {
return;
}

Просмотреть файл

@ -26,140 +26,29 @@ using namespace mozilla::gfx;
namespace mozilla {
static inline bool IsMoveto(uint16_t aSegType) {
return aSegType == PATHSEG_MOVETO_ABS || aSegType == PATHSEG_MOVETO_REL;
}
static inline bool IsValidType(uint16_t aSegType) {
return SVGPathSegUtils::IsValidType(aSegType);
}
static inline bool IsClosePath(uint16_t aSegType) {
return aSegType == PATHSEG_CLOSEPATH;
}
nsresult SVGPathData::CopyFrom(const SVGPathData& rhs) {
if (!mData.Assign(rhs.mData, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
void SVGPathData::GetValueAsString(nsAString& aValue) const {
// we need this function in DidChangePathSegList
aValue.Truncate();
if (!Length()) {
return;
}
uint32_t i = 0;
for (;;) {
nsAutoString segAsString;
SVGPathSegUtils::GetValueAsString(&mData[i], segAsString);
// We ignore OOM, since it's not useful for us to return an error.
aValue.Append(segAsString);
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
if (i >= mData.Length()) {
MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
return;
}
aValue.Append(' ');
}
}
nsresult SVGPathData::SetValueFromString(const nsAString& aValue) {
nsresult SVGPathData::SetValueFromString(const nsACString& aValue) {
// We don't use a temp variable since the spec says to parse everything up to
// the first error. We still return any error though so that callers know if
// there's a problem.
SVGPathDataParser pathParser(aValue, this);
return pathParser.Parse() ? NS_OK : NS_ERROR_DOM_SYNTAX_ERR;
bool ok = Servo_SVGPathData_Parse(&aValue, &mData);
return ok ? NS_OK : NS_ERROR_DOM_SYNTAX_ERR;
}
nsresult SVGPathData::AppendSeg(uint32_t aType, ...) {
uint32_t oldLength = mData.Length();
uint32_t newLength = oldLength + 1 + SVGPathSegUtils::ArgCountForType(aType);
if (!mData.SetLength(newLength, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
mData[oldLength] = SVGPathSegUtils::EncodeType(aType);
va_list args;
va_start(args, aType);
for (uint32_t i = oldLength + 1; i < newLength; ++i) {
// NOTE! 'float' is promoted to 'double' when passed through '...'!
mData[i] = float(va_arg(args, double));
}
va_end(args);
return NS_OK;
void SVGPathData::GetValueAsString(nsACString& aValue) const {
Servo_SVGPathData_ToString(&mData, &aValue);
}
float SVGPathData::GetPathLength() const {
SVGPathTraversalState state;
uint32_t i = 0;
while (i < mData.Length()) {
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
for (const auto& segment : AsSpan()) {
SVGPathSegUtils::TraversePathSegment(segment, state);
}
MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
return state.length;
}
#ifdef DEBUG
uint32_t SVGPathData::CountItems() const {
uint32_t i = 0, count = 0;
while (i < mData.Length()) {
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
count++;
}
MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
return count;
}
#endif
bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
FallibleTArray<double>* aOutput) const {
SVGPathTraversalState state;
aOutput->Clear();
uint32_t i = 0;
while (i < mData.Length()) {
uint32_t segType = SVGPathSegUtils::DecodeType(mData[i]);
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
// With degenerately large point coordinates, TraversePathSegment can fail
// and end up producing NaNs.
if (!std::isfinite(state.length)) {
return false;
}
// 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
// the duration...':
//
// http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement
//
// This is important in the non-default case of calcMode="linear". In
// this case an equal amount of time is spent on each path segment,
// except on moveto segments which are jumped over immediately.
if (i == 0 || !IsMoveto(segType)) {
if (!aOutput->AppendElement(state.length, fallible)) {
return false;
}
}
i += 1 + SVGPathSegUtils::ArgCountForType(segType);
}
MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt?");
return true;
return GetDistancesFromOriginToEndsOfVisibleSegments(AsSpan(), aOutput);
}
/* static */
@ -191,33 +80,13 @@ bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
return true;
}
uint32_t SVGPathData::GetPathSegAtLength(float aDistance) const {
/* static */
uint32_t SVGPathData::GetPathSegAtLength(Span<const StylePathCommand> aPath,
float aDistance) {
// TODO [SVGWG issue] get specified what happen if 'aDistance' < 0, or
// 'aDistance' > the length of the path, or the seg list is empty.
// Return -1? Throwing would better help authors avoid tricky bugs (DOM
// could do that if we return -1).
uint32_t i = 0, segIndex = 0;
SVGPathTraversalState state;
while (i < mData.Length()) {
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
if (state.length >= aDistance) {
return segIndex;
}
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
segIndex++;
}
MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
return std::max(1U, segIndex) -
1; // -1 because while loop takes us 1 too far
}
/* static */
uint32_t SVGPathData::GetPathSegAtLength(Span<const StylePathCommand> aPath,
float aDistance) {
uint32_t segIndex = 0;
SVGPathTraversalState state;
@ -294,237 +163,7 @@ static void ApproximateZeroLengthSubpathSquareCaps(PathBuilder* aPB,
already_AddRefed<Path> SVGPathData::BuildPath(PathBuilder* aBuilder,
StyleStrokeLinecap aStrokeLineCap,
Float aStrokeWidth) const {
if (mData.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData[0]))) {
return nullptr; // paths without an initial moveto are invalid
}
bool hasLineCaps = aStrokeLineCap != StyleStrokeLinecap::Butt;
bool subpathHasLength = false; // visual length
bool subpathContainsNonMoveTo = false;
uint32_t segType = PATHSEG_UNKNOWN;
uint32_t prevSegType = PATHSEG_UNKNOWN;
Point pathStart(0.0, 0.0); // start point of [sub]path
Point segStart(0.0, 0.0);
Point segEnd;
Point cp1, cp2; // previous bezier's control points
Point tcp1, tcp2; // temporaries
// Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
// then cp2 is its second control point. If the previous segment was a
// quadratic curve, then cp1 is its (only) control point.
uint32_t i = 0;
while (i < mData.Length()) {
segType = SVGPathSegUtils::DecodeType(mData[i++]);
uint32_t argCount = SVGPathSegUtils::ArgCountForType(segType);
switch (segType) {
case PATHSEG_CLOSEPATH:
// set this early to allow drawing of square caps for "M{x},{y} Z":
subpathContainsNonMoveTo = true;
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
segEnd = pathStart;
aBuilder->Close();
break;
case PATHSEG_MOVETO_ABS:
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
pathStart = segEnd = Point(mData[i], mData[i + 1]);
aBuilder->MoveTo(segEnd);
subpathHasLength = false;
break;
case PATHSEG_MOVETO_REL:
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
pathStart = segEnd = segStart + Point(mData[i], mData[i + 1]);
aBuilder->MoveTo(segEnd);
subpathHasLength = false;
break;
case PATHSEG_LINETO_ABS:
segEnd = Point(mData[i], mData[i + 1]);
if (segEnd != segStart) {
subpathHasLength = true;
aBuilder->LineTo(segEnd);
}
break;
case PATHSEG_LINETO_REL:
segEnd = segStart + Point(mData[i], mData[i + 1]);
if (segEnd != segStart) {
subpathHasLength = true;
aBuilder->LineTo(segEnd);
}
break;
case PATHSEG_CURVETO_CUBIC_ABS:
cp1 = Point(mData[i], mData[i + 1]);
cp2 = Point(mData[i + 2], mData[i + 3]);
segEnd = Point(mData[i + 4], mData[i + 5]);
if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
subpathHasLength = true;
aBuilder->BezierTo(cp1, cp2, segEnd);
}
break;
case PATHSEG_CURVETO_CUBIC_REL:
cp1 = segStart + Point(mData[i], mData[i + 1]);
cp2 = segStart + Point(mData[i + 2], mData[i + 3]);
segEnd = segStart + Point(mData[i + 4], mData[i + 5]);
if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
subpathHasLength = true;
aBuilder->BezierTo(cp1, cp2, segEnd);
}
break;
case PATHSEG_CURVETO_QUADRATIC_ABS:
cp1 = Point(mData[i], mData[i + 1]);
// Convert quadratic curve to cubic curve:
tcp1 = segStart + (cp1 - segStart) * 2 / 3;
segEnd = Point(mData[i + 2], mData[i + 3]); // set before setting tcp2!
tcp2 = cp1 + (segEnd - cp1) / 3;
if (segEnd != segStart || segEnd != cp1) {
subpathHasLength = true;
aBuilder->BezierTo(tcp1, tcp2, segEnd);
}
break;
case PATHSEG_CURVETO_QUADRATIC_REL:
cp1 = segStart + Point(mData[i], mData[i + 1]);
// Convert quadratic curve to cubic curve:
tcp1 = segStart + (cp1 - segStart) * 2 / 3;
segEnd = segStart +
Point(mData[i + 2], mData[i + 3]); // set before setting tcp2!
tcp2 = cp1 + (segEnd - cp1) / 3;
if (segEnd != segStart || segEnd != cp1) {
subpathHasLength = true;
aBuilder->BezierTo(tcp1, tcp2, segEnd);
}
break;
case PATHSEG_ARC_ABS:
case PATHSEG_ARC_REL: {
Point radii(mData[i], mData[i + 1]);
segEnd = Point(mData[i + 5], mData[i + 6]);
if (segType == PATHSEG_ARC_REL) {
segEnd += segStart;
}
if (segEnd != segStart) {
subpathHasLength = true;
if (radii.x == 0.0f || radii.y == 0.0f) {
aBuilder->LineTo(segEnd);
} else {
SVGArcConverter converter(segStart, segEnd, radii, mData[i + 2],
mData[i + 3] != 0, mData[i + 4] != 0);
while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
aBuilder->BezierTo(cp1, cp2, segEnd);
}
}
}
break;
}
case PATHSEG_LINETO_HORIZONTAL_ABS:
segEnd = Point(mData[i], segStart.y);
if (segEnd != segStart) {
subpathHasLength = true;
aBuilder->LineTo(segEnd);
}
break;
case PATHSEG_LINETO_HORIZONTAL_REL:
segEnd = segStart + Point(mData[i], 0.0f);
if (segEnd != segStart) {
subpathHasLength = true;
aBuilder->LineTo(segEnd);
}
break;
case PATHSEG_LINETO_VERTICAL_ABS:
segEnd = Point(segStart.x, mData[i]);
if (segEnd != segStart) {
subpathHasLength = true;
aBuilder->LineTo(segEnd);
}
break;
case PATHSEG_LINETO_VERTICAL_REL:
segEnd = segStart + Point(0.0f, mData[i]);
if (segEnd != segStart) {
subpathHasLength = true;
aBuilder->LineTo(segEnd);
}
break;
case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2
: segStart;
cp2 = Point(mData[i], mData[i + 1]);
segEnd = Point(mData[i + 2], mData[i + 3]);
if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
subpathHasLength = true;
aBuilder->BezierTo(cp1, cp2, segEnd);
}
break;
case PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2
: segStart;
cp2 = segStart + Point(mData[i], mData[i + 1]);
segEnd = segStart + Point(mData[i + 2], mData[i + 3]);
if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
subpathHasLength = true;
aBuilder->BezierTo(cp1, cp2, segEnd);
}
break;
case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1
: segStart;
// Convert quadratic curve to cubic curve:
tcp1 = segStart + (cp1 - segStart) * 2 / 3;
segEnd = Point(mData[i], mData[i + 1]); // set before setting tcp2!
tcp2 = cp1 + (segEnd - cp1) / 3;
if (segEnd != segStart || segEnd != cp1) {
subpathHasLength = true;
aBuilder->BezierTo(tcp1, tcp2, segEnd);
}
break;
case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1
: segStart;
// Convert quadratic curve to cubic curve:
tcp1 = segStart + (cp1 - segStart) * 2 / 3;
segEnd = segStart +
Point(mData[i], mData[i + 1]); // changed before setting tcp2!
tcp2 = cp1 + (segEnd - cp1) / 3;
if (segEnd != segStart || segEnd != cp1) {
subpathHasLength = true;
aBuilder->BezierTo(tcp1, tcp2, segEnd);
}
break;
default:
MOZ_ASSERT_UNREACHABLE("Bad path segment type");
return nullptr; // according to spec we'd use everything up to the bad
// seg anyway
}
subpathContainsNonMoveTo = !IsMoveto(segType);
i += argCount;
prevSegType = segType;
segStart = segEnd;
}
MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
MOZ_ASSERT(prevSegType == segType,
"prevSegType should be left at the final segType");
MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
return aBuilder->Finish();
return BuildPath(AsSpan(), aBuilder, aStrokeLineCap, aStrokeWidth);
}
#undef MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
@ -887,256 +526,7 @@ ComputeSegAnglesAndCorrectRadii(const Point& aSegStart, const Point& aSegEnd,
}
void SVGPathData::GetMarkerPositioningData(nsTArray<SVGMark>* aMarks) const {
// This code should assume that ANY type of segment can appear at ANY index.
// It should also assume that segments such as M and Z can appear in weird
// places, and repeat multiple times consecutively.
// info on current [sub]path (reset every M command):
Point pathStart(0.0, 0.0);
float pathStartAngle = 0.0f;
uint32_t pathStartIndex = 0;
// info on previous segment:
uint16_t prevSegType = PATHSEG_UNKNOWN;
Point prevSegEnd(0.0, 0.0);
float prevSegEndAngle = 0.0f;
Point prevCP; // if prev seg was a bezier, this was its last control point
uint32_t i = 0;
while (i < mData.Length()) {
// info on current segment:
uint16_t segType =
SVGPathSegUtils::DecodeType(mData[i++]); // advances i to args
Point& segStart = prevSegEnd;
Point segEnd;
float segStartAngle, segEndAngle;
switch (segType) // to find segStartAngle, segEnd and segEndAngle
{
case PATHSEG_CLOSEPATH:
segEnd = pathStart;
segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
break;
case PATHSEG_MOVETO_ABS:
case PATHSEG_MOVETO_REL:
if (segType == PATHSEG_MOVETO_ABS) {
segEnd = Point(mData[i], mData[i + 1]);
} else {
segEnd = segStart + Point(mData[i], mData[i + 1]);
}
pathStart = segEnd;
pathStartIndex = aMarks->Length();
// If authors are going to specify multiple consecutive moveto commands
// with markers, me might as well make the angle do something useful:
segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
i += 2;
break;
case PATHSEG_LINETO_ABS:
case PATHSEG_LINETO_REL:
if (segType == PATHSEG_LINETO_ABS) {
segEnd = Point(mData[i], mData[i + 1]);
} else {
segEnd = segStart + Point(mData[i], mData[i + 1]);
}
segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
i += 2;
break;
case PATHSEG_CURVETO_CUBIC_ABS:
case PATHSEG_CURVETO_CUBIC_REL: {
Point cp1, cp2; // control points
if (segType == PATHSEG_CURVETO_CUBIC_ABS) {
cp1 = Point(mData[i], mData[i + 1]);
cp2 = Point(mData[i + 2], mData[i + 3]);
segEnd = Point(mData[i + 4], mData[i + 5]);
} else {
cp1 = segStart + Point(mData[i], mData[i + 1]);
cp2 = segStart + Point(mData[i + 2], mData[i + 3]);
segEnd = segStart + Point(mData[i + 4], mData[i + 5]);
}
prevCP = cp2;
segStartAngle = AngleOfVector(
cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
segEndAngle = AngleOfVector(
segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
i += 6;
break;
}
case PATHSEG_CURVETO_QUADRATIC_ABS:
case PATHSEG_CURVETO_QUADRATIC_REL: {
Point cp1; // control point
if (segType == PATHSEG_CURVETO_QUADRATIC_ABS) {
cp1 = Point(mData[i], mData[i + 1]);
segEnd = Point(mData[i + 2], mData[i + 3]);
} else {
cp1 = segStart + Point(mData[i], mData[i + 1]);
segEnd = segStart + Point(mData[i + 2], mData[i + 3]);
}
prevCP = cp1;
segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
i += 4;
break;
}
case PATHSEG_ARC_ABS:
case PATHSEG_ARC_REL: {
float rx = mData[i];
float ry = mData[i + 1];
float angle = mData[i + 2];
bool largeArcFlag = mData[i + 3] != 0.0f;
bool sweepFlag = mData[i + 4] != 0.0f;
if (segType == PATHSEG_ARC_ABS) {
segEnd = Point(mData[i + 5], mData[i + 6]);
} else {
segEnd = segStart + Point(mData[i + 5], mData[i + 6]);
}
// See section F.6 of SVG 1.1 for details on what we're doing here:
// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
if (segStart == segEnd) {
// F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
// then this is equivalent to omitting the elliptical arc segment
// entirely." We take that very literally here, not adding a mark, and
// not even setting any of the 'prev' variables so that it's as if
// this arc had never existed; note the difference this will make e.g.
// if the arc is proceeded by a bezier curve and followed by a
// "smooth" bezier curve of the same degree!
i += 7;
continue;
}
// Below we have funny interleaving of F.6.6 (Correction of out-of-range
// radii) and F.6.5 (Conversion from endpoint to center
// parameterization) which is designed to avoid some unnecessary
// calculations.
if (rx == 0.0 || ry == 0.0) {
// F.6.6 step 1 - straight line or coincidental points
segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
i += 7;
break;
}
std::tie(rx, ry, segStartAngle, segEndAngle) =
ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle,
largeArcFlag, sweepFlag, rx, ry);
i += 7;
break;
}
case PATHSEG_LINETO_HORIZONTAL_ABS:
case PATHSEG_LINETO_HORIZONTAL_REL:
if (segType == PATHSEG_LINETO_HORIZONTAL_ABS) {
segEnd = Point(mData[i++], segStart.y);
} else {
segEnd = segStart + Point(mData[i++], 0.0f);
}
segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
break;
case PATHSEG_LINETO_VERTICAL_ABS:
case PATHSEG_LINETO_VERTICAL_REL:
if (segType == PATHSEG_LINETO_VERTICAL_ABS) {
segEnd = Point(segStart.x, mData[i++]);
} else {
segEnd = segStart + Point(0.0f, mData[i++]);
}
segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
break;
case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: {
Point cp1 = SVGPathSegUtils::IsCubicType(prevSegType)
? segStart * 2 - prevCP
: segStart;
Point cp2;
if (segType == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) {
cp2 = Point(mData[i], mData[i + 1]);
segEnd = Point(mData[i + 2], mData[i + 3]);
} else {
cp2 = segStart + Point(mData[i], mData[i + 1]);
segEnd = segStart + Point(mData[i + 2], mData[i + 3]);
}
prevCP = cp2;
segStartAngle = AngleOfVector(
cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
segEndAngle = AngleOfVector(
segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
i += 4;
break;
}
case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: {
Point cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType)
? segStart * 2 - prevCP
: segStart;
if (segType == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS) {
segEnd = Point(mData[i], mData[i + 1]);
} else {
segEnd = segStart + Point(mData[i], mData[i + 1]);
}
prevCP = cp1;
segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
i += 2;
break;
}
default:
// Leave any existing marks in aMarks so we have a visual indication of
// when things went wrong.
MOZ_ASSERT(false, "Unknown segment type - path corruption?");
return;
}
// Set the angle of the mark at the start of this segment:
if (aMarks->Length()) {
SVGMark& mark = aMarks->LastElement();
if (!IsMoveto(segType) && IsMoveto(prevSegType)) {
// start of new subpath
pathStartAngle = mark.angle = segStartAngle;
} else if (IsMoveto(segType) && !IsMoveto(prevSegType)) {
// end of a subpath
if (prevSegType != PATHSEG_CLOSEPATH) mark.angle = prevSegEndAngle;
} else {
if (!(segType == PATHSEG_CLOSEPATH && prevSegType == PATHSEG_CLOSEPATH))
mark.angle =
SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle);
}
}
// Add the mark at the end of this segment, and set its position:
// XXX(Bug 1631371) Check if this should use a fallible operation as it
// pretended earlier.
aMarks->AppendElement(SVGMark(static_cast<float>(segEnd.x),
static_cast<float>(segEnd.y), 0.0f,
SVGMark::eMid));
if (segType == PATHSEG_CLOSEPATH && prevSegType != PATHSEG_CLOSEPATH) {
aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle =
SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle);
}
prevSegType = segType;
prevSegEnd = segEnd;
prevSegEndAngle = segEndAngle;
}
MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
if (aMarks->Length()) {
if (prevSegType != PATHSEG_CLOSEPATH) {
aMarks->LastElement().angle = prevSegEndAngle;
}
aMarks->LastElement().type = SVGMark::eEnd;
aMarks->ElementAt(0).type = SVGMark::eStart;
}
return GetMarkerPositioningData(AsSpan(), aMarks);
}
// Basically, this is identical to the above function, but replace |mData| with
@ -1358,7 +748,8 @@ void SVGPathData::GetMarkerPositioningData(Span<const StylePathCommand> aPath,
}
size_t SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
return mData.ShallowSizeOfExcludingThis(aMallocSizeOf);
// TODO: measure mData if unshared?
return 0;
}
size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {

Просмотреть файл

@ -27,13 +27,6 @@ namespace mozilla {
struct SVGMark;
enum class StyleStrokeLinecap : uint8_t;
class SVGPathDataParser; // IWYU pragma: keep
namespace dom {
class DOMSVGPathSeg;
class DOMSVGPathSegList;
} // namespace dom
/**
* ATTENTION! WARNING! WATCH OUT!!
*
@ -82,11 +75,7 @@ class DOMSVGPathSegList;
*/
class SVGPathData {
friend class SVGAnimatedPathSegList;
friend class dom::DOMSVGPathSeg;
friend class dom::DOMSVGPathSegList;
friend class SVGPathDataParser;
// SVGPathDataParser will not keep wrappers in sync, so consumers
// are responsible for that!
friend class SVGPathDataAndInfo;
using DrawTarget = gfx::DrawTarget;
using Path = gfx::Path;
@ -96,60 +85,32 @@ class SVGPathData {
using CapStyle = gfx::CapStyle;
public:
using const_iterator = const float*;
SVGPathData() = default;
~SVGPathData() = default;
SVGPathData& operator=(const SVGPathData& aOther) {
mData.ClearAndRetainStorage();
// Best-effort, really.
Unused << mData.AppendElements(aOther.mData, fallible);
return *this;
explicit SVGPathData(const nsACString& aString) {
SetValueFromString(aString);
}
SVGPathData(const SVGPathData& aOther) { *this = aOther; }
SVGPathData& operator=(const SVGPathData&) = default;
SVGPathData(const SVGPathData&) = default;
SVGPathData& operator=(SVGPathData&&) = default;
SVGPathData(SVGPathData&&) = default;
// Used by SMILCompositor to check if the cached base val is out of date
bool operator==(const SVGPathData& rhs) const { return mData == rhs.mData; }
// Only methods that don't make/permit modification to this list are public.
// Only our friend classes can access methods that may change us.
/// This may return an incomplete string on OOM, but that's acceptable.
void GetValueAsString(nsAString& aValue) const;
void GetValueAsString(nsACString& aValue) const;
bool IsEmpty() const { return mData.IsEmpty(); }
Span<const StylePathCommand> AsSpan() const { return mData._0.AsSpan(); }
bool IsEmpty() const { return AsSpan().IsEmpty(); }
#ifdef DEBUG
/**
* This method iterates over the encoded segment data and counts the number
* of segments we currently have.
*/
uint32_t CountItems() const;
#endif
/**
* Returns the number of *floats* in the encoding array, and NOT the number
* of segments encoded in this object. (For that, see CountItems() above.)
*/
uint32_t Length() const { return mData.Length(); }
const nsTArray<float>& RawData() const { return mData; }
const float& operator[](uint32_t aIndex) const { return mData[aIndex]; }
// Used by SMILCompositor to check if the cached base val is out of date
bool operator==(const SVGPathData& rhs) const {
// We use memcmp so that we don't need to worry that the data encoded in
// the first float may have the same bit pattern as a NaN.
return mData.Length() == rhs.mData.Length() &&
memcmp(mData.Elements(), rhs.mData.Elements(),
mData.Length() * sizeof(float)) == 0;
}
bool SetCapacity(uint32_t aSize) {
return mData.SetCapacity(aSize, fallible);
}
void Compact() { mData.Compact(); }
StyleSVGPathData& RawData() { return mData; }
const StyleSVGPathData& RawData() const { return mData; }
float GetPathLength() const;
@ -207,9 +168,6 @@ class SVGPathData {
const CSSSize& aBasis, const gfx::Point& aOffset = gfx::Point(),
float aZoomFactor = 1.0);
const_iterator begin() const { return mData.Elements(); }
const_iterator end() const { return mData.Elements() + mData.Length(); }
// memory reporting methods
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
@ -222,28 +180,9 @@ class SVGPathData {
// can take care of keeping DOM wrappers in sync.
protected:
using iterator = float*;
nsresult SetValueFromString(const nsACString& aValue);
/**
* This may fail on OOM if the internal capacity needs to be increased, in
* which case the list will be left unmodified.
*/
nsresult CopyFrom(const SVGPathData& rhs);
void SwapWith(SVGPathData& aRhs) { mData.SwapElements(aRhs.mData); }
float& operator[](uint32_t aIndex) { return mData[aIndex]; }
/**
* This may fail (return false) on OOM if the internal capacity is being
* increased, in which case the list will be left unmodified.
*/
bool SetLength(uint32_t aLength) {
return mData.SetLength(aLength, fallible);
}
nsresult SetValueFromString(const nsAString& aValue);
void Clear() { mData.Clear(); }
void Clear() { mData = {}; }
// Our DOM wrappers have direct access to our mData, so they directly
// manipulate it rather than us implementing:
@ -255,10 +194,7 @@ class SVGPathData {
nsresult AppendSeg(uint32_t aType, ...); // variable number of float args
iterator begin() { return mData.Elements(); }
iterator end() { return mData.Elements() + mData.Length(); }
FallibleTArray<float> mData;
mozilla::StyleSVGPathData mData;
};
/**
@ -283,9 +219,11 @@ class SVGPathDataAndInfo final : public SVGPathData {
return static_cast<dom::SVGElement*>(e.get());
}
nsresult CopyFrom(const SVGPathDataAndInfo& rhs) {
mElement = rhs.mElement;
return SVGPathData::CopyFrom(rhs);
// If you use this, you need to call SetElement manually.
void CopyFrom(const SVGPathData& aOther) { mData = aOther.mData; }
void CopyFrom(const SVGPathDataAndInfo& aOther) {
CopyFrom(static_cast<const SVGPathData&>(aOther));
mElement = aOther.mElement;
}
/**
@ -301,20 +239,6 @@ class SVGPathDataAndInfo final : public SVGPathData {
return false;
}
/**
* Exposed so that SVGPathData baseVals can be copied to
* SVGPathDataAndInfo objects. Note that callers should also call
* SetElement() when using this method!
*/
using SVGPathData::CopyFrom;
// Exposed since SVGPathData objects can be modified.
using SVGPathData::iterator;
using SVGPathData::operator[];
using SVGPathData::begin;
using SVGPathData::end;
using SVGPathData::SetLength;
private:
// We must keep a weak reference to our element because we may belong to a
// cached baseVal SMILValue. See the comments starting at:

Просмотреть файл

@ -6,346 +6,10 @@
#include "SVGPathDataParser.h"
#include "mozilla/gfx/Point.h"
#include "SVGDataParser.h"
#include "SVGContentUtils.h"
#include "SVGPathData.h"
#include "SVGPathSegUtils.h"
using namespace mozilla::gfx;
namespace mozilla {
static inline char16_t ToUpper(char16_t aCh) {
return aCh >= 'a' && aCh <= 'z' ? aCh - 'a' + 'A' : aCh;
}
bool SVGPathDataParser::Parse() {
mPathSegList->Clear();
return ParsePath();
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseCoordPair(float& aX, float& aY) {
return SVGContentUtils::ParseNumber(mIter, mEnd, aX) && SkipCommaWsp() &&
SVGContentUtils::ParseNumber(mIter, mEnd, aY);
}
bool SVGPathDataParser::ParseFlag(bool& aFlag) {
if (mIter == mEnd || (*mIter != '0' && *mIter != '1')) {
return false;
}
aFlag = (*mIter == '1');
++mIter;
return true;
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParsePath() {
while (SkipWsp()) {
if (!ParseSubPath()) {
return false;
}
}
return true;
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseSubPath() {
return ParseMoveto() && ParseSubPathElements();
}
bool SVGPathDataParser::ParseSubPathElements() {
while (SkipWsp() && !IsStartOfSubPath()) {
char16_t commandType = ToUpper(*mIter);
// Upper case commands have absolute co-ordinates,
// lower case commands have relative co-ordinates.
bool absCoords = commandType == *mIter;
++mIter;
SkipWsp();
if (!ParseSubPathElement(commandType, absCoords)) {
return false;
}
}
return true;
}
bool SVGPathDataParser::ParseSubPathElement(char16_t aCommandType,
bool aAbsCoords) {
switch (aCommandType) {
case 'Z':
return ParseClosePath();
case 'L':
return ParseLineto(aAbsCoords);
case 'H':
return ParseHorizontalLineto(aAbsCoords);
case 'V':
return ParseVerticalLineto(aAbsCoords);
case 'C':
return ParseCurveto(aAbsCoords);
case 'S':
return ParseSmoothCurveto(aAbsCoords);
case 'Q':
return ParseQuadBezierCurveto(aAbsCoords);
case 'T':
return ParseSmoothQuadBezierCurveto(aAbsCoords);
case 'A':
return ParseEllipticalArc(aAbsCoords);
}
return false;
}
bool SVGPathDataParser::IsStartOfSubPath() const {
return *mIter == 'm' || *mIter == 'M';
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseMoveto() {
if (!IsStartOfSubPath()) {
return false;
}
bool absCoords = (*mIter == 'M');
++mIter;
SkipWsp();
float x, y;
if (!ParseCoordPair(x, y)) {
return false;
}
if (NS_FAILED(mPathSegList->AppendSeg(
absCoords ? PATHSEG_MOVETO_ABS : PATHSEG_MOVETO_REL, x, y))) {
return false;
}
if (!SkipWsp() || IsAsciiAlpha(*mIter)) {
// End of data, or start of a new command
return true;
}
SkipCommaWsp();
// Per SVG 1.1 Section 8.3.2
// If a moveto is followed by multiple pairs of coordinates,
// the subsequent pairs are treated as implicit lineto commands
return ParseLineto(absCoords);
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseClosePath() {
return NS_SUCCEEDED(mPathSegList->AppendSeg(PATHSEG_CLOSEPATH));
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseLineto(bool aAbsCoords) {
while (true) {
float x, y;
if (!ParseCoordPair(x, y)) {
return false;
}
if (NS_FAILED(mPathSegList->AppendSeg(
aAbsCoords ? PATHSEG_LINETO_ABS : PATHSEG_LINETO_REL, x, y))) {
return false;
}
if (!SkipWsp() || IsAsciiAlpha(*mIter)) {
// End of data, or start of a new command
return true;
}
SkipCommaWsp();
}
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseHorizontalLineto(bool aAbsCoords) {
while (true) {
float x;
if (!SVGContentUtils::ParseNumber(mIter, mEnd, x)) {
return false;
}
if (NS_FAILED(mPathSegList->AppendSeg(aAbsCoords
? PATHSEG_LINETO_HORIZONTAL_ABS
: PATHSEG_LINETO_HORIZONTAL_REL,
x))) {
return false;
}
if (!SkipWsp() || IsAsciiAlpha(*mIter)) {
// End of data, or start of a new command
return true;
}
SkipCommaWsp();
}
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseVerticalLineto(bool aAbsCoords) {
while (true) {
float y;
if (!SVGContentUtils::ParseNumber(mIter, mEnd, y)) {
return false;
}
if (NS_FAILED(mPathSegList->AppendSeg(aAbsCoords
? PATHSEG_LINETO_VERTICAL_ABS
: PATHSEG_LINETO_VERTICAL_REL,
y))) {
return false;
}
if (!SkipWsp() || IsAsciiAlpha(*mIter)) {
// End of data, or start of a new command
return true;
}
SkipCommaWsp();
}
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseCurveto(bool aAbsCoords) {
while (true) {
float x1, y1, x2, y2, x, y;
if (!(ParseCoordPair(x1, y1) && SkipCommaWsp() && ParseCoordPair(x2, y2) &&
SkipCommaWsp() && ParseCoordPair(x, y))) {
return false;
}
if (NS_FAILED(mPathSegList->AppendSeg(
aAbsCoords ? PATHSEG_CURVETO_CUBIC_ABS : PATHSEG_CURVETO_CUBIC_REL,
x1, y1, x2, y2, x, y))) {
return false;
}
if (!SkipWsp() || IsAsciiAlpha(*mIter)) {
// End of data, or start of a new command
return true;
}
SkipCommaWsp();
}
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseSmoothCurveto(bool aAbsCoords) {
while (true) {
float x2, y2, x, y;
if (!(ParseCoordPair(x2, y2) && SkipCommaWsp() && ParseCoordPair(x, y))) {
return false;
}
if (NS_FAILED(mPathSegList->AppendSeg(
aAbsCoords ? PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
: PATHSEG_CURVETO_CUBIC_SMOOTH_REL,
x2, y2, x, y))) {
return false;
}
if (!SkipWsp() || IsAsciiAlpha(*mIter)) {
// End of data, or start of a new command
return true;
}
SkipCommaWsp();
}
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseQuadBezierCurveto(bool aAbsCoords) {
while (true) {
float x1, y1, x, y;
if (!(ParseCoordPair(x1, y1) && SkipCommaWsp() && ParseCoordPair(x, y))) {
return false;
}
if (NS_FAILED(mPathSegList->AppendSeg(aAbsCoords
? PATHSEG_CURVETO_QUADRATIC_ABS
: PATHSEG_CURVETO_QUADRATIC_REL,
x1, y1, x, y))) {
return false;
}
if (!SkipWsp() || IsAsciiAlpha(*mIter)) {
// Start of a new command
return true;
}
SkipCommaWsp();
}
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseSmoothQuadBezierCurveto(bool aAbsCoords) {
while (true) {
float x, y;
if (!ParseCoordPair(x, y)) {
return false;
}
if (NS_FAILED(mPathSegList->AppendSeg(
aAbsCoords ? PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
: PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
x, y))) {
return false;
}
if (!SkipWsp() || IsAsciiAlpha(*mIter)) {
// End of data, or start of a new command
return true;
}
SkipCommaWsp();
}
}
//----------------------------------------------------------------------
bool SVGPathDataParser::ParseEllipticalArc(bool aAbsCoords) {
while (true) {
float r1, r2, angle, x, y;
bool largeArcFlag, sweepFlag;
if (!(SVGContentUtils::ParseNumber(mIter, mEnd, r1) && SkipCommaWsp() &&
SVGContentUtils::ParseNumber(mIter, mEnd, r2) && SkipCommaWsp() &&
SVGContentUtils::ParseNumber(mIter, mEnd, angle) && SkipCommaWsp() &&
ParseFlag(largeArcFlag) && SkipCommaWsp() && ParseFlag(sweepFlag) &&
SkipCommaWsp() && ParseCoordPair(x, y))) {
return false;
}
// We can only pass floats after 'type', and per the SVG spec for arc,
// non-zero args are treated at 'true'.
if (NS_FAILED(mPathSegList->AppendSeg(
aAbsCoords ? PATHSEG_ARC_ABS : PATHSEG_ARC_REL, r1, r2, angle,
largeArcFlag ? 1.0f : 0.0f, sweepFlag ? 1.0f : 0.0f, x, y))) {
return false;
}
if (!SkipWsp() || IsAsciiAlpha(*mIter)) {
// End of data, or start of a new command
return true;
}
SkipCommaWsp();
}
}
//-----------------------------------------------------------------------
static double CalcVectorAngle(double ux, double uy, double vx, double vy) {

Просмотреть файл

@ -7,51 +7,9 @@
#ifndef DOM_SVG_SVGPATHDATAPARSER_H_
#define DOM_SVG_SVGPATHDATAPARSER_H_
#include "mozilla/Attributes.h"
#include "mozilla/gfx/Point.h"
#include "SVGDataParser.h"
namespace mozilla {
class SVGPathData;
////////////////////////////////////////////////////////////////////////
// SVGPathDataParser: a simple recursive descent parser that builds
// DOMSVGPathSegs from path data strings. The grammar for path data
// can be found in SVG CR 20001102, chapter 8.
class SVGPathDataParser : public SVGDataParser {
public:
SVGPathDataParser(const nsAString& aValue, mozilla::SVGPathData* aList)
: SVGDataParser(aValue), mPathSegList(aList) {
MOZ_ASSERT(aList, "null path data");
}
bool Parse();
private:
bool ParseCoordPair(float& aX, float& aY);
bool ParseFlag(bool& aFlag);
bool ParsePath();
bool IsStartOfSubPath() const;
bool ParseSubPath();
bool ParseSubPathElements();
bool ParseSubPathElement(char16_t aCommandType, bool aAbsCoords);
bool ParseMoveto();
bool ParseClosePath();
bool ParseLineto(bool aAbsCoords);
bool ParseHorizontalLineto(bool aAbsCoords);
bool ParseVerticalLineto(bool aAbsCoords);
bool ParseCurveto(bool aAbsCoords);
bool ParseSmoothCurveto(bool aAbsCoords);
bool ParseQuadBezierCurveto(bool aAbsCoords);
bool ParseSmoothQuadBezierCurveto(bool aAbsCoords);
bool ParseEllipticalArc(bool aAbsCoords);
mozilla::SVGPathData* const mPathSegList;
};
class SVGArcConverter {
using Point = mozilla::gfx::Point;

Просмотреть файл

@ -193,35 +193,29 @@ bool SVGPathElement::GetDistancesFromOriginToEndsOfVisibleSegments(
aOutput);
}
static bool PathIsClosed(Span<const StylePathCommand> aPath) {
return !aPath.IsEmpty() && aPath.rbegin()->IsClose();
}
// Offset paths (including references to SVG Paths) are closed loops only if the
// final command in the path list is a closepath command ("z" or "Z"), otherwise
// they are unclosed intervals.
// https://drafts.fxtf.org/motion/#path-distance
bool SVGPathElement::IsClosedLoop() const {
bool isClosed = false;
auto callback = [&](const ComputedStyle* s) {
const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset();
if (styleSVGReset->mD.IsPath()) {
isClosed = !styleSVGReset->mD.AsPath()._0.IsEmpty() &&
styleSVGReset->mD.AsPath()._0.AsSpan().rbegin()->IsClose();
isClosed = PathIsClosed(styleSVGReset->mD.AsPath()._0.AsSpan());
}
};
const bool success = SVGGeometryProperty::DoForComputedStyle(this, callback);
if (success) {
if (SVGGeometryProperty::DoForComputedStyle(this, callback)) {
return isClosed;
}
const SVGPathData& data = mD.GetAnimValue();
// FIXME: Bug 1847621, we can cache this value, instead of walking through the
// entire path again and again.
uint32_t i = 0;
uint32_t segType = PATHSEG_UNKNOWN;
while (i < data.Length()) {
segType = SVGPathSegUtils::DecodeType(data[i++]);
i += SVGPathSegUtils::ArgCountForType(segType);
}
return segType == PATHSEG_CLOSEPATH;
return PathIsClosed(mD.GetAnimValue().AsSpan());
}
/* static */

Просмотреть файл

@ -11,11 +11,6 @@
#include "SVGPathData.h"
#include "SVGPathSegUtils.h"
// Indices of boolean flags within 'arc' segment chunks in path-data arrays
// (where '0' would correspond to the index of the encoded segment type):
#define LARGE_ARC_FLAG_IDX 4
#define SWEEP_FLAG_IDX 5
namespace mozilla {
//----------------------------------------------------------------------
@ -42,8 +37,8 @@ nsresult SVGPathSegListSMILType::Assign(SMILValue& aDest,
const SVGPathDataAndInfo* src =
static_cast<const SVGPathDataAndInfo*>(aSrc.mU.mPtr);
SVGPathDataAndInfo* dest = static_cast<SVGPathDataAndInfo*>(aDest.mU.mPtr);
return dest->CopyFrom(*src);
dest->CopyFrom(*src);
return NS_OK;
}
bool SVGPathSegListSMILType::IsEqual(const SMILValue& aLeft,
@ -55,319 +50,6 @@ bool SVGPathSegListSMILType::IsEqual(const SMILValue& aLeft,
*static_cast<const SVGPathDataAndInfo*>(aRight.mU.mPtr);
}
static bool ArcFlagsDiffer(SVGPathDataAndInfo::const_iterator aPathData1,
SVGPathDataAndInfo::const_iterator aPathData2) {
MOZ_ASSERT(
SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData1[0])),
"ArcFlagsDiffer called with non-arc segment");
MOZ_ASSERT(
SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData2[0])),
"ArcFlagsDiffer called with non-arc segment");
return aPathData1[LARGE_ARC_FLAG_IDX] != aPathData2[LARGE_ARC_FLAG_IDX] ||
aPathData1[SWEEP_FLAG_IDX] != aPathData2[SWEEP_FLAG_IDX];
}
enum PathInterpolationResult {
eCannotInterpolate,
eRequiresConversion,
eCanInterpolate
};
static PathInterpolationResult CanInterpolate(const SVGPathDataAndInfo& aStart,
const SVGPathDataAndInfo& aEnd) {
if (aStart.IsIdentity()) {
return eCanInterpolate;
}
if (aStart.Length() != aEnd.Length()) {
return eCannotInterpolate;
}
PathInterpolationResult result = eCanInterpolate;
SVGPathDataAndInfo::const_iterator pStart = aStart.begin();
SVGPathDataAndInfo::const_iterator pEnd = aEnd.begin();
SVGPathDataAndInfo::const_iterator pStartDataEnd = aStart.end();
SVGPathDataAndInfo::const_iterator pEndDataEnd = aEnd.end();
while (pStart < pStartDataEnd && pEnd < pEndDataEnd) {
uint32_t startType = SVGPathSegUtils::DecodeType(*pStart);
uint32_t 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);
}
MOZ_ASSERT(pStart <= pStartDataEnd && pEnd <= pEndDataEnd,
"Iterated past end of buffer! (Corrupt path data?)");
if (pStart != pStartDataEnd || pEnd != pEndDataEnd) {
return eCannotInterpolate;
}
return result;
}
enum RelativenessAdjustmentType { eAbsoluteToRelative, eRelativeToAbsolute };
static inline void AdjustSegmentForRelativeness(
RelativenessAdjustmentType aAdjustmentType,
const SVGPathDataAndInfo::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;
}
}
/**
* Helper function for AddWeightedPathSegLists, to add multiples of two
* path-segments of the same type.
*
* NOTE: |aSeg1| is allowed to be nullptr, so we use |aSeg2| as the
* authoritative source of things like segment-type and boolean arc flags.
*
* @param aCoeff1 The coefficient to use on the first segment.
* @param aSeg1 An iterator pointing to the first segment. This can be
* null, which is treated as identity (zero).
* @param aCoeff2 The coefficient to use on the second segment.
* @param aSeg2 An iterator pointing to the second segment.
* @param [out] aResultSeg An iterator pointing to where we should write the
* result of this operation.
*/
static inline void AddWeightedPathSegs(
double aCoeff1, SVGPathDataAndInfo::const_iterator& aSeg1, double aCoeff2,
SVGPathDataAndInfo::const_iterator& aSeg2,
SVGPathDataAndInfo::iterator& aResultSeg) {
MOZ_ASSERT(aSeg2, "2nd segment must be non-null");
MOZ_ASSERT(aResultSeg, "result segment must be non-null");
uint32_t segType = SVGPathSegUtils::DecodeType(aSeg2[0]);
MOZ_ASSERT(!aSeg1 || SVGPathSegUtils::DecodeType(*aSeg1) == segType,
"unexpected segment type");
// FIRST: Directly copy the arguments that don't make sense to add.
aResultSeg[0] = aSeg2[0]; // encoded segment type
bool isArcType = SVGPathSegUtils::IsArcType(segType);
if (isArcType) {
// Copy boolean arc flags.
MOZ_ASSERT(!aSeg1 || !ArcFlagsDiffer(aSeg1, aSeg2),
"Expecting arc flags to match");
aResultSeg[LARGE_ARC_FLAG_IDX] = aSeg2[LARGE_ARC_FLAG_IDX];
aResultSeg[SWEEP_FLAG_IDX] = aSeg2[SWEEP_FLAG_IDX];
}
// SECOND: Add the arguments that are supposed to be added.
// (The 1's below are to account for segment type)
uint32_t numArgs = SVGPathSegUtils::ArgCountForType(segType);
for (uint32_t i = 1; i < 1 + numArgs; ++i) {
// Need to skip arc flags for arc-type segments. (already handled them)
if (!(isArcType && (i == LARGE_ARC_FLAG_IDX || i == SWEEP_FLAG_IDX))) {
aResultSeg[i] = (aSeg1 ? aCoeff1 * aSeg1[i] : 0.0) + aCoeff2 * aSeg2[i];
}
}
// FINALLY: Shift iterators forward. ("1+" is to include seg-type)
if (aSeg1) {
aSeg1 += 1 + numArgs;
}
aSeg2 += 1 + numArgs;
aResultSeg += 1 + numArgs;
}
/**
* Helper function for Add & Interpolate, to add multiples of two path-segment
* lists.
*
* NOTE: aList1 and aList2 are assumed to have their segment-types and
* segment-count match exactly (unless aList1 is an identity value).
*
* NOTE: aResult, the output list, is expected to either be an identity value
* (in which case we'll grow it) *or* to already have the exactly right length
* (e.g. in cases where aList1 and aResult are actually the same list).
*
* @param aCoeff1 The coefficient to use on the first path segment list.
* @param aList1 The first path segment list. Allowed to be identity.
* @param aCoeff2 The coefficient to use on the second path segment list.
* @param aList2 The second path segment list.
* @param [out] aResultSeg The resulting path segment list. Allowed to be
* identity, in which case we'll grow it to the right
* size. Also allowed to be the same list as aList1.
*/
static nsresult AddWeightedPathSegLists(double aCoeff1,
const SVGPathDataAndInfo& aList1,
double aCoeff2,
const SVGPathDataAndInfo& aList2,
SVGPathDataAndInfo& aResult) {
MOZ_ASSERT(aCoeff1 >= 0.0 && aCoeff2 >= 0.0,
"expecting non-negative coefficients");
MOZ_ASSERT(!aList2.IsIdentity(), "expecting 2nd list to be non-identity");
MOZ_ASSERT(aList1.IsIdentity() || aList1.Length() == aList2.Length(),
"expecting 1st list to be identity or to have same "
"length as 2nd list");
MOZ_ASSERT(aResult.IsIdentity() || aResult.Length() == aList2.Length(),
"expecting result list to be identity or to have same "
"length as 2nd list");
SVGPathDataAndInfo::const_iterator iter1, end1;
if (aList1.IsIdentity()) {
iter1 = end1 = nullptr; // indicate that this is an identity list
} else {
iter1 = aList1.begin();
end1 = aList1.end();
}
SVGPathDataAndInfo::const_iterator iter2 = aList2.begin();
SVGPathDataAndInfo::const_iterator end2 = aList2.end();
// Grow |aResult| if necessary. (NOTE: It's possible that aResult and aList1
// are the same list, so this may implicitly resize aList1. That's fine,
// because in that case, we will have already set iter1 to nullptr above, to
// record that our first operand is an identity value.)
if (aResult.IsIdentity()) {
if (!aResult.SetLength(aList2.Length())) {
return NS_ERROR_OUT_OF_MEMORY;
}
aResult.SetElement(aList2.Element()); // propagate target element info!
}
SVGPathDataAndInfo::iterator resultIter = aResult.begin();
while ((!iter1 || iter1 != end1) && iter2 != end2) {
AddWeightedPathSegs(aCoeff1, iter1, aCoeff2, iter2, resultIter);
}
MOZ_ASSERT(
(!iter1 || iter1 == end1) && iter2 == end2 && resultIter == aResult.end(),
"Very, very bad - path data corrupt");
return NS_OK;
}
static void ConvertPathSegmentData(SVGPathDataAndInfo::const_iterator& aStart,
SVGPathDataAndInfo::const_iterator& aEnd,
SVGPathDataAndInfo::iterator& aResult,
SVGPathTraversalState& aState) {
uint32_t startType = SVGPathSegUtils::DecodeType(*aStart);
uint32_t endType = SVGPathSegUtils::DecodeType(*aEnd);
uint32_t segmentLengthIncludingType =
1 + SVGPathSegUtils::ArgCountForType(startType);
SVGPathDataAndInfo::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;
}
MOZ_ASSERT(
SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType),
"Incompatible path segment types passed to ConvertPathSegmentData!");
RelativenessAdjustmentType adjustmentType =
SVGPathSegUtils::IsRelativeType(startType) ? eRelativeToAbsolute
: eAbsoluteToRelative;
MOZ_ASSERT(
segmentLengthIncludingType ==
1 + SVGPathSegUtils::ArgCountForType(endType),
"Compatible path segment types for interpolation had different lengths!");
aResult[0] = aEnd[0];
switch (endType) {
case PATHSEG_LINETO_HORIZONTAL_ABS:
case PATHSEG_LINETO_HORIZONTAL_REL:
aResult[1] =
aStart[1] +
(adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.x;
break;
case PATHSEG_LINETO_VERTICAL_ABS:
case PATHSEG_LINETO_VERTICAL_REL:
aResult[1] =
aStart[1] +
(adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.y;
break;
case PATHSEG_ARC_ABS:
case 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 PATHSEG_CURVETO_CUBIC_ABS:
case PATHSEG_CURVETO_CUBIC_REL:
aResult[5] = aStart[5];
aResult[6] = aStart[6];
AdjustSegmentForRelativeness(adjustmentType, aResult + 5, aState);
[[fallthrough]];
case PATHSEG_CURVETO_QUADRATIC_ABS:
case PATHSEG_CURVETO_QUADRATIC_REL:
case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
case PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
aResult[3] = aStart[3];
aResult[4] = aStart[4];
AdjustSegmentForRelativeness(adjustmentType, aResult + 3, aState);
[[fallthrough]];
case PATHSEG_MOVETO_ABS:
case PATHSEG_MOVETO_REL:
case PATHSEG_LINETO_ABS:
case PATHSEG_LINETO_REL:
case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
case 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(
SVGPathDataAndInfo::const_iterator aStart,
SVGPathDataAndInfo::const_iterator aStartDataEnd,
SVGPathDataAndInfo::const_iterator aEnd,
SVGPathDataAndInfo::const_iterator aEndDataEnd,
SVGPathDataAndInfo::iterator aResult) {
SVGPathTraversalState state;
state.mode = SVGPathTraversalState::eUpdateOnlyStartAndCurrentPos;
while (aStart < aStartDataEnd && aEnd < aEndDataEnd) {
ConvertPathSegmentData(aStart, aEnd, aResult, state);
}
MOZ_ASSERT(aStart == aStartDataEnd && aEnd == aEndDataEnd,
"Failed to convert all path segment data! (Corrupt?)");
}
nsresult SVGPathSegListSMILType::Add(SMILValue& aDest,
const SMILValue& aValueToAdd,
uint32_t aCount) const {
@ -382,26 +64,19 @@ nsresult SVGPathSegListSMILType::Add(SMILValue& aDest,
return NS_OK;
}
if (!dest.IsIdentity()) {
// Neither value is identity; make sure they're compatible.
MOZ_ASSERT(dest.Element() == valueToAdd.Element(),
"adding values from different elements...?");
PathInterpolationResult check = CanInterpolate(dest, valueToAdd);
if (check == eCannotInterpolate) {
// SVGContentUtils::ReportToConsole - can't add path segment lists with
// different numbers of segments, with arcs that have different flag
// values, or with incompatible segment types.
return NS_ERROR_FAILURE;
}
if (check == eRequiresConversion) {
// Convert dest, in-place, to match the types in valueToAdd:
ConvertAllPathSegmentData(dest.begin(), dest.end(), valueToAdd.begin(),
valueToAdd.end(), dest.begin());
}
if (dest.IsIdentity()) {
dest.CopyFrom(valueToAdd);
aCount--;
}
return AddWeightedPathSegLists(1.0, dest, aCount, valueToAdd, dest);
if (aCount &&
!Servo_SVGPathData_Add(&dest.RawData(), &valueToAdd.RawData(), aCount)) {
// SVGContentUtils::ReportToConsole - can't add path segment lists with
// different numbers of segments, with arcs that have different flag
// values, or with incompatible segment types.
return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult SVGPathSegListSMILType::ComputeDistance(const SMILValue& aFrom,
@ -433,32 +108,13 @@ nsresult SVGPathSegListSMILType::Interpolate(const SMILValue& aStartVal,
*static_cast<SVGPathDataAndInfo*>(aResult.mU.mPtr);
MOZ_ASSERT(result.IsIdentity(),
"expecting outparam to start out as identity");
PathInterpolationResult check = CanInterpolate(start, end);
if (check == eCannotInterpolate) {
// SVGContentUtils::ReportToConsole - can't interpolate path segment lists
// with different numbers of segments, with arcs with different flag values,
// or with incompatible segment types.
result.SetElement(end.Element());
if (!Servo_SVGPathData_Interpolate(
start.IsIdentity() ? nullptr : &start.RawData(), &end.RawData(),
aUnitDistance, &result.RawData())) {
return NS_ERROR_FAILURE;
}
const SVGPathDataAndInfo* startListToUse = &start;
if (check == eRequiresConversion) {
// Can't convert |start| in-place, since it's const. Instead, we copy it
// into |result|, converting the types as we go, and use that as our start.
if (!result.SetLength(end.Length())) {
return NS_ERROR_OUT_OF_MEMORY;
}
result.SetElement(end.Element()); // propagate target element info!
ConvertAllPathSegmentData(start.begin(), start.end(), end.begin(),
end.end(), result.begin());
startListToUse = &result;
}
return AddWeightedPathSegLists(1.0 - aUnitDistance, *startListToUse,
aUnitDistance, end, result);
return NS_OK;
}
} // namespace mozilla

Просмотреть файл

@ -20,59 +20,6 @@ namespace mozilla {
static const float PATH_SEG_LENGTH_TOLERANCE = 0.0000001f;
static const uint32_t MAX_RECURSION = 10;
/* static */
void SVGPathSegUtils::GetValueAsString(const float* aSeg, nsAString& aValue) {
// Adding new seg type? Is the formatting below acceptable for the new types?
static_assert(
NS_SVG_PATH_SEG_LAST_VALID_TYPE == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
"Update GetValueAsString for the new value.");
static_assert(NS_SVG_PATH_SEG_MAX_ARGS == 7,
"Add another case to the switch below.");
uint32_t type = DecodeType(aSeg[0]);
char16_t typeAsChar = GetPathSegTypeAsLetter(type);
// Special case arcs:
if (IsArcType(type)) {
bool largeArcFlag = aSeg[4] != 0.0f;
bool sweepFlag = aSeg[5] != 0.0f;
nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g %d,%d %g,%g", typeAsChar,
aSeg[1], aSeg[2], aSeg[3], largeArcFlag,
sweepFlag, aSeg[6], aSeg[7]);
} else {
switch (ArgCountForType(type)) {
case 0:
aValue = typeAsChar;
break;
case 1:
nsTextFormatter::ssprintf(aValue, u"%c%g", typeAsChar, aSeg[1]);
break;
case 2:
nsTextFormatter::ssprintf(aValue, u"%c%g,%g", typeAsChar, aSeg[1],
aSeg[2]);
break;
case 4:
nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g,%g", typeAsChar, aSeg[1],
aSeg[2], aSeg[3], aSeg[4]);
break;
case 6:
nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g,%g %g,%g", typeAsChar,
aSeg[1], aSeg[2], aSeg[3], aSeg[4], aSeg[5],
aSeg[6]);
break;
default:
MOZ_ASSERT(false, "Unknown segment type");
aValue = u"<unknown-segment-type>";
return;
}
}
}
static float CalcDistanceBetweenPoints(const Point& aP1, const Point& aP2) {
return NS_hypot(aP2.x - aP1.x, aP2.y - aP1.y);
}
@ -146,269 +93,6 @@ static inline float CalcLengthOfQuadraticBezier(const Point& aPos,
return CalcBezLengthHelper(curve, 3, 0, SplitQuadraticBezier);
}
static void TraverseClosePath(const float* aArgs,
SVGPathTraversalState& aState) {
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += CalcDistanceBetweenPoints(aState.pos, aState.start);
aState.cp1 = aState.cp2 = aState.start;
}
aState.pos = aState.start;
}
static void TraverseMovetoAbs(const float* aArgs,
SVGPathTraversalState& aState) {
aState.start = aState.pos = Point(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 void TraverseMovetoRel(const float* aArgs,
SVGPathTraversalState& aState) {
aState.start = aState.pos += Point(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 void TraverseLinetoAbs(const float* aArgs,
SVGPathTraversalState& aState) {
Point to(aArgs[0], aArgs[1]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += CalcDistanceBetweenPoints(aState.pos, to);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static void TraverseLinetoRel(const float* aArgs,
SVGPathTraversalState& aState) {
Point to = aState.pos + Point(aArgs[0], aArgs[1]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += CalcDistanceBetweenPoints(aState.pos, to);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static void TraverseLinetoHorizontalAbs(const float* aArgs,
SVGPathTraversalState& aState) {
Point to(aArgs[0], aState.pos.y);
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += std::fabs(to.x - aState.pos.x);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static void TraverseLinetoHorizontalRel(const float* aArgs,
SVGPathTraversalState& aState) {
aState.pos.x += aArgs[0];
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += std::fabs(aArgs[0]);
aState.cp1 = aState.cp2 = aState.pos;
}
}
static void TraverseLinetoVerticalAbs(const float* aArgs,
SVGPathTraversalState& aState) {
Point to(aState.pos.x, aArgs[0]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += std::fabs(to.y - aState.pos.y);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static void TraverseLinetoVerticalRel(const float* aArgs,
SVGPathTraversalState& aState) {
aState.pos.y += aArgs[0];
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += std::fabs(aArgs[0]);
aState.cp1 = aState.cp2 = aState.pos;
}
}
static void TraverseCurvetoCubicAbs(const float* aArgs,
SVGPathTraversalState& aState) {
Point to(aArgs[4], aArgs[5]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
Point cp1(aArgs[0], aArgs[1]);
Point cp2(aArgs[2], aArgs[3]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static void TraverseCurvetoCubicSmoothAbs(const float* aArgs,
SVGPathTraversalState& aState) {
Point to(aArgs[2], aArgs[3]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
Point cp1 = aState.pos - (aState.cp2 - aState.pos);
Point cp2(aArgs[0], aArgs[1]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static void TraverseCurvetoCubicRel(const float* aArgs,
SVGPathTraversalState& aState) {
Point to = aState.pos + Point(aArgs[4], aArgs[5]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
Point cp1 = aState.pos + Point(aArgs[0], aArgs[1]);
Point cp2 = aState.pos + Point(aArgs[2], aArgs[3]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static void TraverseCurvetoCubicSmoothRel(const float* aArgs,
SVGPathTraversalState& aState) {
Point to = aState.pos + Point(aArgs[2], aArgs[3]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
Point cp1 = aState.pos - (aState.cp2 - aState.pos);
Point cp2 = aState.pos + Point(aArgs[0], aArgs[1]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static void TraverseCurvetoQuadraticAbs(const float* aArgs,
SVGPathTraversalState& aState) {
Point to(aArgs[2], aArgs[3]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
Point 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) {
Point to(aArgs[0], aArgs[1]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
Point 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) {
Point to = aState.pos + Point(aArgs[2], aArgs[3]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
Point cp = aState.pos + Point(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) {
Point to = aState.pos + Point(aArgs[0], aArgs[1]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
Point 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 TraverseArcAbs(const float* aArgs, SVGPathTraversalState& aState) {
Point to(aArgs[5], aArgs[6]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
float dist = 0;
Point radii(aArgs[0], aArgs[1]);
if (radii.x == 0.0f || radii.y == 0.0f) {
dist = CalcDistanceBetweenPoints(aState.pos, to);
} else {
Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)};
SVGArcConverter 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 = to;
}
static void TraverseArcRel(const float* aArgs, SVGPathTraversalState& aState) {
Point to = aState.pos + Point(aArgs[5], aArgs[6]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
float dist = 0;
Point radii(aArgs[0], aArgs[1]);
if (radii.x == 0.0f || radii.y == 0.0f) {
dist = CalcDistanceBetweenPoints(aState.pos, to);
} else {
Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)};
SVGArcConverter 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 = to;
}
using TraverseFunc = void (*)(const float*, SVGPathTraversalState&);
static TraverseFunc gTraverseFuncTable[NS_SVG_PATH_SEG_TYPE_COUNT] = {
nullptr, // 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) {
static_assert(
MOZ_ARRAY_LENGTH(gTraverseFuncTable) == NS_SVG_PATH_SEG_TYPE_COUNT,
"gTraverseFuncTable is out of date");
uint32_t type = DecodeType(aData[0]);
gTraverseFuncTable[type](aData + 1, aState);
}
// Basically, this is just a variant version of the above TraverseXXX functions.
// We just put those function inside this and use StylePathCommand instead.
// This function and the above ones should be dropped by Bug 1388931.
@ -417,7 +101,11 @@ void SVGPathSegUtils::TraversePathSegment(const StylePathCommand& aCommand,
SVGPathTraversalState& aState) {
switch (aCommand.tag) {
case StylePathCommand::Tag::Close:
TraverseClosePath(nullptr, aState);
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += CalcDistanceBetweenPoints(aState.pos, aState.start);
aState.cp1 = aState.cp2 = aState.start;
}
aState.pos = aState.start;
break;
case StylePathCommand::Tag::Move: {
const Point& p = aCommand.move.point.ToGfxPoint();

Просмотреть файл

@ -17,32 +17,6 @@ namespace mozilla {
template <typename Angle, typename LP>
struct StyleGenericShapeCommand;
constexpr uint16_t PATHSEG_UNKNOWN = 0;
constexpr uint16_t PATHSEG_CLOSEPATH = 1;
constexpr uint16_t PATHSEG_MOVETO_ABS = 2;
constexpr uint16_t PATHSEG_MOVETO_REL = 3;
constexpr uint16_t PATHSEG_LINETO_ABS = 4;
constexpr uint16_t PATHSEG_LINETO_REL = 5;
constexpr uint16_t PATHSEG_CURVETO_CUBIC_ABS = 6;
constexpr uint16_t PATHSEG_CURVETO_CUBIC_REL = 7;
constexpr uint16_t PATHSEG_CURVETO_QUADRATIC_ABS = 8;
constexpr uint16_t PATHSEG_CURVETO_QUADRATIC_REL = 9;
constexpr uint16_t PATHSEG_ARC_ABS = 10;
constexpr uint16_t PATHSEG_ARC_REL = 11;
constexpr uint16_t PATHSEG_LINETO_HORIZONTAL_ABS = 12;
constexpr uint16_t PATHSEG_LINETO_HORIZONTAL_REL = 13;
constexpr uint16_t PATHSEG_LINETO_VERTICAL_ABS = 14;
constexpr uint16_t PATHSEG_LINETO_VERTICAL_REL = 15;
constexpr uint16_t PATHSEG_CURVETO_CUBIC_SMOOTH_ABS = 16;
constexpr uint16_t PATHSEG_CURVETO_CUBIC_SMOOTH_REL = 17;
constexpr uint16_t PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS = 18;
constexpr uint16_t PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL = 19;
#define NS_SVG_PATH_SEG_MAX_ARGS 7
#define NS_SVG_PATH_SEG_FIRST_VALID_TYPE PATHSEG_CLOSEPATH
#define NS_SVG_PATH_SEG_LAST_VALID_TYPE PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
#define NS_SVG_PATH_SEG_TYPE_COUNT (NS_SVG_PATH_SEG_LAST_VALID_TYPE + 1)
/**
* Code that works with path segments can use an instance of this class to
* store/provide information about the start of the current subpath and the
@ -98,181 +72,6 @@ class SVGPathSegUtils {
SVGPathSegUtils() = default; // private to prevent instances
public:
static void GetValueAsString(const float* aSeg, nsAString& aValue);
/**
* Encode a segment type enum to a float.
*
* At some point in the future we will likely want to encode other
* information into the float, such as whether the command was explicit or
* not. For now all this method does is save on int to float runtime
* conversion by requiring uint32_t and float to be of the same size so we
* can simply do a bitwise uint32_t<->float copy.
*/
static float EncodeType(uint32_t aType) {
static_assert(sizeof(uint32_t) == sizeof(float),
"sizeof uint32_t and float must be the same");
MOZ_ASSERT(IsValidType(aType), "Seg type not recognized");
return *(reinterpret_cast<float*>(&aType));
}
static uint32_t DecodeType(float aType) {
static_assert(sizeof(uint32_t) == sizeof(float),
"sizeof uint32_t and float must be the same");
uint32_t type = *(reinterpret_cast<uint32_t*>(&aType));
MOZ_ASSERT(IsValidType(type), "Seg type not recognized");
return type;
}
static char16_t GetPathSegTypeAsLetter(uint32_t aType) {
MOZ_ASSERT(IsValidType(aType), "Seg type not recognized");
static const char16_t table[] = {
char16_t('x'), // 0 == PATHSEG_UNKNOWN
char16_t('z'), // 1 == PATHSEG_CLOSEPATH
char16_t('M'), // 2 == PATHSEG_MOVETO_ABS
char16_t('m'), // 3 == PATHSEG_MOVETO_REL
char16_t('L'), // 4 == PATHSEG_LINETO_ABS
char16_t('l'), // 5 == PATHSEG_LINETO_REL
char16_t('C'), // 6 == PATHSEG_CURVETO_CUBIC_ABS
char16_t('c'), // 7 == PATHSEG_CURVETO_CUBIC_REL
char16_t('Q'), // 8 == PATHSEG_CURVETO_QUADRATIC_ABS
char16_t('q'), // 9 == PATHSEG_CURVETO_QUADRATIC_REL
char16_t('A'), // 10 == PATHSEG_ARC_ABS
char16_t('a'), // 11 == PATHSEG_ARC_REL
char16_t('H'), // 12 == PATHSEG_LINETO_HORIZONTAL_ABS
char16_t('h'), // 13 == PATHSEG_LINETO_HORIZONTAL_REL
char16_t('V'), // 14 == PATHSEG_LINETO_VERTICAL_ABS
char16_t('v'), // 15 == PATHSEG_LINETO_VERTICAL_REL
char16_t('S'), // 16 == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
char16_t('s'), // 17 == PATHSEG_CURVETO_CUBIC_SMOOTH_REL
char16_t('T'), // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
char16_t('t') // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
};
static_assert(MOZ_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT,
"Unexpected table size");
return table[aType];
}
static uint32_t ArgCountForType(uint32_t aType) {
MOZ_ASSERT(IsValidType(aType), "Seg type not recognized");
static const uint8_t table[] = {
0, // 0 == PATHSEG_UNKNOWN
0, // 1 == PATHSEG_CLOSEPATH
2, // 2 == PATHSEG_MOVETO_ABS
2, // 3 == PATHSEG_MOVETO_REL
2, // 4 == PATHSEG_LINETO_ABS
2, // 5 == PATHSEG_LINETO_REL
6, // 6 == PATHSEG_CURVETO_CUBIC_ABS
6, // 7 == PATHSEG_CURVETO_CUBIC_REL
4, // 8 == PATHSEG_CURVETO_QUADRATIC_ABS
4, // 9 == PATHSEG_CURVETO_QUADRATIC_REL
7, // 10 == PATHSEG_ARC_ABS
7, // 11 == PATHSEG_ARC_REL
1, // 12 == PATHSEG_LINETO_HORIZONTAL_ABS
1, // 13 == PATHSEG_LINETO_HORIZONTAL_REL
1, // 14 == PATHSEG_LINETO_VERTICAL_ABS
1, // 15 == PATHSEG_LINETO_VERTICAL_REL
4, // 16 == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
4, // 17 == PATHSEG_CURVETO_CUBIC_SMOOTH_REL
2, // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
2 // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
};
static_assert(MOZ_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT,
"Unexpected table size");
return table[aType];
}
/**
* Convenience so that callers can pass a float containing an encoded type
* and have it decoded implicitly.
*/
static uint32_t ArgCountForType(float aType) {
return ArgCountForType(DecodeType(aType));
}
static bool IsValidType(uint32_t aType) {
return aType >= NS_SVG_PATH_SEG_FIRST_VALID_TYPE &&
aType <= NS_SVG_PATH_SEG_LAST_VALID_TYPE;
}
static bool IsCubicType(uint32_t aType) {
return aType == PATHSEG_CURVETO_CUBIC_REL ||
aType == PATHSEG_CURVETO_CUBIC_ABS ||
aType == PATHSEG_CURVETO_CUBIC_SMOOTH_REL ||
aType == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
}
static bool IsQuadraticType(uint32_t aType) {
return aType == PATHSEG_CURVETO_QUADRATIC_REL ||
aType == PATHSEG_CURVETO_QUADRATIC_ABS ||
aType == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL ||
aType == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
}
static bool IsArcType(uint32_t aType) {
return aType == PATHSEG_ARC_ABS || aType == PATHSEG_ARC_REL;
}
static bool IsRelativeOrAbsoluteType(uint32_t aType) {
MOZ_ASSERT(IsValidType(aType), "Seg type not recognized");
// When adding a new path segment type, ensure that the returned condition
// below is still correct.
static_assert(
NS_SVG_PATH_SEG_LAST_VALID_TYPE == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
"Unexpected type");
return aType >= PATHSEG_MOVETO_ABS;
}
static bool IsRelativeType(uint32_t aType) {
MOZ_ASSERT(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.
static_assert(
NS_SVG_PATH_SEG_LAST_VALID_TYPE == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
"Unexpected type");
return aType & 1;
}
static uint32_t RelativeVersionOfType(uint32_t aType) {
MOZ_ASSERT(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.
static_assert(
NS_SVG_PATH_SEG_LAST_VALID_TYPE == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
"Unexpected type");
return aType | 1;
}
static bool SameTypeModuloRelativeness(uint32_t aType1, uint32_t aType2) {
if (!IsRelativeOrAbsoluteType(aType1) ||
!IsRelativeOrAbsoluteType(aType2)) {
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);
/**
* Traverse the given path segment and update the SVGPathTraversalState
* object. This is identical to the above one but accepts StylePathCommand.

Просмотреть файл

@ -408,7 +408,7 @@ interface Path2D
{
constructor();
constructor(Path2D other);
constructor(DOMString pathString);
constructor(UTF8String pathString);
[Throws] undefined addPath(Path2D path, optional DOMMatrix2DInit transform = {});
};

Просмотреть файл

@ -9,7 +9,6 @@ headers = [
"mozilla/css/Loader.h",
"mozilla/AnimatedPropertyID.h",
"mozilla/css/SheetLoadData.h",
"mozilla/dom/SVGPathSegUtils.h",
"mozilla/DeclarationBlock.h",
"mozilla/dom/AnimationEffectBinding.h",
"mozilla/dom/HTMLSlotElement.h",
@ -195,7 +194,6 @@ allowlist-vars = [
"GECKO_IS_NIGHTLY",
"NS_SAME_AS_FOREGROUND_COLOR",
"mozilla::detail::gGkAtoms",
"mozilla::PATHSEG_.*",
]
# TODO(emilio): A bunch of types here can go away once we generate bindings and
# structs together.

Просмотреть файл

@ -11,7 +11,6 @@ use crate::values::generics::basic_shape::GenericShapeCommand;
use crate::values::generics::basic_shape::{ArcSize, ArcSweep, ByTo, CoordinatePair};
use crate::values::CSSFloat;
use cssparser::Parser;
use num_traits::FromPrimitive;
use std::fmt::{self, Write};
use std::iter::{Cloned, Peekable};
use std::slice;
@ -67,111 +66,6 @@ impl SVGPathData {
SVGPathData(crate::ArcSlice::from_iter(iter))
}
// FIXME: Bug 1714238, we may drop this once we use the same data structure for both SVG and
// CSS.
/// Decode the svg path raw data from Gecko.
#[cfg(feature = "gecko")]
pub fn decode_from_f32_array(path: &[f32]) -> Result<Self, ()> {
use crate::gecko_bindings::structs::*;
use crate::values::generics::basic_shape::GenericShapeCommand::*;
let mut result: Vec<PathCommand> = Vec::new();
let mut i: usize = 0;
while i < path.len() {
// See EncodeType() and DecodeType() in SVGPathSegUtils.h.
// We are using reinterpret_cast<> to encode and decode between u32 and f32, so here we
// use to_bits() to decode the type.
let seg_type = path[i].to_bits() as u16;
i = i + 1;
match seg_type {
PATHSEG_CLOSEPATH => result.push(Close),
PATHSEG_MOVETO_ABS | PATHSEG_MOVETO_REL => {
debug_assert!(i + 1 < path.len());
result.push(Move {
point: CoordPair::new(path[i], path[i + 1]),
by_to: ByTo::new(seg_type == PATHSEG_MOVETO_ABS),
});
i = i + 2;
},
PATHSEG_LINETO_ABS | PATHSEG_LINETO_REL => {
debug_assert!(i + 1 < path.len());
result.push(Line {
point: CoordPair::new(path[i], path[i + 1]),
by_to: ByTo::new(seg_type == PATHSEG_LINETO_ABS),
});
i = i + 2;
},
PATHSEG_CURVETO_CUBIC_ABS | PATHSEG_CURVETO_CUBIC_REL => {
debug_assert!(i + 5 < path.len());
result.push(CubicCurve {
control1: CoordPair::new(path[i], path[i + 1]),
control2: CoordPair::new(path[i + 2], path[i + 3]),
point: CoordPair::new(path[i + 4], path[i + 5]),
by_to: ByTo::new(seg_type == PATHSEG_CURVETO_CUBIC_ABS),
});
i = i + 6;
},
PATHSEG_CURVETO_QUADRATIC_ABS | PATHSEG_CURVETO_QUADRATIC_REL => {
debug_assert!(i + 3 < path.len());
result.push(QuadCurve {
control1: CoordPair::new(path[i], path[i + 1]),
point: CoordPair::new(path[i + 2], path[i + 3]),
by_to: ByTo::new(seg_type == PATHSEG_CURVETO_QUADRATIC_ABS),
});
i = i + 4;
},
PATHSEG_ARC_ABS | PATHSEG_ARC_REL => {
debug_assert!(i + 6 < path.len());
result.push(Arc {
radii: CoordPair::new(path[i], path[i + 1]),
rotate: path[i + 2],
arc_size: ArcSize::from_u8((path[i + 3] != 0.0f32) as u8).unwrap(),
arc_sweep: ArcSweep::from_u8((path[i + 4] != 0.0f32) as u8).unwrap(),
point: CoordPair::new(path[i + 5], path[i + 6]),
by_to: ByTo::new(seg_type == PATHSEG_ARC_ABS),
});
i = i + 7;
},
PATHSEG_LINETO_HORIZONTAL_ABS | PATHSEG_LINETO_HORIZONTAL_REL => {
debug_assert!(i < path.len());
result.push(HLine {
x: path[i],
by_to: ByTo::new(seg_type == PATHSEG_LINETO_HORIZONTAL_ABS),
});
i = i + 1;
},
PATHSEG_LINETO_VERTICAL_ABS | PATHSEG_LINETO_VERTICAL_REL => {
debug_assert!(i < path.len());
result.push(VLine {
y: path[i],
by_to: ByTo::new(seg_type == PATHSEG_LINETO_VERTICAL_ABS),
});
i = i + 1;
},
PATHSEG_CURVETO_CUBIC_SMOOTH_ABS | PATHSEG_CURVETO_CUBIC_SMOOTH_REL => {
debug_assert!(i + 3 < path.len());
result.push(SmoothCubic {
control2: CoordPair::new(path[i], path[i + 1]),
point: CoordPair::new(path[i + 2], path[i + 3]),
by_to: ByTo::new(seg_type == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS),
});
i = i + 4;
},
PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS | PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL => {
debug_assert!(i + 1 < path.len());
result.push(SmoothQuad {
point: CoordPair::new(path[i], path[i + 1]),
by_to: ByTo::new(seg_type == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS),
});
i = i + 2;
},
PATHSEG_UNKNOWN | _ => return Err(()),
}
}
Ok(SVGPathData(crate::ArcSlice::from_iter(result.into_iter())))
}
/// Parse this SVG path string with the argument that indicates whether we should allow the
/// empty string.
// We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make
@ -179,32 +73,61 @@ impl SVGPathData {
// e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident
// is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable
// str::Char iterator to check each character.
//
// css-shapes-1 says a path data string that does conform but defines an empty path is
// invalid and causes the entire path() to be invalid, so we use allow_empty to decide
// whether we should allow it.
// https://drafts.csswg.org/css-shapes-1/#typedef-basic-shape
pub fn parse<'i, 't>(
input: &mut Parser<'i, 't>,
allow_empty: AllowEmpty,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
let path_string = input.expect_string()?.as_ref();
let (path, ok) = Self::parse_bytes(path_string.as_bytes());
if !ok || (allow_empty == AllowEmpty::No && path.0.is_empty()) {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
return Ok(path);
}
/// As above, but just parsing the raw byte stream.
///
/// Returns the (potentially empty or partial) path, and whether the parsing was ok or we found
/// an error. The API is a bit weird because some SVG callers require "parse until first error"
/// behavior.
pub fn parse_bytes(input: &[u8]) -> (Self, bool) {
// Parse the svg path string as multiple sub-paths.
let mut path_parser = PathParser::new(path_string);
let mut ok = true;
let mut path_parser = PathParser::new(input);
while skip_wsp(&mut path_parser.chars) {
if path_parser.parse_subpath().is_err() {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
ok = false;
break;
}
}
// The css-shapes-1 says a path data string that does conform but defines an empty path is
// invalid and causes the entire path() to be invalid, so we use the argement to decide
// whether we should allow the empty string.
// https://drafts.csswg.org/css-shapes-1/#typedef-basic-shape
if matches!(allow_empty, AllowEmpty::No) && path_parser.path.is_empty() {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let path = Self(crate::ArcSlice::from_iter(path_parser.path.into_iter()));
(path, ok)
}
Ok(SVGPathData(crate::ArcSlice::from_iter(
path_parser.path.into_iter(),
)))
/// Serializes to the path string, potentially including quotes.
pub fn to_css<W>(&self, dest: &mut CssWriter<W>, quote: bool) -> fmt::Result
where
W: fmt::Write,
{
if quote {
dest.write_char('"')?;
}
let mut writer = SequenceWriter::new(dest, " ");
for command in self.commands() {
writer.write_item(|inner| command.to_css_for_svg(inner))?;
}
if quote {
dest.write_char('"')?;
}
Ok(())
}
}
@ -214,14 +137,7 @@ impl ToCss for SVGPathData {
where
W: fmt::Write,
{
dest.write_char('"')?;
{
let mut writer = SequenceWriter::new(dest, " ");
for command in self.commands() {
writer.write_item(|inner| command.to_css_for_svg(inner))?;
}
}
dest.write_char('"')
self.to_css(dest, /* quote = */ true)
}
}
@ -547,9 +463,9 @@ macro_rules! parse_arguments {
impl<'a> PathParser<'a> {
/// Return a PathParser.
#[inline]
fn new(string: &'a str) -> Self {
fn new(bytes: &'a [u8]) -> Self {
PathParser {
chars: string.as_bytes().iter().cloned().peekable(),
chars: bytes.iter().cloned().peekable(),
path: Vec::new(),
}
}

Просмотреть файл

@ -5618,27 +5618,79 @@ pub extern "C" fn Servo_DeclarationBlock_SetLengthValue(
pub extern "C" fn Servo_DeclarationBlock_SetPathValue(
declarations: &LockedDeclarationBlock,
property: nsCSSPropertyID,
path: &nsTArray<f32>,
path: &specified::SVGPathData,
) {
use style::properties::PropertyDeclaration;
use style::values::specified::DProperty;
// 1. Decode the path data from SVG.
let path = match specified::SVGPathData::decode_from_f32_array(path) {
Ok(p) => p,
Err(()) => return,
};
// 2. Set decoded path into style.
let long = get_longhand_from_id!(property);
let prop = match_wrap_declared! { long,
D => if path.0.is_empty() { DProperty::None } else { DProperty::Path(path) },
D => if path.0.is_empty() { DProperty::None } else { DProperty::Path(path.clone()) },
};
write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
decls.push(prop, Importance::Normal);
})
}
#[no_mangle]
pub extern "C" fn Servo_SVGPathData_Add(
dest: &mut specified::SVGPathData,
to_add: &specified::SVGPathData,
count: u32,
) -> bool {
match dest.animate(to_add, Procedure::Accumulate { count: count as u64 }) {
Ok(r) => {
*dest = r;
true
}
Err(..) => false,
}
}
#[no_mangle]
pub extern "C" fn Servo_SVGPathData_Parse(input: &nsACString, dest: &mut specified::SVGPathData) -> bool {
let (path, ret) = specified::SVGPathData::parse_bytes(input.as_ref());
*dest = path;
ret
}
#[no_mangle]
pub extern "C" fn Servo_SVGPathData_ToString(path: &specified::SVGPathData, dest: &mut nsACString) {
path.to_css(&mut CssWriter::new(dest), /* quote = */ false).unwrap();
}
#[no_mangle]
pub extern "C" fn Servo_SVGPathData_Interpolate(
left: Option<&specified::SVGPathData>,
right: &specified::SVGPathData,
progress: f64,
dest: &mut specified::SVGPathData,
) -> bool {
let zero;
let left = match left {
Some(l) => l,
None => {
zero = match right.to_animated_zero() {
Ok(z) => z,
Err(..) => {
debug_assert!(false, "how?");
return false;
}
};
&zero
}
};
match left.animate(right, Procedure::Interpolate { progress }) {
Ok(r) => {
*dest = r;
true
}
Err(..) => false,
}
}
#[no_mangle]
pub extern "C" fn Servo_DeclarationBlock_SetPercentValue(
declarations: &LockedDeclarationBlock,

Просмотреть файл

@ -1,3 +1,5 @@
[animate-path-animation-Ll-Vv-Hh.tentative.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Path animation where coordinate modes of start and end differ (L-l, V-v and H-h)]
expected: FAIL

Просмотреть файл

@ -1,3 +1,5 @@
[animate-path-animation-Mm-Aa-Z.tentative.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Path animation where coordinate modes of start and end differ (M-m, A-a and Z)]
expected: FAIL

Просмотреть файл

@ -1,3 +1,5 @@
[animate-path-animation-Qq-Tt.tentative.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Path animation where coordinate modes of start and end differ (Q-q and T-t)]
expected: FAIL