summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGPathSegUtils.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /dom/svg/SVGPathSegUtils.cpp
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/svg/SVGPathSegUtils.cpp')
-rw-r--r--dom/svg/SVGPathSegUtils.cpp810
1 files changed, 810 insertions, 0 deletions
diff --git a/dom/svg/SVGPathSegUtils.cpp b/dom/svg/SVGPathSegUtils.cpp
new file mode 100644
index 0000000000..5300bd65b4
--- /dev/null
+++ b/dom/svg/SVGPathSegUtils.cpp
@@ -0,0 +1,810 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SVGPathSegUtils.h"
+
+#include "mozilla/ArrayUtils.h" // MOZ_ARRAY_LENGTH
+#include "mozilla/ServoStyleConsts.h" // StylePathCommand
+#include "gfx2DGlue.h"
+#include "SVGPathDataParser.h"
+#include "nsMathUtils.h"
+#include "nsTextFormatter.h"
+
+using namespace mozilla::dom::SVGPathSeg_Binding;
+using namespace mozilla::gfx;
+
+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);
+}
+
+static void SplitQuadraticBezier(const Point* aCurve, Point* aLeft,
+ Point* aRight) {
+ aLeft[0].x = aCurve[0].x;
+ aLeft[0].y = aCurve[0].y;
+ aRight[2].x = aCurve[2].x;
+ aRight[2].y = aCurve[2].y;
+ aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2;
+ aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2;
+ aRight[1].x = (aCurve[1].x + aCurve[2].x) / 2;
+ aRight[1].y = (aCurve[1].y + aCurve[2].y) / 2;
+ aLeft[2].x = aRight[0].x = (aLeft[1].x + aRight[1].x) / 2;
+ aLeft[2].y = aRight[0].y = (aLeft[1].y + aRight[1].y) / 2;
+}
+
+static void SplitCubicBezier(const Point* aCurve, Point* aLeft, Point* aRight) {
+ Point tmp;
+ tmp.x = (aCurve[1].x + aCurve[2].x) / 4;
+ tmp.y = (aCurve[1].y + aCurve[2].y) / 4;
+ aLeft[0].x = aCurve[0].x;
+ aLeft[0].y = aCurve[0].y;
+ aRight[3].x = aCurve[3].x;
+ aRight[3].y = aCurve[3].y;
+ aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2;
+ aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2;
+ aRight[2].x = (aCurve[2].x + aCurve[3].x) / 2;
+ aRight[2].y = (aCurve[2].y + aCurve[3].y) / 2;
+ aLeft[2].x = aLeft[1].x / 2 + tmp.x;
+ aLeft[2].y = aLeft[1].y / 2 + tmp.y;
+ aRight[1].x = aRight[2].x / 2 + tmp.x;
+ aRight[1].y = aRight[2].y / 2 + tmp.y;
+ aLeft[3].x = aRight[0].x = (aLeft[2].x + aRight[1].x) / 2;
+ aLeft[3].y = aRight[0].y = (aLeft[2].y + aRight[1].y) / 2;
+}
+
+static float CalcBezLengthHelper(const Point* aCurve, uint32_t aNumPts,
+ uint32_t aRecursionCount,
+ void (*aSplit)(const Point*, Point*, Point*)) {
+ Point left[4];
+ Point right[4];
+ float length = 0, dist;
+ for (uint32_t i = 0; i < aNumPts - 1; i++) {
+ length += CalcDistanceBetweenPoints(aCurve[i], aCurve[i + 1]);
+ }
+ dist = CalcDistanceBetweenPoints(aCurve[0], aCurve[aNumPts - 1]);
+ if (length - dist > PATH_SEG_LENGTH_TOLERANCE &&
+ aRecursionCount < MAX_RECURSION) {
+ aSplit(aCurve, left, right);
+ ++aRecursionCount;
+ return CalcBezLengthHelper(left, aNumPts, aRecursionCount, aSplit) +
+ CalcBezLengthHelper(right, aNumPts, aRecursionCount, aSplit);
+ }
+ return length;
+}
+
+static inline float CalcLengthOfCubicBezier(const Point& aPos,
+ const Point& aCP1,
+ const Point& aCP2,
+ const Point& aTo) {
+ Point curve[4] = {aPos, aCP1, aCP2, aTo};
+ return CalcBezLengthHelper(curve, 4, 0, SplitCubicBezier);
+}
+
+static inline float CalcLengthOfQuadraticBezier(const Point& aPos,
+ const Point& aCP,
+ const Point& aTo) {
+ Point curve[3] = {aPos, aCP, aTo};
+ 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.
+/* static */
+void SVGPathSegUtils::TraversePathSegment(const StylePathCommand& aCommand,
+ SVGPathTraversalState& aState) {
+ switch (aCommand.tag) {
+ case StylePathCommand::Tag::ClosePath:
+ TraverseClosePath(nullptr, aState);
+ break;
+ case StylePathCommand::Tag::MoveTo: {
+ const Point& p = aCommand.move_to.point.ConvertsToGfxPoint();
+ aState.start = aState.pos =
+ aCommand.move_to.absolute == StyleIsAbsolute::Yes ? p
+ : aState.pos + p;
+ if (aState.ShouldUpdateLengthAndControlPoints()) {
+ // aState.length is unchanged, since move commands don't affect path=
+ // length.
+ aState.cp1 = aState.cp2 = aState.start;
+ }
+ break;
+ }
+ case StylePathCommand::Tag::LineTo: {
+ Point to = aCommand.line_to.absolute == StyleIsAbsolute::Yes
+ ? aCommand.line_to.point.ConvertsToGfxPoint()
+ : aState.pos + aCommand.line_to.point.ConvertsToGfxPoint();
+ if (aState.ShouldUpdateLengthAndControlPoints()) {
+ aState.length += CalcDistanceBetweenPoints(aState.pos, to);
+ aState.cp1 = aState.cp2 = to;
+ }
+ aState.pos = to;
+ break;
+ }
+ case StylePathCommand::Tag::CurveTo: {
+ const bool isRelative = aCommand.curve_to.absolute == StyleIsAbsolute::No;
+ Point to = isRelative
+ ? aState.pos + aCommand.curve_to.point.ConvertsToGfxPoint()
+ : aCommand.curve_to.point.ConvertsToGfxPoint();
+ if (aState.ShouldUpdateLengthAndControlPoints()) {
+ Point cp1 = aCommand.curve_to.control1.ConvertsToGfxPoint();
+ Point cp2 = aCommand.curve_to.control2.ConvertsToGfxPoint();
+ if (isRelative) {
+ cp1 += aState.pos;
+ cp2 += aState.pos;
+ }
+ aState.length +=
+ (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
+ aState.cp2 = cp2;
+ aState.cp1 = to;
+ }
+ aState.pos = to;
+ break;
+ }
+ case StylePathCommand::Tag::QuadBezierCurveTo: {
+ const bool isRelative = aCommand.curve_to.absolute == StyleIsAbsolute::No;
+ Point to =
+ isRelative
+ ? aState.pos +
+ aCommand.quad_bezier_curve_to.point.ConvertsToGfxPoint()
+ : aCommand.quad_bezier_curve_to.point.ConvertsToGfxPoint();
+ if (aState.ShouldUpdateLengthAndControlPoints()) {
+ Point cp =
+ isRelative
+ ? aState.pos + aCommand.quad_bezier_curve_to.control1
+ .ConvertsToGfxPoint()
+ : aCommand.quad_bezier_curve_to.control1.ConvertsToGfxPoint();
+ aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
+ aState.cp1 = cp;
+ aState.cp2 = to;
+ }
+ aState.pos = to;
+ break;
+ }
+ case StylePathCommand::Tag::EllipticalArc: {
+ Point to =
+ aCommand.elliptical_arc.absolute == StyleIsAbsolute::Yes
+ ? aCommand.elliptical_arc.point.ConvertsToGfxPoint()
+ : aState.pos + aCommand.elliptical_arc.point.ConvertsToGfxPoint();
+ if (aState.ShouldUpdateLengthAndControlPoints()) {
+ const auto& arc = aCommand.elliptical_arc;
+ float dist = 0;
+ Point radii(arc.rx, arc.ry);
+ 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, arc.angle,
+ arc.large_arc_flag._0, arc.sweep_flag._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;
+ break;
+ }
+ case StylePathCommand::Tag::HorizontalLineTo: {
+ Point to(aCommand.horizontal_line_to.absolute == StyleIsAbsolute::Yes
+ ? aCommand.horizontal_line_to.x
+ : aState.pos.x + aCommand.horizontal_line_to.x,
+ aState.pos.y);
+ if (aState.ShouldUpdateLengthAndControlPoints()) {
+ aState.length += std::fabs(to.x - aState.pos.x);
+ aState.cp1 = aState.cp2 = to;
+ }
+ aState.pos = to;
+ break;
+ }
+ case StylePathCommand::Tag::VerticalLineTo: {
+ Point to(aState.pos.x,
+ aCommand.vertical_line_to.absolute == StyleIsAbsolute::Yes
+ ? aCommand.vertical_line_to.y
+ : aState.pos.y + aCommand.vertical_line_to.y);
+ if (aState.ShouldUpdateLengthAndControlPoints()) {
+ aState.length += std::fabs(to.y - aState.pos.y);
+ aState.cp1 = aState.cp2 = to;
+ }
+ aState.pos = to;
+ break;
+ }
+ case StylePathCommand::Tag::SmoothCurveTo: {
+ const bool isRelative =
+ aCommand.smooth_curve_to.absolute == StyleIsAbsolute::No;
+ Point to =
+ isRelative
+ ? aState.pos + aCommand.smooth_curve_to.point.ConvertsToGfxPoint()
+ : aCommand.smooth_curve_to.point.ConvertsToGfxPoint();
+ if (aState.ShouldUpdateLengthAndControlPoints()) {
+ Point cp1 = aState.pos - (aState.cp2 - aState.pos);
+ Point cp2 =
+ isRelative
+ ? aState.pos +
+ aCommand.smooth_curve_to.control2.ConvertsToGfxPoint()
+ : aCommand.smooth_curve_to.control2.ConvertsToGfxPoint();
+ aState.length +=
+ (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
+ aState.cp2 = cp2;
+ aState.cp1 = to;
+ }
+ aState.pos = to;
+ break;
+ }
+ case StylePathCommand::Tag::SmoothQuadBezierCurveTo: {
+ Point to = aCommand.smooth_curve_to.absolute == StyleIsAbsolute::Yes
+ ? aCommand.smooth_curve_to.point.ConvertsToGfxPoint()
+ : aState.pos +
+ aCommand.smooth_curve_to.point.ConvertsToGfxPoint();
+ 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;
+ break;
+ }
+ case StylePathCommand::Tag::Unknown:
+ MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
+ }
+}
+
+// Possible directions of an edge that doesn't immediately disqualify the path
+// as a rectangle.
+enum class EdgeDir {
+ LEFT,
+ RIGHT,
+ UP,
+ DOWN,
+ // NONE represents (almost) zero-length edges, they should be ignored.
+ NONE,
+};
+
+Maybe<EdgeDir> GetDirection(Point v) {
+ if (!std::isfinite(v.x.value) || !std::isfinite(v.y.value)) {
+ return Nothing();
+ }
+
+ bool x = fabs(v.x) > 0.001;
+ bool y = fabs(v.y) > 0.001;
+ if (x && y) {
+ return Nothing();
+ }
+
+ if (!x && !y) {
+ return Some(EdgeDir::NONE);
+ }
+
+ if (x) {
+ return Some(v.x > 0.0 ? EdgeDir::RIGHT : EdgeDir::LEFT);
+ }
+
+ return Some(v.y > 0.0 ? EdgeDir::DOWN : EdgeDir::UP);
+}
+
+EdgeDir OppositeDirection(EdgeDir dir) {
+ switch (dir) {
+ case EdgeDir::LEFT:
+ return EdgeDir::RIGHT;
+ case EdgeDir::RIGHT:
+ return EdgeDir::LEFT;
+ case EdgeDir::UP:
+ return EdgeDir::DOWN;
+ case EdgeDir::DOWN:
+ return EdgeDir::UP;
+ default:
+ return EdgeDir::NONE;
+ }
+}
+
+struct IsRectHelper {
+ Point min;
+ Point max;
+ EdgeDir currentDir;
+ // Index of the next corner.
+ uint32_t idx;
+ EdgeDir dirs[4];
+
+ bool Edge(Point from, Point to) {
+ auto edge = to - from;
+
+ auto maybeDir = GetDirection(edge);
+ if (maybeDir.isNothing()) {
+ return false;
+ }
+
+ EdgeDir dir = maybeDir.value();
+
+ if (dir == EdgeDir::NONE) {
+ // zero-length edges aren't an issue.
+ return true;
+ }
+
+ if (dir != currentDir) {
+ // The edge forms a corner with the previous edge.
+ if (idx >= 4) {
+ // We are at the 5th corner, can't be a rectangle.
+ return false;
+ }
+
+ if (dir == OppositeDirection(currentDir)) {
+ // Can turn left or right but not a full 180 degrees.
+ return false;
+ }
+
+ dirs[idx] = dir;
+ idx += 1;
+ currentDir = dir;
+ }
+
+ min.x = fmin(min.x, to.x);
+ min.y = fmin(min.y, to.y);
+ max.x = fmax(max.x, to.x);
+ max.y = fmax(max.y, to.y);
+
+ return true;
+ }
+
+ bool EndSubpath() {
+ if (idx != 4) {
+ return false;
+ }
+
+ if (dirs[0] != OppositeDirection(dirs[2]) ||
+ dirs[1] != OppositeDirection(dirs[3])) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+bool ApproxEqual(gfx::Point a, gfx::Point b) {
+ auto v = b - a;
+ return fabs(v.x) < 0.001 && fabs(v.y) < 0.001;
+}
+
+Maybe<gfx::Rect> SVGPathToAxisAlignedRect(Span<const StylePathCommand> aPath) {
+ Point pathStart(0.0, 0.0);
+ Point segStart(0.0, 0.0);
+ IsRectHelper helper = {
+ Point(0.0, 0.0),
+ Point(0.0, 0.0),
+ EdgeDir::NONE,
+ 0,
+ {EdgeDir::NONE, EdgeDir::NONE, EdgeDir::NONE, EdgeDir::NONE},
+ };
+
+ for (const StylePathCommand& cmd : aPath) {
+ switch (cmd.tag) {
+ case StylePathCommand::Tag::MoveTo: {
+ Point to = cmd.move_to.point.ConvertsToGfxPoint();
+ if (helper.idx != 0) {
+ // This is overly strict since empty moveto sequences such as "M 10 12
+ // M 3 2 M 0 0" render nothing, but I expect it won't make us miss a
+ // lot of rect-shaped paths in practice and lets us avoidhandling
+ // special caps for empty sub-paths like "M 0 0 L 0 0" and "M 1 2 Z".
+ return Nothing();
+ }
+
+ if (!ApproxEqual(pathStart, segStart)) {
+ // If we were only interested in filling we could auto-close here
+ // by calling helper.Edge like in the ClosePath case and detect some
+ // unclosed paths as rectangles.
+ //
+ // For example:
+ // - "M 1 0 L 0 0 L 0 1 L 1 1 L 1 0" are both rects for filling and
+ // stroking.
+ // - "M 1 0 L 0 0 L 0 1 L 1 1" fills a rect but the stroke is shaped
+ // like a C.
+ return Nothing();
+ }
+
+ if (helper.idx != 0 && !helper.EndSubpath()) {
+ return Nothing();
+ }
+
+ if (cmd.move_to.absolute == StyleIsAbsolute::No) {
+ to = segStart + to;
+ }
+
+ pathStart = to;
+ segStart = to;
+ if (helper.idx == 0) {
+ helper.min = to;
+ helper.max = to;
+ }
+
+ break;
+ }
+ case StylePathCommand::Tag::ClosePath: {
+ if (!helper.Edge(segStart, pathStart)) {
+ return Nothing();
+ }
+ if (!helper.EndSubpath()) {
+ return Nothing();
+ }
+ pathStart = segStart;
+ break;
+ }
+ case StylePathCommand::Tag::LineTo: {
+ Point to = cmd.line_to.point.ConvertsToGfxPoint();
+ if (cmd.line_to.absolute == StyleIsAbsolute::No) {
+ to = segStart + to;
+ }
+
+ if (!helper.Edge(segStart, to)) {
+ return Nothing();
+ }
+ segStart = to;
+ break;
+ }
+ case StylePathCommand::Tag::HorizontalLineTo: {
+ Point to = gfx::Point(cmd.horizontal_line_to.x, segStart.y);
+ if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::No) {
+ to.x += segStart.x;
+ }
+
+ if (!helper.Edge(segStart, to)) {
+ return Nothing();
+ }
+ segStart = to;
+ break;
+ }
+ case StylePathCommand::Tag::VerticalLineTo: {
+ Point to = gfx::Point(segStart.x, cmd.vertical_line_to.y);
+ if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::No) {
+ to.y += segStart.y;
+ }
+
+ if (!helper.Edge(segStart, to)) {
+ return Nothing();
+ }
+ segStart = to;
+ break;
+ }
+ default:
+ return Nothing();
+ }
+ }
+
+ if (!ApproxEqual(pathStart, segStart)) {
+ // Same situation as with moveto regarding stroking not fullly closed path
+ // even though the fill is a rectangle.
+ return Nothing();
+ }
+
+ if (!helper.EndSubpath()) {
+ return Nothing();
+ }
+
+ auto size = (helper.max - helper.min);
+ return Some(Rect(helper.min, Size(size.x, size.y)));
+}
+
+} // namespace mozilla