summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGPathSegUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/SVGPathSegUtils.cpp')
-rw-r--r--dom/svg/SVGPathSegUtils.cpp452
1 files changed, 452 insertions, 0 deletions
diff --git a/dom/svg/SVGPathSegUtils.cpp b/dom/svg/SVGPathSegUtils.cpp
new file mode 100644
index 000000000..97d34a650
--- /dev/null
+++ b/dom/svg/SVGPathSegUtils.cpp
@@ -0,0 +1,452 @@
+/* -*- 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 "mozilla/ArrayUtils.h" // MOZ_ARRAY_LENGTH
+
+#include "SVGPathSegUtils.h"
+
+#include "gfx2DGlue.h"
+#include "nsSVGPathDataParser.h"
+#include "nsTextFormatter.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+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;
+ }
+ }
+
+ // nsTextFormatter::ssprintf is one of the nsTextFormatter methods that
+ // randomly appends '\0' to its output string, which means that the length
+ // of the output string is one too long. We need to manually remove that '\0'
+ // until nsTextFormatter is fixed.
+ //
+ if (aValue[aValue.Length() - 1] == char16_t('\0')) {
+ aValue.SetLength(aValue.Length() - 1);
+ }
+}
+
+
+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 += 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 += 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 += 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 += 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]);
+ Point bez[4] = { aState.pos, Point(0, 0), Point(0, 0), Point(0, 0) };
+ nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
+ aArgs[3] != 0, aArgs[4] != 0);
+ while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
+ dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
+ bez[0] = bez[3];
+ }
+ aState.length += dist;
+ aState.cp1 = aState.cp2 = to;
+ }
+ aState.pos = 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]);
+ Point bez[4] = { aState.pos, Point(0, 0), Point(0, 0), Point(0, 0) };
+ nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
+ aArgs[3] != 0, aArgs[4] != 0);
+ while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
+ dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
+ bez[0] = bez[3];
+ }
+ aState.length += dist;
+ aState.cp1 = aState.cp2 = to;
+ }
+ aState.pos = to;
+}
+
+
+typedef void (*TraverseFunc)(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);
+}