SkPath::getLastPt() to return optional

- clarifies to the caller that this can fail
- start updating internal call-sites

Change-Id: I1357dafcd473d1ba920e9044aa110a03a9a34a30
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1061316
Owners-Override: Kaylee Lubick <kjlubick@google.com>
Reviewed-by: Kaylee Lubick <kjlubick@google.com>
Reviewed-by: Daniel Dilan <danieldilan@google.com>
Commit-Queue: Mike Reed <mike@reedtribe.org>
diff --git a/docs/examples/Path_getLastPt.cpp b/docs/examples/Path_getLastPt.cpp
index c1de446..2b3b286 100644
--- a/docs/examples/Path_getLastPt.cpp
+++ b/docs/examples/Path_getLastPt.cpp
@@ -3,14 +3,14 @@
 #include "tools/fiddle/examples.h"
 REG_FIDDLE(Path_getLastPt, 256, 256, true, 0) {
 void draw(SkCanvas* canvas) {
-    SkPath path;
-    path.moveTo(100, 100);
-    path.quadTo(100, 20, 20, 100);
+    SkPath path = SkPathBuilder()
+                  .moveTo(100, 100)
+                  .quadTo(100, 20, 20, 100)
+                  .detach();
     SkMatrix matrix;
     matrix.setRotate(36, 100, 100);
-    path.transform(matrix);
-    SkPoint last;
-    path.getLastPt(&last);
+    path = path.makeTransform(matrix);
+    SkPoint last = path.getLastPt().value_or(SkPoint{0, 0});
     SkDebugf("last point: %g, %g\n", last.fX, last.fY);
 }
 }  // END FIDDLE
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index e9d3210..857d62a 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -611,15 +611,27 @@
     }
 #endif
 
-    /** Returns last point on SkPath in lastPt. Returns false if SkPoint array is empty,
-        storing (0, 0) if lastPt is not nullptr.
+    /** Return the last point, or {}
 
-        @param lastPt  storage for final SkPoint in SkPoint array; may be nullptr
-        @return        true if SkPoint array contains one or more SkPoint
+        @return The last if the path contains one or more SkPoint, else returns {}
 
         example: https://fiddle.skia.org/c/@Path_getLastPt
     */
-    bool getLastPt(SkPoint* lastPt) const;
+    std::optional<SkPoint> getLastPt() const;
+
+    // DEPRECATED
+    bool getLastPt(SkPoint* lastPt) const {
+        if (auto lp = this->getLastPt()) {
+            if (lastPt) {
+                *lastPt = *lp;
+            }
+            return true;
+        }
+        if (lastPt) {
+            *lastPt = {0, 0};
+        }
+        return false;
+    }
 
     /** \enum SkPath::SegmentMask
         SegmentMask constants correspond to each drawing Verb type in SkPath; for
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index a85c85c..17a68f5 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -507,20 +507,13 @@
     return size;
 }
 
-bool SkPath::getLastPt(SkPoint* lastPt) const {
+std::optional<SkPoint> SkPath::getLastPt() const {
     SkDEBUGCODE(this->validate();)
 
-    int count = fPathRef->countPoints();
-    if (count > 0) {
-        if (lastPt) {
-            *lastPt = fPathRef->atPoint(count - 1);
-        }
-        return true;
+    if (const int count = fPathRef->countPoints()) {
+        return fPathRef->atPoint(count - 1);
     }
-    if (lastPt) {
-        lastPt->set(0, 0);
-    }
-    return false;
+    return {};
 }
 
 void SkPath::setPt(int index, SkScalar x, SkScalar y) {
@@ -665,8 +658,7 @@
 
 SkPath& SkPath::rLineTo(SkScalar x, SkScalar y) {
     this->injectMoveToIfNeeded();  // This can change the result of this->getLastPt().
-    SkPoint pt;
-    this->getLastPt(&pt);
+    SkPoint pt = this->getLastPt().value_or(SkPoint{0, 0});
     return this->lineTo(pt.fX + x, pt.fY + y);
 }
 
@@ -685,8 +677,7 @@
 
 SkPath& SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
     this->injectMoveToIfNeeded();  // This can change the result of this->getLastPt().
-    SkPoint pt;
-    this->getLastPt(&pt);
+    SkPoint pt = this->getLastPt().value_or(SkPoint{0, 0});
     return this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
 }
 
@@ -718,8 +709,7 @@
 SkPath& SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
                          SkScalar w) {
     this->injectMoveToIfNeeded();  // This can change the result of this->getLastPt().
-    SkPoint pt;
-    this->getLastPt(&pt);
+    SkPoint pt = this->getLastPt().value_or(SkPoint{0, 0});
     return this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
 }
 
@@ -741,8 +731,7 @@
 SkPath& SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
                          SkScalar x3, SkScalar y3) {
     this->injectMoveToIfNeeded();  // This can change the result of this->getLastPt().
-    SkPoint pt;
-    this->getLastPt(&pt);
+    SkPoint pt = this->getLastPt().value_or(SkPoint{0, 0});
     return this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
                          pt.fX + x3, pt.fY + y3);
 }
@@ -1049,13 +1038,15 @@
     // 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) {
-        SkPoint lastPt;
         if (forceMoveTo) {
             this->moveTo(pt);
-        } else if (!this->getLastPt(&lastPt) ||
-                   !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
-                   !SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
-            this->lineTo(pt);
+        } else {
+            auto lastPt = this->getLastPt();
+            if (!lastPt ||
+                !SkScalarNearlyEqual(lastPt->fX, pt.fX) ||
+                !SkScalarNearlyEqual(lastPt->fY, pt.fY)) {
+                this->lineTo(pt);
+            }
         }
     };
 
@@ -1101,7 +1092,7 @@
                       SkPathDirection arcSweep, SkScalar x, SkScalar y) {
     this->injectMoveToIfNeeded();
     SkPoint srcPts[2];
-    this->getLastPt(&srcPts[0]);
+    srcPts[0] = *this->getLastPt();
     // 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
@@ -1227,8 +1218,7 @@
 
 SkPath& SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
                        SkPathDirection sweep, SkScalar dx, SkScalar dy) {
-    SkPoint currentPoint;
-    this->getLastPt(&currentPoint);
+    SkPoint currentPoint = this->getLastPt().value_or(SkPoint{0, 0});
     return this->arcTo(rx, ry, xAxisRotate, largeArc, sweep,
                        currentPoint.fX + dx, currentPoint.fY + dy);
 }
@@ -1269,8 +1259,7 @@
     }
 
     // need to know our prev pt so we can construct tangent vectors
-    SkPoint start;
-    this->getLastPt(&start);
+    SkPoint start = this->getLastPt().value_or(SkPoint{0, 0});
 
     // need double precision for these calcs.
     skvx::double2 befored = normalize(skvx::double2{x1 - start.fX, y1 - start.fY});
@@ -1355,9 +1344,9 @@
                 mapPtsProc(matrix, mappedPts, &pts[0], 1);
                 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
                     injectMoveToIfNeeded(); // In case last contour is closed
-                    SkPoint lastPt;
+                    auto lastPt = this->getLastPt();
                     // don't add lineTo if it is degenerate
-                    if (!this->getLastPt(&lastPt) || lastPt != mappedPts[0]) {
+                    if (!lastPt.has_value() || *lastPt != mappedPts[0]) {
                         this->lineTo(mappedPts[0]);
                     }
                 } else {
diff --git a/src/pathops/SkOpBuilder.cpp b/src/pathops/SkOpBuilder.cpp
index a923b15..0510134 100644
--- a/src/pathops/SkOpBuilder.cpp
+++ b/src/pathops/SkOpBuilder.cpp
@@ -41,13 +41,13 @@
 }
 
 void SkOpBuilder::ReversePath(SkPath* path) {
-    SkPath temp;
-    SkPoint lastPt;
-    SkAssertResult(path->getLastPt(&lastPt));
-    temp.moveTo(lastPt);
-    temp.reversePathTo(*path);
+    auto lastPt = path->getLastPt();
+    SkASSERT(lastPt.has_value());
+    SkPathBuilder temp;
+    temp.moveTo(*lastPt);
+    SkPathPriv::ReversePathTo(&temp, *path);
     temp.close();
-    *path = temp;
+    *path = temp.detach();
 }
 
 bool SkOpBuilder::FixWinding(SkPath* path) {
diff --git a/src/pathops/SkPathWriter.cpp b/src/pathops/SkPathWriter.cpp
index 4c6a550..0092cc4a 100644
--- a/src/pathops/SkPathWriter.cpp
+++ b/src/pathops/SkPathWriter.cpp
@@ -361,7 +361,9 @@
                 if (forward) {
                     next = contour.getPoint(0);
                 } else {
-                    SkAssertResult(contour.getLastPt(&next));
+                    auto lastPt = contour.getLastPt();
+                    SkASSERT(lastPt.has_value());
+                    next = *lastPt;
                 }
                 if (*prior != next) {
                     /* TODO: if there is a gap between open path written so far and path to come,
diff --git a/tools/viewer/VariableWidthStrokerSlide.cpp b/tools/viewer/VariableWidthStrokerSlide.cpp
index d92c50b..9bbe375 100644
--- a/tools/viewer/VariableWidthStrokerSlide.cpp
+++ b/tools/viewer/VariableWidthStrokerSlide.cpp
@@ -382,7 +382,7 @@
             break;
     }
 
-    fCurrVerb.getLastPt(&fPreviousPoint);
+    fPreviousPoint = fCurrVerb.getLastPt().value_or(SkPoint{0, 0});
     fMeas.setPath(&fCurrVerb, false);
 }
 
@@ -748,8 +748,7 @@
             fOuter.close();
         } else {
             // Inner last pt == first pt when appending in reverse
-            SkPoint innerLastPt;
-            fInner.getLastPt(&innerLastPt);
+            SkPoint innerLastPt = fInner.getLastPt().value_or(SkPoint{0, 0});
             fOuter.lineTo(innerLastPt);
         }
     };