Add arc methods to path builder

Bug: skia:9000
Change-Id: I0a25c6f792f59230762651386da74e547b073930
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/307558
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Mike Reed <reed@google.com>
diff --git a/gm/aarectmodes.cpp b/gm/aarectmodes.cpp
index cd14096..31e6ef6 100644
--- a/gm/aarectmodes.cpp
+++ b/gm/aarectmodes.cpp
@@ -14,6 +14,7 @@
 #include "include/core/SkMatrix.h"
 #include "include/core/SkPaint.h"
 #include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
 #include "include/core/SkPoint.h"
 #include "include/core/SkRect.h"
 #include "include/core/SkRefCnt.h"
@@ -44,7 +45,7 @@
         0, 1, 1, 1, 4,
         0, 1, 1, 1, 4
     };
-    SkPath path;
+    SkPathBuilder path;
     SkPoint* ptPtr = pts;
     for (size_t i = 0; i < sizeof(verbs); ++i) {
         switch ((SkPath::Verb) verbs[i]) {
@@ -66,7 +67,7 @@
     }
     SkRect clip = {0, 130, 772, 531};
     canvas->clipRect(clip);
-    canvas->drawPath(path, paint);
+    canvas->drawPath(path.detach(), paint);
 }
 
 constexpr SkBlendMode gModes[] = {
diff --git a/gm/addarc.cpp b/gm/addarc.cpp
index eeed7d6..3f4d578 100644
--- a/gm/addarc.cpp
+++ b/gm/addarc.cpp
@@ -10,6 +10,7 @@
 #include "include/core/SkColor.h"
 #include "include/core/SkPaint.h"
 #include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
 #include "include/core/SkPathMeasure.h"
 #include "include/core/SkPoint.h"
 #include "include/core/SkRect.h"
@@ -215,7 +216,7 @@
 
 //////////////////////
 
-static void html_canvas_arc(SkPath* path, SkScalar x, SkScalar y, SkScalar r, SkScalar start,
+static void html_canvas_arc(SkPathBuilder* path, SkScalar x, SkScalar y, SkScalar r, SkScalar start,
                             SkScalar end, bool ccw, bool callArcTo) {
     SkRect bounds = { x - r, y - r, x + r, y + r };
     SkScalar sweep = ccw ? end - start : start - end;
@@ -257,12 +258,12 @@
             SkScalar startAngle = startAngles[i % SK_ARRAY_COUNT(startAngles)] * sign;
             canvas->save();
             for (size_t j = 0; j < SK_ARRAY_COUNT(sweepAngles); ++j) {
-                SkPath path;
+                SkPathBuilder path;
                 path.moveTo(0, 2);
                 html_canvas_arc(&path, 18, 15, 10, startAngle, startAngle + (sweepAngles[j] * sign),
                                 anticlockwise, true);
                 path.lineTo(0, 28);
-                canvas->drawPath(path, paint);
+                canvas->drawPath(path.detach(), paint);
                 canvas->translate(30, 0);
             }
             canvas->restore();
@@ -286,7 +287,7 @@
         SkScalar sweepAngle = 10.0f / outerRadius;
 
         for (size_t i = 0; i < SK_ARRAY_COUNT(startAngles); ++i) {
-            SkPath path;
+            SkPathBuilder path;
             SkScalar endAngle = startAngles[i] + sweepAngle;
             path.moveTo(centerX + innerRadius * sk_float_cos(startAngles[i]),
                         centerY + innerRadius * sk_float_sin(startAngles[i]));
@@ -301,7 +302,7 @@
             html_canvas_arc(&path, centerX, outerRadius, innerRadius,
                             endAngle * 180 / SK_ScalarPI, startAngles[i] * 180 / SK_ScalarPI,
                             true, false);
-            canvas->drawPath(path, paint);
+            canvas->drawPath(path.detach(), paint);
             canvas->translate(20, 0);
         }
 }
diff --git a/gm/arcto.cpp b/gm/arcto.cpp
index 03942fe..20528c8 100644
--- a/gm/arcto.cpp
+++ b/gm/arcto.cpp
@@ -9,7 +9,7 @@
 #include "include/core/SkCanvas.h"
 #include "include/core/SkColor.h"
 #include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
 #include "include/core/SkPathEffect.h"
 #include "include/core/SkPathMeasure.h"
 #include "include/core/SkRect.h"
@@ -72,23 +72,21 @@
     paint.setColor(0xFF660000);
 //    canvas->scale(2, 2);  // for testing on retina
     SkRect oval = SkRect::MakeXYWH(100, 100, 100, 100);
-    SkPath svgArc;
 
-    for (int angle = 0; angle <= 45; angle += 45) {
-       for (int oHeight = 2; oHeight >= 1; --oHeight) {
+    for (SkScalar angle = 0; angle <= 45; angle += 45) {
+        for (int oHeight = 2; oHeight >= 1; --oHeight) {
+            SkPathBuilder svgArc;
             SkScalar ovalHeight = oval.height() / oHeight;
             svgArc.moveTo(oval.fLeft, oval.fTop);
-            svgArc.arcTo(oval.width() / 2, ovalHeight, SkIntToScalar(angle), SkPath::kSmall_ArcSize,
-                    SkPathDirection::kCW, oval.right(), oval.bottom());
-            canvas->drawPath(svgArc, paint);
-            svgArc.reset();
+            svgArc.arcTo({oval.width() / 2, ovalHeight}, angle, SkPathBuilder::kSmall_ArcSize,
+                         SkPathDirection::kCW, {oval.right(), oval.bottom()});
+            canvas->drawPath(svgArc.detach(), paint);
 
             svgArc.moveTo(oval.fLeft + 100, oval.fTop + 100);
-            svgArc.arcTo(oval.width() / 2, ovalHeight, SkIntToScalar(angle), SkPath::kLarge_ArcSize,
-                    SkPathDirection::kCCW, oval.right(), oval.bottom() + 100);
-            canvas->drawPath(svgArc, paint);
+            svgArc.arcTo({oval.width() / 2, ovalHeight}, angle, SkPathBuilder::kLarge_ArcSize,
+                         SkPathDirection::kCCW, {oval.right(), oval.bottom() + 100});
+            canvas->drawPath(svgArc.detach(), paint);
             oval.offset(50, 0);
-            svgArc.reset();
 
         }
     }
@@ -105,22 +103,22 @@
     };
     int cIndex = 0;
     for (const char* arcstr : arcstrs) {
-        SkParsePath::FromSVGString(arcstr, &svgArc);
+        SkPath path;
+        SkParsePath::FromSVGString(arcstr, &path);
         paint.setColor(colors[cIndex++]);
-        canvas->drawPath(svgArc, paint);
+        canvas->drawPath(path, paint);
     }
 
     // test that zero length arcs still draw round cap
     paint.setStrokeCap(SkPaint::kRound_Cap);
-    SkPath path;
-    path.moveTo(100, 100);
-    path.arcTo(0, 0, 0, SkPath::kLarge_ArcSize, SkPathDirection::kCW, 200, 200);
-    canvas->drawPath(path, paint);
+    SkPathBuilder path;
+    path.moveTo(100, 100)
+        .arcTo({0, 0}, 0, SkPathBuilder::kLarge_ArcSize, SkPathDirection::kCW, {200, 200});
+    canvas->drawPath(path.detach(), paint);
 
-    path.reset();
-    path.moveTo(200, 100);
-    path.arcTo(80, 80, 0, SkPath::kLarge_ArcSize, SkPathDirection::kCW, 200, 100);
-    canvas->drawPath(path, paint);
+    path.moveTo(200, 100)
+        .arcTo({80, 80}, 0, SkPathBuilder::kLarge_ArcSize, SkPathDirection::kCW, {200, 100});
+    canvas->drawPath(path.detach(), paint);
 }
 
 enum {
@@ -200,7 +198,7 @@
 DEF_SIMPLE_GM(bug593049, canvas, 300, 300) {
     canvas->translate(111, 0);
 
-    SkPath p;
+    SkPathBuilder p;
     p.moveTo(-43.44464063610148f, 79.43535936389853f);
     const SkScalar yOffset = 122.88f;
     const SkScalar radius = 61.44f;
@@ -212,7 +210,7 @@
     paint.setStrokeCap(SkPaint::kRound_Cap);
     paint.setStrokeWidth(15.36f);
 
-    canvas->drawPath(p, paint);
+    canvas->drawPath(p.detach(), paint);
 }
 
 DEF_SIMPLE_GM(bug583299, canvas, 300, 300) {
diff --git a/gm/complexclip3.cpp b/gm/complexclip3.cpp
index b6fea1f..3c160e0 100644
--- a/gm/complexclip3.cpp
+++ b/gm/complexclip3.cpp
@@ -11,7 +11,7 @@
 #include "include/core/SkColor.h"
 #include "include/core/SkFont.h"
 #include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
 #include "include/core/SkRect.h"
 #include "include/core/SkScalar.h"
 #include "include/core/SkSize.h"
@@ -45,21 +45,18 @@
     SkISize onISize() override { return SkISize::Make(400, 950); }
 
     void onDraw(SkCanvas* canvas) override {
-        SkPath clipSimple;
-        clipSimple.addCircle(SkIntToScalar(70), SkIntToScalar(50), SkIntToScalar(20));
+        SkPath clipSimple = SkPath::Circle(70, 50, 20);
 
         SkRect r1 = { 10, 20, 70, 80 };
-        SkPath clipComplex;
-        clipComplex.moveTo(SkIntToScalar(40),  SkIntToScalar(50));
-        clipComplex.arcTo(r1, SkIntToScalar(30), SkIntToScalar(300), false);
-        clipComplex.close();
+        SkPath clipComplex = SkPathBuilder().moveTo(40,  50)
+                                            .arcTo(r1, 30, 300, false)
+                                            .close()
+                                            .detach();
 
         SkPath* firstClip = &clipSimple;
         SkPath* secondClip = &clipComplex;
-
         if (!fDoSimpleClipFirst) {
-            using std::swap;
-            swap(firstClip, secondClip);
+            std::swap(firstClip, secondClip);
         }
 
         SkPaint paint;
diff --git a/gm/conicpaths.cpp b/gm/conicpaths.cpp
index f3ceaea..f68edbd 100644
--- a/gm/conicpaths.cpp
+++ b/gm/conicpaths.cpp
@@ -156,9 +156,9 @@
     paint.setAntiAlias(true);
     paint.setStroke(true);
     canvas->drawCircle(c, radius, paint);
-    SkPath path;
-    path.moveTo(288.88884710654133f, -280.26680862609f);
-    path.arcTo(0, 0, -39.00216443306411f, 400.6058925796476f, radius);
+    SkPath path = SkPathBuilder().moveTo(288.88884710654133f, -280.26680862609f)
+                                 .arcTo({0, 0}, {-39.00216443306411f, 400.6058925796476f}, radius)
+                                 .detach();
     paint.setColor(0xff007f00);
     canvas->drawPath(path, paint);
 }
diff --git a/gm/crbug_996140.cpp b/gm/crbug_996140.cpp
index d2d2b6d..1cc6097 100644
--- a/gm/crbug_996140.cpp
+++ b/gm/crbug_996140.cpp
@@ -9,7 +9,7 @@
 #include "include/core/SkCanvas.h"
 #include "include/core/SkColor.h"
 #include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
 
 /*
  * Canvas example. Expected large blue stroked circle, white middle, small red circle.
@@ -43,7 +43,6 @@
     canvas->translate(-800, -200);
 
     // 3: ctx.beginPath();
-    SkPath path;
 
     // 4: ctx.scale(203.20, 203.20);
     canvas->scale(s, s);
@@ -66,8 +65,10 @@
     // 9: ctx.arc(19.221, 720-6.76,0.0295275590551181,0,2*Math.PI);
     // This matches how Canvas prepares an arc(x, y, radius, 0, 2pi) call
     SkRect boundingBox = SkRect::MakeLTRB(cx - radius, cy - radius, cx + radius, cy + radius);
-    path.arcTo(boundingBox, 0, 180.f, false);
-    path.arcTo(boundingBox, 180.f, 180.f, false);
+
+    auto path = SkPathBuilder().arcTo(boundingBox, 0, 180.f, false)
+                               .arcTo(boundingBox, 180.f, 180.f, false)
+                               .detach();
 
     // 12: ctx.closePath();
     // path.close();
diff --git a/gm/overstroke.cpp b/gm/overstroke.cpp
index 6352a94..ecac1bb 100644
--- a/gm/overstroke.cpp
+++ b/gm/overstroke.cpp
@@ -26,7 +26,7 @@
 #include "include/core/SkCanvas.h"
 #include "include/core/SkColor.h"
 #include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
 #include "include/core/SkPathMeasure.h"
 #include "include/core/SkPoint.h"
 #include "include/core/SkRect.h"
@@ -83,11 +83,7 @@
 SkPath oval_path() {
     SkRect oval = SkRect::MakeXYWH(0, -25, 100, 50);
 
-    SkPath path;
-    path.arcTo(oval, 0, 359, true);
-    path.close();
-
-    return path;
+    return SkPathBuilder().arcTo(oval, 0, 359, true).close().detach();
 }
 
 SkPath ribs_path(SkPath path, SkScalar radius) {
diff --git a/gm/patharcto.cpp b/gm/patharcto.cpp
index 1350033..d67033d 100644
--- a/gm/patharcto.cpp
+++ b/gm/patharcto.cpp
@@ -8,7 +8,7 @@
 #include "gm/gm.h"
 #include "include/core/SkCanvas.h"
 #include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
 
 // crbug.com/982968
 // Intended to draw a curvy triangle.
@@ -18,25 +18,25 @@
 // The fix was to use doubles for this part of the calc in SkPath::arcTo().
 //
 DEF_SIMPLE_GM(shallow_angle_path_arcto, canvas, 300, 300) {
-    SkPath path;
+    SkPathBuilder path;
     SkPaint paint;
     paint.setStyle(SkPaint::kStroke_Style);
 
-    path.moveTo(313.44189096331155f, 106.6009423589212f);
-    path.arcTo(284.3113082008462f, 207.1407719157063f,
-               255.15053777129728f, 307.6718505416374f,
-               697212.0011054524f);
-    path.lineTo(255.15053777129728f, 307.6718505416374f);
-    path.arcTo(340.4737465981018f, 252.6907319346971f,
-               433.54333477716153f, 212.18116363345337f,
-               1251.2484277907251f);
-    path.lineTo(433.54333477716153f, 212.18116363345337f);
-    path.arcTo(350.19513833839466f, 185.89280014838369f,
-               313.44189096331155f, 106.6009423589212f,
+    path.moveTo(313.44189096331155f, 106.6009423589212f)
+        .arcTo({284.3113082008462f, 207.1407719157063f},
+               {255.15053777129728f, 307.6718505416374f},
+               697212.0011054524f)
+        .lineTo(255.15053777129728f, 307.6718505416374f)
+        .arcTo({340.4737465981018f, 252.6907319346971f},
+               {433.54333477716153f, 212.18116363345337f},
+               1251.2484277907251f)
+        .lineTo(433.54333477716153f, 212.18116363345337f)
+        .arcTo({350.19513833839466f, 185.89280014838369f},
+               {313.44189096331155f, 106.6009423589212f},
                198.03116885327813f);
 
     canvas->translate(-200, -50);
-    canvas->drawPath(path, paint);
+    canvas->drawPath(path.detach(), paint);
 };
 
 #include "include/utils/SkParsePath.h"
diff --git a/gm/pathfill.cpp b/gm/pathfill.cpp
index 9d908cb..17244c5 100644
--- a/gm/pathfill.cpp
+++ b/gm/pathfill.cpp
@@ -9,7 +9,7 @@
 #include "include/core/SkCanvas.h"
 #include "include/core/SkColor.h"
 #include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
 #include "include/core/SkRect.h"
 #include "include/core/SkScalar.h"
 #include "include/core/SkSize.h"
@@ -668,7 +668,7 @@
     p.setStyle(SkPaint::kStroke_Style);
     p.setStrokeWidth(2);
 
-    SkPath path;
+    SkPathBuilder path;
     SkPoint pts[] = { {20, 20}, {100, 20}, {100, 60}, {130, 150}, {180, 160} };
     SkScalar radius = 60;
     path.moveTo(pts[0]);
@@ -676,5 +676,5 @@
     path.lineTo(pts[2]);
     path.close();
     path.arcTo(pts[3], pts[4], radius);
-    canvas->drawPath(path, p);
+    canvas->drawPath(path.detach(), p);
 }
diff --git a/include/core/SkPathBuilder.h b/include/core/SkPathBuilder.h
index 86e48f9..029f851 100644
--- a/include/core/SkPathBuilder.h
+++ b/include/core/SkPathBuilder.h
@@ -73,7 +73,95 @@
         return this->rCubicTo({x1, y1}, {x2, y2}, {x3, y3});
     }
 
-    // Add a closed contour
+    // Arcs
+
+    /** Appends arc to the builder. Arc added is part of ellipse
+        bounded by oval, from startAngle through sweepAngle. Both startAngle and
+        sweepAngle are measured in degrees, where zero degrees is aligned with the
+        positive x-axis, and positive sweeps extends arc clockwise.
+
+        arcTo() adds line connecting the builder's last point to initial arc point if forceMoveTo
+        is false and the builder is not empty. Otherwise, added contour begins with first point
+        of arc. Angles greater than -360 and less than 360 are treated modulo 360.
+
+        @param oval          bounds of ellipse containing arc
+        @param startAngleDeg starting angle of arc in degrees
+        @param sweepAngleDeg sweep, in degrees. Positive is clockwise; treated modulo 360
+        @param forceMoveTo   true to start a new contour with arc
+        @return              reference to the builder
+    */
+    SkPathBuilder& arcTo(const SkRect& oval, SkScalar startAngleDeg, SkScalar sweepAngleDeg,
+                         bool forceMoveTo);
+
+    /** Appends arc to SkPath, after appending line if needed. Arc is implemented by conic
+        weighted to describe part of circle. Arc is contained by tangent from
+        last SkPath point to p1, and tangent from p1 to p2. Arc
+        is part of circle sized to radius, positioned so it touches both tangent lines.
+
+        If last SkPath SkPoint does not start arc, arcTo() appends connecting line to SkPath.
+        The length of vector from p1 to p2 does not affect arc.
+
+        Arc sweep is always less than 180 degrees. If radius is zero, or if
+        tangents are nearly parallel, arcTo() appends line from last SkPath SkPoint to p1.
+
+        arcTo() appends at most one line and one conic.
+        arcTo() implements the functionality of PostScript arct and HTML Canvas arcTo.
+
+        @param p1      SkPoint common to pair of tangents
+        @param p2      end of second tangent
+        @param radius  distance from arc to circle center
+        @return        reference to SkPath
+    */
+    SkPathBuilder& arcTo(SkPoint p1, SkPoint p2, SkScalar radius);
+
+    enum ArcSize {
+        kSmall_ArcSize, //!< smaller of arc pair
+        kLarge_ArcSize, //!< larger of arc pair
+    };
+
+    /** Appends arc to SkPath. Arc is implemented by one or more conic weighted to describe
+        part of oval with radii (r.fX, r.fY) rotated by xAxisRotate degrees. Arc curves
+        from last SkPath SkPoint to (xy.fX, xy.fY), choosing one of four possible routes:
+        clockwise or counterclockwise,
+        and smaller or larger.
+
+        Arc sweep is always less than 360 degrees. arcTo() appends line to xy if either
+        radii are zero, or if last SkPath SkPoint equals (xy.fX, xy.fY). arcTo() scales radii r to
+        fit last SkPath SkPoint and xy if both are greater than zero but too small to describe
+        an arc.
+
+        arcTo() appends up to four conic curves.
+        arcTo() implements the functionality of SVG arc, although SVG sweep-flag value is
+        opposite the integer value of sweep; SVG sweep-flag uses 1 for clockwise, while
+        kCW_Direction cast to int is zero.
+
+        @param r            radii on axes before x-axis rotation
+        @param xAxisRotate  x-axis rotation in degrees; positive values are clockwise
+        @param largeArc     chooses smaller or larger arc
+        @param sweep        chooses clockwise or counterclockwise arc
+        @param xy           end of arc
+        @return             reference to SkPath
+    */
+    SkPathBuilder& arcTo(SkPoint r, SkScalar xAxisRotate, ArcSize largeArc, SkPathDirection sweep,
+                         SkPoint xy);
+
+    /** Appends arc to the builder, as the start of new contour. Arc added is part of ellipse
+        bounded by oval, from startAngle through sweepAngle. Both startAngle and
+        sweepAngle are measured in degrees, where zero degrees is aligned with the
+        positive x-axis, and positive sweeps extends arc clockwise.
+
+        If sweepAngle <= -360, or sweepAngle >= 360; and startAngle modulo 90 is nearly
+        zero, append oval instead of arc. Otherwise, sweepAngle values are treated
+        modulo 360, and arc may or may not draw depending on numeric rounding.
+
+        @param oval          bounds of ellipse containing arc
+        @param startAngleDeg starting angle of arc in degrees
+        @param sweepAngleDeg sweep, in degrees. Positive is clockwise; treated modulo 360
+        @return              reference to this builder
+    */
+    SkPathBuilder& addArc(const SkRect& oval, SkScalar startAngleDeg, SkScalar sweepAngleDeg);
+
+    // Add a new contour
 
     SkPathBuilder& addRect(const SkRect&, SkPathDirection, unsigned startIndex);
     SkPathBuilder& addOval(const SkRect&, SkPathDirection, unsigned startIndex);
diff --git a/include/private/SkTDArray.h b/include/private/SkTDArray.h
index 9d475f6..6ce7170 100644
--- a/include/private/SkTDArray.h
+++ b/include/private/SkTDArray.h
@@ -125,6 +125,8 @@
         return (*this)[index];
     }
 
+    const T& back() const { SkASSERT(fCount > 0); return fArray[fCount-1]; }
+          T& back()       { SkASSERT(fCount > 0); return fArray[fCount-1]; }
 
     void reset() {
         if (fArray) {
diff --git a/src/core/SkPathBuilder.cpp b/src/core/SkPathBuilder.cpp
index 134f356..cc2208b 100644
--- a/src/core/SkPathBuilder.cpp
+++ b/src/core/SkPathBuilder.cpp
@@ -9,6 +9,9 @@
 #include "include/core/SkRRect.h"
 #include "include/private/SkPathRef.h"
 #include "include/private/SkSafe32.h"
+#include "src/core/SkGeometry.h"
+// need SkDVector
+#include "src/pathops/SkPathOpsPoint.h"
 
 SkPathBuilder::SkPathBuilder() {
     this->reset();
@@ -117,25 +120,24 @@
 
 SkPathBuilder& SkPathBuilder::rLineTo(SkPoint p1) {
     this->ensureMove();
-    SkPoint base = fPts[fPts.count() - 1];
-    return this->lineTo(base + p1);
+    return this->lineTo(fPts.back() + p1);
 }
 
 SkPathBuilder& SkPathBuilder::rQuadTo(SkPoint p1, SkPoint p2) {
     this->ensureMove();
-    SkPoint base = fPts[fPts.count() - 1];
+    SkPoint base = fPts.back();
     return this->quadTo(base + p1, base + p2);
 }
 
 SkPathBuilder& SkPathBuilder::rConicTo(SkPoint p1, SkPoint p2, SkScalar w) {
     this->ensureMove();
-    SkPoint base = fPts[fPts.count() - 1];
+    SkPoint base = fPts.back();
     return this->conicTo(base + p1, base + p2, w);
 }
 
 SkPathBuilder& SkPathBuilder::rCubicTo(SkPoint p1, SkPoint p2, SkPoint p3) {
     this->ensureMove();
-    SkPoint base = fPts[fPts.count() - 1];
+    SkPoint base = fPts.back();
     return this->cubicTo(base + p1, base + p2, base + p3);
 }
 
@@ -166,6 +168,346 @@
     return path;
 }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
+                              SkPoint* pt) {
+    if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
+        // Chrome uses this path to move into and out of ovals. If not
+        // treated as a special case the moves can distort the oval's
+        // bounding box (and break the circle special case).
+        pt->set(oval.fRight, oval.centerY());
+        return true;
+    } else if (0 == oval.width() && 0 == oval.height()) {
+        // Chrome will sometimes create 0 radius round rects. Having degenerate
+        // quad segments in the path prevents the path from being recognized as
+        // a rect.
+        // TODO: optimizing the case where only one of width or height is zero
+        // should also be considered. This case, however, doesn't seem to be
+        // as common as the single point case.
+        pt->set(oval.fRight, oval.fTop);
+        return true;
+    }
+    return false;
+}
+
+// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
+//
+static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
+                                   SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
+    SkScalar startRad = SkDegreesToRadians(startAngle),
+             stopRad  = SkDegreesToRadians(startAngle + sweepAngle);
+
+    startV->fY = SkScalarSinSnapToZero(startRad);
+    startV->fX = SkScalarCosSnapToZero(startRad);
+    stopV->fY = SkScalarSinSnapToZero(stopRad);
+    stopV->fX = SkScalarCosSnapToZero(stopRad);
+
+    /*  If the sweep angle is nearly (but less than) 360, then due to precision
+     loss in radians-conversion and/or sin/cos, we may end up with coincident
+     vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
+     of drawing a nearly complete circle (good).
+     e.g. canvas.drawArc(0, 359.99, ...)
+     -vs- canvas.drawArc(0, 359.9, ...)
+     We try to detect this edge case, and tweak the stop vector
+     */
+    if (*startV == *stopV) {
+        SkScalar sw = SkScalarAbs(sweepAngle);
+        if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
+            // make a guess at a tiny angle (in radians) to tweak by
+            SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
+            // not sure how much will be enough, so we use a loop
+            do {
+                stopRad -= deltaRad;
+                stopV->fY = SkScalarSinSnapToZero(stopRad);
+                stopV->fX = SkScalarCosSnapToZero(stopRad);
+            } while (*startV == *stopV);
+        }
+    }
+    *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
+}
+
+/**
+ *  If this returns 0, then the caller should just line-to the singlePt, else it should
+ *  ignore singlePt and append the specified number of conics.
+ */
+static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
+                            SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
+                            SkPoint* singlePt) {
+    SkMatrix    matrix;
+
+    matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
+    matrix.postTranslate(oval.centerX(), oval.centerY());
+
+    int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
+    if (0 == count) {
+        matrix.mapXY(stop.x(), stop.y(), singlePt);
+    }
+    return count;
+}
+
+static bool nearly_equal(const SkPoint& a, const SkPoint& b) {
+    return SkScalarNearlyEqual(a.fX, b.fX)
+        && SkScalarNearlyEqual(a.fY, b.fY);
+}
+
+SkPathBuilder& SkPathBuilder::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
+                                    bool forceMoveTo) {
+    if (oval.width() < 0 || oval.height() < 0) {
+        return *this;
+    }
+
+    if (fVerbs.count() == 0) {
+        forceMoveTo = true;
+    }
+
+    SkPoint lonePt;
+    if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
+        return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
+    }
+
+    SkVector startV, stopV;
+    SkRotationDirection dir;
+    angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
+
+    SkPoint singlePt;
+
+    // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
+    // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
+    // arcs from the same oval.
+    auto addPt = [forceMoveTo, this](const SkPoint& pt) {
+        if (forceMoveTo) {
+            this->moveTo(pt);
+        } else if (!nearly_equal(fPts.back(), pt)) {
+            this->lineTo(pt);
+        }
+    };
+
+    // At this point, we know that the arc is not a lone point, but startV == stopV
+    // indicates that the sweepAngle is too small such that angles_to_unit_vectors
+    // cannot handle it.
+    if (startV == stopV) {
+        SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
+        SkScalar radiusX = oval.width() / 2;
+        SkScalar radiusY = oval.height() / 2;
+        // We do not use SkScalar[Sin|Cos]SnapToZero here. When sin(startAngle) is 0 and sweepAngle
+        // is very small and radius is huge, the expected behavior here is to draw a line. But
+        // calling SkScalarSinSnapToZero will make sin(endAngle) be 0 which will then draw a dot.
+        singlePt.set(oval.centerX() + radiusX * SkScalarCos(endAngle),
+                     oval.centerY() + radiusY * SkScalarSin(endAngle));
+        addPt(singlePt);
+        return *this;
+    }
+
+    SkConic conics[SkConic::kMaxConicsForArc];
+    int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
+    if (count) {
+        this->incReserve(count * 2 + 1);
+        const SkPoint& pt = conics[0].fPts[0];
+        addPt(pt);
+        for (int i = 0; i < count; ++i) {
+            this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
+        }
+    } else {
+        addPt(singlePt);
+    }
+    return *this;
+}
+
+SkPathBuilder& SkPathBuilder::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
+    if (oval.isEmpty() || 0 == sweepAngle) {
+        return *this;
+    }
+
+    const SkScalar kFullCircleAngle = SkIntToScalar(360);
+
+    if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
+        // We can treat the arc as an oval if it begins at one of our legal starting positions.
+        // See SkPath::addOval() docs.
+        SkScalar startOver90 = startAngle / 90.f;
+        SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
+        SkScalar error = startOver90 - startOver90I;
+        if (SkScalarNearlyEqual(error, 0)) {
+            // Index 1 is at startAngle == 0.
+            SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
+            startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
+            return this->addOval(oval, sweepAngle > 0 ? SkPathDirection::kCW : SkPathDirection::kCCW,
+                                 (unsigned) startIndex);
+        }
+    }
+    return this->arcTo(oval, startAngle, sweepAngle, true);
+}
+
+SkPathBuilder& SkPathBuilder::arcTo(SkPoint p1, SkPoint p2, SkScalar radius) {
+    this->ensureMove();
+
+    if (radius == 0) {
+        return this->lineTo(p1);
+    }
+
+    // need to know our prev pt so we can construct tangent vectors
+    SkPoint start = fPts.back();
+
+    // need double precision for these calcs.
+    SkDVector befored, afterd;
+    befored.set({p1.fX - start.fX, p1.fY - start.fY}).normalize();
+    afterd.set({p2.fX - p1.fX, p2.fY - p1.fY}).normalize();
+    double cosh = befored.dot(afterd);
+    double sinh = befored.cross(afterd);
+
+    if (!befored.isFinite() || !afterd.isFinite() || SkScalarNearlyZero(SkDoubleToScalar(sinh))) {
+        return this->lineTo(p1);
+    }
+
+    // safe to convert back to floats now
+    SkVector before = befored.asSkVector();
+    SkVector after = afterd.asSkVector();
+    SkScalar dist = SkScalarAbs(SkDoubleToScalar(radius * (1 - cosh) / sinh));
+    SkScalar xx = p1.fX - dist * before.fX;
+    SkScalar yy = p1.fY - dist * before.fY;
+    after.setLength(dist);
+    this->lineTo(xx, yy);
+    SkScalar weight = SkScalarSqrt(SkDoubleToScalar(SK_ScalarHalf + cosh * 0.5));
+    return this->conicTo(p1, p1 + after, weight);
+}
+
+// This converts the SVG arc to conics.
+// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
+// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
+// See also SVG implementation notes:
+// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
+// Note that arcSweep bool value is flipped from the original implementation.
+SkPathBuilder& SkPathBuilder::arcTo(SkPoint rad, SkScalar angle, SkPathBuilder::ArcSize arcLarge,
+                                    SkPathDirection arcSweep, SkPoint endPt) {
+    this->ensureMove();
+
+    SkPoint srcPts[2] = { fPts.back(), endPt };
+
+    // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
+    // joining the endpoints.
+    // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
+    if (!rad.fX || !rad.fY) {
+        return this->lineTo(endPt);
+    }
+    // If the current point and target point for the arc are identical, it should be treated as a
+    // zero length path. This ensures continuity in animations.
+    if (srcPts[0] == srcPts[1]) {
+        return this->lineTo(endPt);
+    }
+    SkScalar rx = SkScalarAbs(rad.fX);
+    SkScalar ry = SkScalarAbs(rad.fY);
+    SkVector midPointDistance = srcPts[0] - srcPts[1];
+    midPointDistance *= 0.5f;
+
+    SkMatrix pointTransform;
+    pointTransform.setRotate(-angle);
+
+    SkPoint transformedMidPoint;
+    pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
+    SkScalar squareRx = rx * rx;
+    SkScalar squareRy = ry * ry;
+    SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
+    SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
+
+    // Check if the radii are big enough to draw the arc, scale radii if not.
+    // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
+    SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
+    if (radiiScale > 1) {
+        radiiScale = SkScalarSqrt(radiiScale);
+        rx *= radiiScale;
+        ry *= radiiScale;
+    }
+
+    pointTransform.setScale(1 / rx, 1 / ry);
+    pointTransform.preRotate(-angle);
+
+    SkPoint unitPts[2];
+    pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
+    SkVector delta = unitPts[1] - unitPts[0];
+
+    SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
+    SkScalar scaleFactorSquared = std::max(1 / d - 0.25f, 0.f);
+
+    SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
+    if ((arcSweep == SkPathDirection::kCCW) != SkToBool(arcLarge)) {  // flipped from the original implementation
+        scaleFactor = -scaleFactor;
+    }
+    delta.scale(scaleFactor);
+    SkPoint centerPoint = unitPts[0] + unitPts[1];
+    centerPoint *= 0.5f;
+    centerPoint.offset(-delta.fY, delta.fX);
+    unitPts[0] -= centerPoint;
+    unitPts[1] -= centerPoint;
+    SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
+    SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
+    SkScalar thetaArc = theta2 - theta1;
+    if (thetaArc < 0 && (arcSweep == SkPathDirection::kCW)) {  // arcSweep flipped from the original implementation
+        thetaArc += SK_ScalarPI * 2;
+    } else if (thetaArc > 0 && (arcSweep != SkPathDirection::kCW)) {  // arcSweep flipped from the original implementation
+        thetaArc -= SK_ScalarPI * 2;
+    }
+
+    // Very tiny angles cause our subsequent math to go wonky (skbug.com/9272)
+    // so we do a quick check here. The precise tolerance amount is just made up.
+    // PI/million happens to fix the bug in 9272, but a larger value is probably
+    // ok too.
+    if (SkScalarAbs(thetaArc) < (SK_ScalarPI / (1000 * 1000))) {
+        return this->lineTo(endPt);
+    }
+
+    pointTransform.setRotate(angle);
+    pointTransform.preScale(rx, ry);
+
+    // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
+    int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
+    SkScalar thetaWidth = thetaArc / segments;
+    SkScalar t = SkScalarTan(0.5f * thetaWidth);
+    if (!SkScalarIsFinite(t)) {
+        return *this;
+    }
+    SkScalar startTheta = theta1;
+    SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
+    auto scalar_is_integer = [](SkScalar scalar) -> bool {
+        return scalar == SkScalarFloorToScalar(scalar);
+    };
+    bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
+        scalar_is_integer(rx) && scalar_is_integer(ry) &&
+        scalar_is_integer(endPt.fX) && scalar_is_integer(endPt.fY);
+
+    for (int i = 0; i < segments; ++i) {
+        SkScalar endTheta    = startTheta + thetaWidth,
+                 sinEndTheta = SkScalarSinSnapToZero(endTheta),
+                 cosEndTheta = SkScalarCosSnapToZero(endTheta);
+
+        unitPts[1].set(cosEndTheta, sinEndTheta);
+        unitPts[1] += centerPoint;
+        unitPts[0] = unitPts[1];
+        unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
+        SkPoint mapped[2];
+        pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
+        /*
+        Computing the arc width introduces rounding errors that cause arcs to start
+        outside their marks. A round rect may lose convexity as a result. If the input
+        values are on integers, place the conic on integers as well.
+         */
+        if (expectIntegers) {
+            for (SkPoint& point : mapped) {
+                point.fX = SkScalarRoundToScalar(point.fX);
+                point.fY = SkScalarRoundToScalar(point.fY);
+            }
+        }
+        this->conicTo(mapped[0], mapped[1], w);
+        startTheta = endTheta;
+    }
+
+#ifndef SK_LEGACY_PATH_ARCTO_ENDPOINT
+    // The final point should match the input point (by definition); replace it to
+    // ensure that rounding errors in the above math don't cause any problems.
+    fPts.back() = endPt;
+#endif
+    return *this;
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////
 
 namespace {