Bug 1429298 - Part 5: Implement BuildPath for offset-path. r=jwatt
☠☠ backed out by e03480382807 ☠ ☠
authorBoris Chiou <boris.chiou@gmail.com>
Wed, 22 Aug 2018 01:21:45 +0000
changeset 487843 c217209a3b0494d4c4e6718aa6474221681aa660
parent 487842 196fc7b48b84303aaf9b6d8354d3ea9ff9fe5ef0
child 487844 cc2785ab879e48a01bf87c20aaebedbad0277574
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt
bugs1429298
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1429298 - Part 5: Implement BuildPath for offset-path. r=jwatt Implement one variant of BuildPath to accept nsTArray<StylePathCommand>, which is used by <offset-path> (and clip-path in the future). Depends on D3922 Differential Revision: https://phabricator.services.mozilla.com/D2967
dom/svg/SVGPathData.cpp
dom/svg/SVGPathData.h
--- a/dom/svg/SVGPathData.cpp
+++ b/dom/svg/SVGPathData.cpp
@@ -21,22 +21,51 @@
 #include "SVGGeometryElement.h" // for nsSVGMark
 #include "SVGPathSegUtils.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::dom::SVGPathSeg_Binding;
 using namespace mozilla::gfx;
 
-static bool IsMoveto(uint16_t aSegType)
+static inline bool IsMoveto(uint16_t aSegType)
 {
   return aSegType == PATHSEG_MOVETO_ABS ||
          aSegType == PATHSEG_MOVETO_REL;
 }
 
+static inline bool
+IsMoveto(StylePathCommand::Tag aSegType)
+{
+  return aSegType == StylePathCommand::Tag::MoveTo;
+}
+
+static inline bool
+IsValidType(uint16_t aSegType)
+{
+  return SVGPathSegUtils::IsValidType(aSegType);
+}
+
+static inline bool
+IsValidType(StylePathCommand::Tag aSegType)
+{
+  return aSegType != StylePathCommand::Tag::Unknown;
+}
+
+static inline bool
+IsClosePath(uint16_t aSegType) {
+  return aSegType == PATHSEG_CLOSEPATH;
+}
+
+static inline bool
+IsClosePath(StylePathCommand::Tag aSegType)
+{
+  return aSegType == StylePathCommand::Tag::ClosePath;
+}
+
 nsresult
 SVGPathData::CopyFrom(const SVGPathData& rhs)
 {
   if (!mData.Assign(rhs.mData, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
@@ -261,18 +290,18 @@ ApproximateZeroLengthSubpathSquareCaps(P
   aPB->LineTo(aPoint + Point(tinyLength, 0));
   aPB->MoveTo(aPoint);
 }
 
 #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT                \
   do {                                                                         \
     if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 &&                \
         subpathContainsNonMoveTo &&                                            \
-        SVGPathSegUtils::IsValidType(prevSegType) &&                           \
-        (!IsMoveto(prevSegType) || segType == PATHSEG_CLOSEPATH)) {            \
+        IsValidType(prevSegType) &&                                            \
+        (!IsMoveto(prevSegType) || IsClosePath(segType))) {                    \
       ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, aStrokeWidth);\
     }                                                                          \
   } while(0)
 
 already_AddRefed<Path>
 SVGPathData::BuildPath(PathBuilder* aBuilder,
                        uint8_t aStrokeLineCap,
                        Float aStrokeWidth) const
@@ -519,16 +548,219 @@ SVGPathData::BuildPathForMeasuring() con
 
   RefPtr<DrawTarget> drawTarget =
     gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
   RefPtr<PathBuilder> builder =
     drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
   return BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 0);
 }
 
+// We could simplify this function because this is only used by CSS motion path
+// and clip-path, which don't render the SVG Path. i.e. The returned path is
+// used as a reference.
+/* static */ already_AddRefed<Path>
+SVGPathData::BuildPath(const nsTArray<StylePathCommand>& aPath,
+                       PathBuilder* aBuilder,
+                       uint8_t aStrokeLineCap,
+                       Float aStrokeWidth)
+{
+  if (aPath.IsEmpty() || !aPath[0].IsMoveTo()) {
+    return nullptr; // paths without an initial moveto are invalid
+  }
+
+  auto toGfxPoint = [](const StyleCoordPair& aPair) {
+    return Point(aPair._0, aPair._1);
+  };
+
+  auto isCubicType = [](StylePathCommand::Tag aType) {
+    return aType == StylePathCommand::Tag::CurveTo ||
+           aType == StylePathCommand::Tag::SmoothCurveTo;
+  };
+
+  auto isQuadraticType = [](StylePathCommand::Tag aType) {
+    return aType == StylePathCommand::Tag::QuadBezierCurveTo ||
+           aType == StylePathCommand::Tag::SmoothQuadBezierCurveTo;
+  };
+
+  bool hasLineCaps = aStrokeLineCap != NS_STYLE_STROKE_LINECAP_BUTT;
+  bool subpathHasLength = false;  // visual length
+  bool subpathContainsNonMoveTo = false;
+
+  StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown;
+  StylePathCommand::Tag prevSegType = StylePathCommand::Tag::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.
+
+  for (const StylePathCommand& cmd: aPath) {
+    segType = cmd.tag;
+    switch (segType) {
+      case StylePathCommand::Tag::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 StylePathCommand::Tag::MoveTo: {
+        MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
+        const Point& p = toGfxPoint(cmd.move_to.point);
+        pathStart = segEnd = cmd.move_to.absolute ? p : segStart + p;
+        aBuilder->MoveTo(segEnd);
+        subpathHasLength = false;
+        break;
+      }
+      case StylePathCommand::Tag::LineTo: {
+        const Point& p = toGfxPoint(cmd.line_to.point);
+        segEnd = cmd.line_to.absolute ? p : segStart + p;
+        if (segEnd != segStart) {
+          subpathHasLength = true;
+          aBuilder->LineTo(segEnd);
+        }
+        break;
+      }
+      case StylePathCommand::Tag::CurveTo:
+        cp1 = toGfxPoint(cmd.curve_to.control1);
+        cp2 = toGfxPoint(cmd.curve_to.control2);
+        segEnd = toGfxPoint(cmd.curve_to.point);
+
+        if (!cmd.curve_to.absolute) {
+          cp1 += segStart;
+          cp2 += segStart;
+          segEnd += segStart;
+        }
+
+        if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
+          subpathHasLength = true;
+          aBuilder->BezierTo(cp1, cp2, segEnd);
+        }
+        break;
+
+      case StylePathCommand::Tag::QuadBezierCurveTo:
+        cp1 = toGfxPoint(cmd.quad_bezier_curve_to.control1);
+        segEnd = toGfxPoint(cmd.quad_bezier_curve_to.point);
+
+        if (!cmd.quad_bezier_curve_to.absolute) {
+          cp1 += segStart;
+          segEnd += segStart; // set before setting tcp2!
+        }
+
+        // Convert quadratic curve to cubic curve:
+        tcp1 = segStart + (cp1 - segStart) * 2 / 3;
+        tcp2 = cp1 + (segEnd - cp1) / 3;
+
+        if (segEnd != segStart || segEnd != cp1) {
+          subpathHasLength = true;
+          aBuilder->BezierTo(tcp1, tcp2, segEnd);
+        }
+        break;
+
+      case StylePathCommand::Tag::EllipticalArc: {
+        const auto& arc = cmd.elliptical_arc;
+        Point radii(arc.rx, arc.ry);
+        segEnd = toGfxPoint(arc.point);
+        if (!arc.absolute) {
+          segEnd += segStart;
+        }
+        if (segEnd != segStart) {
+          subpathHasLength = true;
+          if (radii.x == 0.0f || radii.y == 0.0f) {
+            aBuilder->LineTo(segEnd);
+          } else {
+            nsSVGArcConverter converter(segStart, segEnd, radii, arc.angle,
+                                        arc.large_arc_flag, arc.sweep_flag);
+            while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
+              aBuilder->BezierTo(cp1, cp2, segEnd);
+            }
+          }
+        }
+        break;
+      }
+      case StylePathCommand::Tag::HorizontalLineTo:
+        if (cmd.horizontal_line_to.absolute) {
+          segEnd = Point(cmd.horizontal_line_to.x, segStart.y);
+        } else {
+          segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f);
+        }
+
+        if (segEnd != segStart) {
+          subpathHasLength = true;
+          aBuilder->LineTo(segEnd);
+        }
+        break;
+
+      case StylePathCommand::Tag::VerticalLineTo:
+        if (cmd.vertical_line_to.absolute) {
+          segEnd = Point(segStart.x, cmd.vertical_line_to.y);
+        } else {
+          segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y);
+        }
+
+        if (segEnd != segStart) {
+          subpathHasLength = true;
+          aBuilder->LineTo(segEnd);
+        }
+        break;
+
+      case StylePathCommand::Tag::SmoothCurveTo:
+        cp1 = isCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
+        cp2 = toGfxPoint(cmd.smooth_curve_to.control2);
+        segEnd = toGfxPoint(cmd.smooth_curve_to.point);
+
+        if (!cmd.smooth_curve_to.absolute) {
+          cp2 += segStart;
+          segEnd += segStart;
+        }
+
+        if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
+          subpathHasLength = true;
+          aBuilder->BezierTo(cp1, cp2, segEnd);
+        }
+        break;
+
+      case StylePathCommand::Tag::SmoothQuadBezierCurveTo: {
+        cp1 = isQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
+        // Convert quadratic curve to cubic curve:
+        tcp1 = segStart + (cp1 - segStart) * 2 / 3;
+
+        const Point& p = toGfxPoint(cmd.smooth_quad_bezier_curve_to.point);
+        // set before setting tcp2!
+        segEnd = cmd.smooth_quad_bezier_curve_to.absolute ? p : segStart + p;
+        tcp2 = cp1 + (segEnd - cp1) / 3;
+
+        if (segEnd != segStart || segEnd != cp1) {
+          subpathHasLength = true;
+          aBuilder->BezierTo(tcp1, tcp2, segEnd);
+        }
+        break;
+      }
+      case StylePathCommand::Tag::Unknown:
+        MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
+        return nullptr;
+    }
+
+    subpathContainsNonMoveTo = !IsMoveto(segType);
+    prevSegType = segType;
+    segStart = segEnd;
+  }
+
+  MOZ_ASSERT(prevSegType == segType,
+             "prevSegType should be left at the final segType");
+
+  MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
+
+  return aBuilder->Finish();
+}
+
 static double
 AngleOfVector(const Point& aVector)
 {
   // C99 says about atan2 "A domain error may occur if both arguments are
   // zero" and "On a domain error, the function returns an implementation-
   // defined value". In the case of atan2 the implementation-defined value
   // seems to commonly be zero, but it could just as easily be a NaN value.
   // We specifically want zero in this case, hence the check:
--- a/dom/svg/SVGPathData.h
+++ b/dom/svg/SVGPathData.h
@@ -164,16 +164,26 @@ public:
    * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps.
    * See the comment for that function for more info on that.
    */
   already_AddRefed<Path> BuildPathForMeasuring() const;
 
   already_AddRefed<Path> BuildPath(PathBuilder* aBuilder,
                                uint8_t aCapStyle,
                                Float aStrokeWidth) const;
+  /**
+   * This function tries to build the path by an array of StylePathCommand,
+   * which is generated by cbindgen from Rust (see ServoStyleConsts.h).
+   * Basically, this is a variant of the above BuildPath() functions.
+   */
+  static already_AddRefed<Path>
+  BuildPath(const nsTArray<StylePathCommand>& aPath,
+            PathBuilder* aBuilder,
+            uint8_t aCapStyle,
+            Float aStrokeWidth);
 
   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;