Add verb measurement utils to SkContourMeasure

Change-Id: Ia0c5bfeecb462d079179516bc050004ceb8679ea
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/913916
Commit-Queue: Florin Malita <fmalita@google.com>
Reviewed-by: Daniel Dilan <danieldilan@google.com>
diff --git a/include/core/SkContourMeasure.h b/include/core/SkContourMeasure.h
index 29e33d8..eb4804b 100644
--- a/include/core/SkContourMeasure.h
+++ b/include/core/SkContourMeasure.h
@@ -11,6 +11,7 @@
 #include "include/core/SkPoint.h"
 #include "include/core/SkRefCnt.h"
 #include "include/core/SkScalar.h"
+#include "include/core/SkSpan.h"
 #include "include/private/base/SkAPI.h"
 #include "include/private/base/SkTDArray.h"
 
@@ -18,6 +19,7 @@
 
 class SkMatrix;
 class SkPath;
+enum class SkPathVerb;
 
 class SK_API SkContourMeasure : public SkRefCnt {
 public:
@@ -57,6 +59,47 @@
      */
     bool isClosed() const { return fIsClosed; }
 
+    /** Measurement data for individual verbs.
+     */
+    struct VerbMeasure {
+        SkScalar              fDistance; // Cumulative distance along the current contour.
+        SkPathVerb            fVerb;     // Verb type.
+        SkSpan<const SkPoint> fPts;      // Verb points.
+    };
+
+private:
+    struct Segment;
+
+public:
+    /** Utility for iterating over the current contour verbs:
+     *
+     *   for (const auto verb_measure : contour_measure) {
+     *     ...
+     *   }
+     */
+    class VerbIterator final {
+    public:
+        VerbIterator(const Segment* seg, SkSpan<const SkPoint> pts) : fSegment(seg), fPts(pts) {}
+
+        VerbMeasure operator*() const;
+
+        VerbIterator& operator++() { fSegment = Segment::Next(fSegment); return *this; }
+
+        bool operator==(const VerbIterator& other) { return fSegment == other.fSegment; }
+        bool operator!=(const VerbIterator& other) { return fSegment != other.fSegment; }
+
+    private:
+        const SkContourMeasure::Segment* fSegment;
+        const SkSpan<const SkPoint>      fPts;
+    };
+
+    VerbIterator begin() const {
+        return VerbIterator(fSegments.begin(), SkSpan(fPts.data(), fPts.size()));
+    }
+    VerbIterator end() const {
+        return VerbIterator(fSegments.end(), SkSpan(fPts.data(), fPts.size()));
+    }
+
 private:
     struct Segment {
         SkScalar    fDistance;  // total distance up to this point
diff --git a/src/core/SkContourMeasure.cpp b/src/core/SkContourMeasure.cpp
index 8c88119..41aae33 100644
--- a/src/core/SkContourMeasure.cpp
+++ b/src/core/SkContourMeasure.cpp
@@ -13,11 +13,14 @@
 #include "include/private/base/SkAssert.h"
 #include "include/private/base/SkDebug.h"
 #include "include/private/base/SkFloatingPoint.h"
+#include "include/private/base/SkTo.h"
 #include "src/core/SkGeometry.h"
 #include "src/core/SkPathMeasurePriv.h"
 #include "src/core/SkPathPriv.h"
 
 #include <algorithm>
+#include <array>
+#include <cstddef>
 #include <utility>
 
 #define kMaxTValue  0x3FFFFFFF
@@ -698,3 +701,32 @@
 
     return true;
 }
+
+SkContourMeasure::VerbMeasure SkContourMeasure::VerbIterator::operator*() const {
+    static constexpr size_t seg_pt_count[] = {
+        2, // kLine  (current_pt, 1 line pt)
+        3, // kQuad  (current_pt, 2 quad pts)
+        4, // kCubic (current_pt, 3 cubic pts)
+        4, // kConic (current_pt, {weight, 0}, 2 conic pts)
+    };
+    static constexpr SkPathVerb seg_verb[] = {
+        SkPathVerb::kLine,
+        SkPathVerb::kQuad,
+        SkPathVerb::kCubic,
+        SkPathVerb::kConic,
+    };
+    static_assert(std::size(seg_pt_count) == std::size(seg_verb));
+    static_assert(static_cast<size_t>(kLine_SegType)  < std::size(seg_pt_count));
+    static_assert(static_cast<size_t>(kQuad_SegType)  < std::size(seg_pt_count));
+    static_assert(static_cast<size_t>(kCubic_SegType) < std::size(seg_pt_count));
+    static_assert(static_cast<size_t>(kConic_SegType) < std::size(seg_pt_count));
+
+    SkASSERT(SkToSizeT(fSegment->fType) < std::size(seg_pt_count));
+    SkASSERT(fSegment->fPtIndex + seg_pt_count[fSegment->fType] <= fPts.size());
+
+    return {
+        fSegment->fDistance,
+        seg_verb[fSegment->fType],
+        SkSpan(fPts.data() + fSegment->fPtIndex, seg_pt_count[fSegment->fType]),
+    };
+}
diff --git a/tests/PathMeasureTest.cpp b/tests/PathMeasureTest.cpp
index 6d730f6..c7e2506 100644
--- a/tests/PathMeasureTest.cpp
+++ b/tests/PathMeasureTest.cpp
@@ -8,6 +8,7 @@
 #include "include/core/SkContourMeasure.h"
 #include "include/core/SkPath.h"
 #include "include/core/SkPathMeasure.h"
+#include "include/core/SkPathTypes.h"
 #include "include/core/SkPoint.h"
 #include "include/core/SkRefCnt.h"
 #include "include/core/SkScalar.h"
@@ -356,3 +357,93 @@
 
     test_shrink(reporter);
 }
+
+DEF_TEST(contour_measure_verbs, reporter) {
+    SkPath path;
+    path.moveTo(10, 10);
+    path.lineTo(10, 30);
+    path.lineTo(30, 30);
+    path.quadTo({40, 30}, {40, 40});
+    path.cubicTo({50, 40}, {50, 50}, {40, 50});
+    path.conicTo({50, 50}, {50, 60}, 1.2f);
+
+    SkContourMeasureIter measure(path, false);
+
+    sk_sp<SkContourMeasure> cmeasure = measure.next();
+    REPORTER_ASSERT(reporter, cmeasure);
+
+    SkContourMeasure::VerbIterator viter = cmeasure->begin();
+    {
+        REPORTER_ASSERT(reporter, viter != cmeasure->end());
+        const auto vmeasure = *viter;
+        REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kLine);
+        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 20));
+        REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 2);
+        REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(10, 10));
+        REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(10, 30));
+    }
+
+    ++viter;
+    {
+        REPORTER_ASSERT(reporter, viter != cmeasure->end());
+        const auto vmeasure = *viter;
+        REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kLine);
+        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 40));
+        REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 2);
+        REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(10, 30));
+        REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(30, 30));
+    }
+
+    ++viter;
+    {
+        REPORTER_ASSERT(reporter, viter != cmeasure->end());
+        const auto vmeasure = *viter;
+        REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kQuad);
+        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 44.419418f));
+        REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 3);
+        REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(30, 30));
+        REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(40, 30));
+        REPORTER_ASSERT(reporter, vmeasure.fPts[2] == SkPoint::Make(40, 40));
+    }
+
+    ++viter;
+    {
+        REPORTER_ASSERT(reporter, viter != cmeasure->end());
+        const auto vmeasure = *viter;
+        REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kCubic);
+        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 59.436790f));
+        REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 4);
+        REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(40, 40));
+        REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(50, 40));
+        REPORTER_ASSERT(reporter, vmeasure.fPts[2] == SkPoint::Make(50, 50));
+        REPORTER_ASSERT(reporter, vmeasure.fPts[3] == SkPoint::Make(40, 50));
+    }
+
+    ++viter;
+    {
+        REPORTER_ASSERT(reporter, viter != cmeasure->end());
+        const auto vmeasure = *viter;
+        REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kConic);
+        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 80.807449f));
+        REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 4);
+        REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(40, 50));
+        REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(1.2f, 0));
+        REPORTER_ASSERT(reporter, vmeasure.fPts[2] == SkPoint::Make(50, 50));
+        REPORTER_ASSERT(reporter, vmeasure.fPts[3] == SkPoint::Make(50, 60));
+    }
+
+    ++viter;
+    {
+        REPORTER_ASSERT(reporter, viter == cmeasure->end());
+    }
+
+    // Exercise the range iterator form.
+    float current_distance = 0;
+    size_t verb_count = 0;
+    for (const auto vmeasure : *cmeasure) {
+        REPORTER_ASSERT(reporter, vmeasure.fDistance > current_distance);
+        current_distance = vmeasure.fDistance;
+        verb_count++;
+    }
+    REPORTER_ASSERT(reporter, verb_count == 5);
+}