зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
7ec8891f51
Коммит
b1a45fd599
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче