Simpify RawPath::Iter

Implement Iter as an STL-style iterator that can be traversed very
efficiently using range-for:

  for (auto [verb, pts] : rawPath) { ... }

Manually inject the implicit moveTos in RawPath (namely, the implicit
moveTo(0) at the beginning of a path, if the client didn't add one, and
a similar moveTo after close).

The implicit moveTos fix a bug in RawPath::bounds where we were not
accounting for the implicit moveTo(0), as well as a bug in
ContourMeasureIter::tryNext where we expected the first contour in a
path to begin with PathVerb::move.

With the implicit moveTos guaranteed to be in the raw path, the iterator
can be simplified to just peek back one point and give a contiguous array
beginning with p0 for each verb. In addition to generally simplifying
things, one more perk of having all the points contiguous is that it
also enables fast SIMD loads.

IterateRawPath bench result:

  MacOS NEON: 8.71ms -> 5.02 (58%)
  Windows SSE: 5.63ms -> 4.20 (75%)

BuildRawPath bench result:

  MacOS NEON: 2.09ms -> 2.19 (105%)
  Windows SSE: 2.18ms -> 2.27 (104%)

Diffs=
fdeff54d1 Simpify RawPath::Iter (#4157)
diff --git a/.rive_head b/.rive_head
index 7c8701d..59d6fd3 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-0e69f11661b046008ddff212af1ccad53b731047
+fdeff54d123865883813a209c3047d2c5ec2c123
diff --git a/include/rive/math/contour_measure.hpp b/include/rive/math/contour_measure.hpp
index 0965645..1f51b5e 100644
--- a/include/rive/math/contour_measure.hpp
+++ b/include/rive/math/contour_measure.hpp
@@ -69,6 +69,7 @@
 class ContourMeasureIter {
     RawPath m_optionalCopy;
     RawPath::Iter m_iter;
+    RawPath::Iter m_end;
     const Vec2D* m_srcPoints;
     float m_invTolerance;
 
diff --git a/include/rive/math/raw_path.hpp b/include/rive/math/raw_path.hpp
index 41ac2b4..7514e93 100644
--- a/include/rive/math/raw_path.hpp
+++ b/include/rive/math/raw_path.hpp
@@ -22,9 +22,6 @@
 
 class RawPath {
 public:
-    std::vector<Vec2D> m_Points;
-    std::vector<PathVerb> m_Verbs;
-
     bool operator==(const RawPath& o) const;
     bool operator!=(const RawPath& o) const { return !(*this == o); }
 
@@ -78,49 +75,73 @@
 
     void addPath(const RawPath&, const Mat2D* = nullptr);
 
+    // Simple STL-style iterator. To traverse using range-for:
+    //
+    //   for (auto [verb, pts] : rawPath) { ... }
+    //
     class Iter {
-        const Vec2D* m_currPts;
-        const PathVerb* m_currVerb;
-        const PathVerb* m_stopVerb; // 1 past last verb
     public:
-        Iter() : m_currPts(nullptr), m_currVerb(nullptr), m_stopVerb(nullptr) {}
-        Iter(const RawPath& path) { this->reset(path); }
+        Iter() = default;
+        Iter(const PathVerb* verbs, const Vec2D* pts) : m_verbs(verbs), m_pts(pts) {}
 
-        void reset(const RawPath& path) {
-            m_currPts = path.m_Points.data();
-            m_currVerb = path.m_Verbs.data();
-            m_stopVerb = path.m_Verbs.data() + path.m_Verbs.size();
+        bool operator!=(const Iter& that) const { return m_verbs != that.m_verbs; }
+        bool operator==(const Iter& that) const { return m_verbs == that.m_verbs; }
+
+        PathVerb peekVerb() const { return *m_verbs; }
+
+        std::tuple<const PathVerb, const Vec2D* const> operator*() const {
+            PathVerb verb = peekVerb();
+            return {verb, m_pts + PtsBacksetForVerb(verb)};
         }
 
-        // returns true iff next() will return false
-        bool isDone() const { return m_currVerb >= m_stopVerb; }
+        Iter& operator++() { // ++iter
+            m_pts += PtsAdvanceAfterVerb(*m_verbs++);
+            return *this;
+        }
 
-        struct Rec {
-            const Vec2D* pts;
-            int count;
-            PathVerb verb;
+    private:
+        // How much should we advance pts after encountering this verb?
+        constexpr static int PtsAdvanceAfterVerb(PathVerb verb) {
+            switch (verb) {
+                case PathVerb::move: return 1;
+                case PathVerb::line: return 1;
+                case PathVerb::quad: return 2;
+                case PathVerb::cubic: return 3;
+                case PathVerb::close: return 0;
+            }
+            RIVE_UNREACHABLE;
+        }
 
-            operator bool() const { return pts != nullptr; }
-        };
-        Rec next();
+        // Where is p0 relative to our m_pts pointer? We find the start point of segments by
+        // peeking backwards from the current point, which works as long as there is always a
+        // PathVerb::move before any geometry. (injectImplicitMoveToIfNeeded() guarantees this
+        // to be the case.)
+        constexpr static int PtsBacksetForVerb(PathVerb verb) {
+            switch (verb) {
+                case PathVerb::move: return 0;
+                case PathVerb::line: return -1;
+                case PathVerb::quad: return -1;
+                case PathVerb::cubic: return -1;
+                case PathVerb::close: return -1;
+            }
+            RIVE_UNREACHABLE;
+        }
 
-        void backUp();
+        const PathVerb* m_verbs;
+        const Vec2D* m_pts;
     };
+    Iter begin() const { return {m_Verbs.data(), m_Points.data()}; }
+    Iter end() const { return {m_Verbs.data() + m_Verbs.size(), nullptr}; }
 
     template <typename Handler> RawPath morph(Handler proc) const {
         RawPath dst;
         // todo: dst.reserve(src.ptCount, src.verbCount);
-        RawPath::Iter iter(*this);
-        while (auto rec = iter.next()) {
-            Vec2D pts[3];
-            for (int i = 0; i < rec.count; ++i) {
-                pts[i] = proc(rec.pts[i]);
-            }
-            switch (rec.verb) {
-                case PathVerb::move: dst.move(pts[0]); break;
-                case PathVerb::line: dst.line(pts[0]); break;
-                case PathVerb::quad: dst.quad(pts[0], pts[1]); break;
-                case PathVerb::cubic: dst.cubic(pts[0], pts[1], pts[2]); break;
+        for (auto [verb, pts] : *this) {
+            switch (verb) {
+                case PathVerb::move: dst.move(proc(pts[0])); break;
+                case PathVerb::line: dst.line(proc(pts[1])); break;
+                case PathVerb::quad: dst.quad(proc(pts[1]), proc(pts[2])); break;
+                case PathVerb::cubic: dst.cubic(proc(pts[1]), proc(pts[2]), proc(pts[3])); break;
                 case PathVerb::close: dst.close(); break;
             }
         }
@@ -129,6 +150,15 @@
 
     // Utility for pouring a RawPath into a CommandPath
     void addTo(CommandPath*) const;
+
+private:
+    void injectImplicitMoveIfNeeded();
+
+    std::vector<Vec2D> m_Points;
+    std::vector<PathVerb> m_Verbs;
+    size_t m_lastMoveIdx;
+    // True of the path is nonempty and the most recent verb is not "close".
+    bool m_contourIsOpen = false;
 };
 
 } // namespace rive
diff --git a/skia/renderer/src/skia_factory.cpp b/skia/renderer/src/skia_factory.cpp
index 9e8a56b..a03ca7b 100644
--- a/skia/renderer/src/skia_factory.cpp
+++ b/skia/renderer/src/skia_factory.cpp
@@ -247,10 +247,10 @@
     const SkScalar* conicWeights = nullptr;
     const int conicWeightCount = 0;
     return std::make_unique<SkiaRenderPath>(
-        SkPath::Make(reinterpret_cast<const SkPoint*>(rawPath.m_Points.data()),
-                     rawPath.m_Points.size(),
-                     (uint8_t*)rawPath.m_Verbs.data(),
-                     rawPath.m_Verbs.size(),
+        SkPath::Make(reinterpret_cast<const SkPoint*>(rawPath.points().data()),
+                     rawPath.points().size(),
+                     (uint8_t*)rawPath.verbs().data(),
+                     rawPath.verbs().size(),
                      conicWeights,
                      conicWeightCount,
                      ToSkia::convert(fillRule),
diff --git a/src/math/contour_measure.cpp b/src/math/contour_measure.cpp
index b3421d4..5f4f887 100644
--- a/src/math/contour_measure.cpp
+++ b/src/math/contour_measure.cpp
@@ -316,7 +316,8 @@
 }
 
 void ContourMeasureIter::reset(const RawPath& path, float tolerance) {
-    m_iter.reset(path);
+    m_iter = path.begin();
+    m_end = path.end();
     m_srcPoints = path.points().data();
 
     constexpr float kMinTolerance = 1.0f / 16;
@@ -331,53 +332,53 @@
     std::vector<Vec2D> pts;
     float distance = 0;
     bool isClosed = false;
-    bool doneWithThisContour = false;
 
-    if (auto rec = m_iter.next()) {
-        assert(rec.verb == PathVerb::move);
-        pts.push_back(rec.pts[0]);
-
-        while (!doneWithThisContour && (rec = m_iter.next())) {
-            float prevDistance = distance;
-            const uint32_t ptIndex = castTo<uint32_t>(pts.size() - 1);
-            switch (rec.verb) {
-                case PathVerb::move:
-                    m_iter.backUp(); // so we can see this verb again the next time
-                    doneWithThisContour = true;
-                    break;
-                case PathVerb::line:
-                    distance += (rec.pts[0] - rec.pts[-1]).length();
-                    if (distance > prevDistance) {
-                        addSeg(segs, {distance, ptIndex, kMaxDot30, SegmentType::kLine}, true);
-                        pts.push_back(rec.pts[0]);
-                    }
-                    break;
-                case PathVerb::quad:
-                    distance = this->addQuadSegs(segs, &rec.pts[-1], ptIndex, distance);
-                    if (distance > prevDistance) {
-                        pts.push_back(rec.pts[0]);
-                        pts.push_back(rec.pts[1]);
-                    }
-                    break;
-                case PathVerb::cubic:
-                    distance = this->addCubicSegs(segs, &rec.pts[-1], ptIndex, distance);
-                    if (distance > prevDistance) {
-                        pts.push_back(rec.pts[0]);
-                        pts.push_back(rec.pts[1]);
-                        pts.push_back(rec.pts[2]);
-                    }
-                    break;
-                case PathVerb::close: {
-                    auto first = pts.front();
-                    distance += (first - pts.back()).length();
-                    if (distance > prevDistance) {
-                        addSeg(segs, {distance, ptIndex, kMaxDot30, SegmentType::kLine}, true);
-                        pts.push_back(first);
-                    }
-                    isClosed = true;
-                    doneWithThisContour = true;
-                } break;
+    for (; m_iter != m_end; ++m_iter) {
+        auto [verb, iterPts] = *m_iter;
+        if (verb == PathVerb::move) {
+            if (!pts.empty()) {
+                break; // We've alredy seen a move. Save this one for next time.
             }
+            pts.push_back(iterPts[0]);
+            continue;
+        }
+        assert(!pts.empty()); // PathVerb::move should have occurred first, and added a point.
+        assert(!isClosed);    // PathVerb::close is always followed by a move or nothing.
+        float prevDistance = distance;
+        const uint32_t ptIndex = castTo<uint32_t>(pts.size() - 1);
+        switch (verb) {
+            case PathVerb::line:
+                distance += (iterPts[1] - iterPts[0]).length();
+                if (distance > prevDistance) {
+                    addSeg(segs, {distance, ptIndex, kMaxDot30, SegmentType::kLine}, true);
+                    pts.push_back(iterPts[1]);
+                }
+                break;
+            case PathVerb::quad:
+                distance = this->addQuadSegs(segs, iterPts, ptIndex, distance);
+                if (distance > prevDistance) {
+                    pts.push_back(iterPts[1]);
+                    pts.push_back(iterPts[2]);
+                }
+                break;
+            case PathVerb::cubic:
+                distance = this->addCubicSegs(segs, iterPts, ptIndex, distance);
+                if (distance > prevDistance) {
+                    pts.push_back(iterPts[1]);
+                    pts.push_back(iterPts[2]);
+                    pts.push_back(iterPts[3]);
+                }
+                break;
+            case PathVerb::close: {
+                auto first = pts.front();
+                distance += (first - iterPts[0]).length();
+                if (distance > prevDistance) {
+                    addSeg(segs, {distance, ptIndex, kMaxDot30, SegmentType::kLine}, true);
+                    pts.push_back(first);
+                }
+                isClosed = true;
+            } break;
+            case PathVerb::move: RIVE_UNREACHABLE; // Handled above.
         }
     }
 
@@ -394,7 +395,7 @@
         if ((cm = this->tryNext())) {
             break;
         }
-        if (m_iter.isDone()) {
+        if (m_iter == m_end) {
             break;
         }
     }
diff --git a/src/math/raw_path.cpp b/src/math/raw_path.cpp
index 40e821f..1fa9381 100644
--- a/src/math/raw_path.cpp
+++ b/src/math/raw_path.cpp
@@ -37,23 +37,34 @@
     return bounds;
 }
 
+void RawPath::injectImplicitMoveIfNeeded() {
+    if (!m_contourIsOpen) {
+        move(m_Points.empty() ? Vec2D{0, 0} : m_Points[m_lastMoveIdx]);
+    }
+}
+
 void RawPath::move(Vec2D a) {
+    m_contourIsOpen = true;
+    m_lastMoveIdx = m_Points.size();
     m_Points.push_back(a);
     m_Verbs.push_back(PathVerb::move);
 }
 
 void RawPath::line(Vec2D a) {
+    injectImplicitMoveIfNeeded();
     m_Points.push_back(a);
     m_Verbs.push_back(PathVerb::line);
 }
 
 void RawPath::quad(Vec2D a, Vec2D b) {
+    injectImplicitMoveIfNeeded();
     m_Points.push_back(a);
     m_Points.push_back(b);
     m_Verbs.push_back(PathVerb::quad);
 }
 
 void RawPath::cubic(Vec2D a, Vec2D b, Vec2D c) {
+    injectImplicitMoveIfNeeded();
     m_Points.push_back(a);
     m_Points.push_back(b);
     m_Points.push_back(c);
@@ -61,9 +72,9 @@
 }
 
 void RawPath::close() {
-    const auto n = m_Verbs.size();
-    if (n > 0 && m_Verbs[n - 1] != PathVerb::close) {
+    if (m_contourIsOpen) {
         m_Verbs.push_back(PathVerb::close);
+        m_contourIsOpen = false;
     }
 }
 
@@ -203,26 +214,6 @@
     return ptCounts[index];
 }
 
-RawPath::Iter::Rec RawPath::Iter::next() {
-    // initialize with "false"
-    Rec rec = {nullptr, -1, (PathVerb)-1};
-
-    if (m_currVerb < m_stopVerb) {
-        rec.pts = m_currPts;
-        rec.verb = *m_currVerb++;
-        rec.count = path_verb_to_point_count(rec.verb);
-
-        m_currPts += rec.count;
-    }
-    return rec;
-}
-
-void RawPath::Iter::backUp() {
-    --m_currVerb;
-    const int n = path_verb_to_point_count(*m_currVerb);
-    m_currPts -= n;
-}
-
 void RawPath::swap(RawPath& rawPath) {
     m_Points.swap(rawPath.m_Points);
     m_Verbs.swap(rawPath.m_Verbs);
@@ -233,24 +224,25 @@
     m_Points.shrink_to_fit();
     m_Verbs.clear();
     m_Verbs.shrink_to_fit();
+    m_contourIsOpen = false;
 }
 
 void RawPath::rewind() {
     m_Points.clear();
     m_Verbs.clear();
+    m_contourIsOpen = false;
 }
 
 ///////////////////////////////////
 
 void RawPath::addTo(CommandPath* result) const {
-    RawPath::Iter iter(*this);
-    while (auto rec = iter.next()) {
-        switch (rec.verb) {
-            case PathVerb::move: result->move(rec.pts[0]); break;
-            case PathVerb::line: result->line(rec.pts[0]); break;
-            case PathVerb::quad: assert(false); break;
-            case PathVerb::cubic: result->cubic(rec.pts[0], rec.pts[1], rec.pts[2]); break;
+    for (auto [verb, pts] : *this) {
+        switch (verb) {
+            case PathVerb::move: result->move(pts[0]); break;
+            case PathVerb::line: result->line(pts[1]); break;
+            case PathVerb::cubic: result->cubic(pts[1], pts[2], pts[3]); break;
             case PathVerb::close: result->close(); break;
+            case PathVerb::quad: RIVE_UNREACHABLE;
         }
     }
 }
diff --git a/tess/include/rive/tess/segmented_contour.hpp b/tess/include/rive/tess/segmented_contour.hpp
index 47146a2..07b4657 100644
--- a/tess/include/rive/tess/segmented_contour.hpp
+++ b/tess/include/rive/tess/segmented_contour.hpp
@@ -12,9 +12,6 @@
 /// Utilty for converting a RawPath into a contour segments.
 class SegmentedContour {
 private:
-    Vec2D m_pen;
-    Vec2D m_penDown;
-    bool m_isPenDown = false;
     std::vector<Vec2D> m_contourPoints;
 
     AABB m_bounds;
@@ -22,8 +19,6 @@
     float m_thresholdSquared;
 
     void addVertex(Vec2D vertex);
-    void penDown();
-    void close();
     void segmentCubic(const Vec2D& from,
                       const Vec2D& fromOut,
                       const Vec2D& toIn,
@@ -44,4 +39,4 @@
     void contour(const RawPath& rawPath, const Mat2D& transform);
 };
 } // namespace rive
-#endif
\ No newline at end of file
+#endif
diff --git a/tess/src/segmented_contour.cpp b/tess/src/segmented_contour.cpp
index 4906232..c47dc53 100644
--- a/tess/src/segmented_contour.cpp
+++ b/tess/src/segmented_contour.cpp
@@ -20,23 +20,6 @@
     AABB::expandTo(m_bounds, vertex);
 }
 
-void SegmentedContour::penDown() {
-    if (m_isPenDown) {
-        return;
-    }
-    m_isPenDown = true;
-    m_penDown = m_pen;
-    addVertex(m_penDown);
-}
-
-void SegmentedContour::close() {
-    if (!m_isPenDown) {
-        return;
-    }
-    m_pen = m_penDown;
-    m_isPenDown = false;
-}
-
 const std::size_t SegmentedContour::contourSize() const { return m_contourPoints.size(); }
 
 const Span<const Vec2D> SegmentedContour::contourPoints(uint32_t endOffset) const {
@@ -70,36 +53,25 @@
 void SegmentedContour::contour(const RawPath& rawPath, const Mat2D& transform) {
     m_contourPoints.clear();
 
-    RawPath::Iter iter(rawPath);
     // Possible perf consideration: could add second path that doesn't transform
     // if transform is the identity.
-    while (auto rec = iter.next()) {
-        switch (rec.verb) {
-            case PathVerb::move:
-                m_isPenDown = false;
-                m_pen = transform * rec.pts[0];
-                break;
-            case PathVerb::line:
-                penDown();
-                m_pen = transform * rec.pts[0];
-                addVertex(m_pen);
-                break;
+    for (const auto [verb, pts] : rawPath) {
+        switch (verb) {
+            case PathVerb::move: addVertex(transform * pts[0]); break;
+            case PathVerb::line: addVertex(transform * pts[1]); break;
             case PathVerb::cubic:
-                penDown();
-                segmentCubic(m_pen,
-                             transform * rec.pts[0],
-                             transform * rec.pts[1],
-                             transform * rec.pts[2],
+                segmentCubic(transform * pts[0],
+                             transform * pts[1],
+                             transform * pts[2],
+                             transform * pts[3],
                              0.0f,
                              1.0f);
-                m_pen = transform * rec.pts[2];
                 break;
-            case PathVerb::close: close(); break;
+            case PathVerb::close: break;
             case PathVerb::quad:
                 // TODO: not currently used by render paths, however might be
                 // necessary for fonts.
                 break;
         }
     }
-    close();
 }
diff --git a/test/raw_path_test.cpp b/test/raw_path_test.cpp
index 7b77337..460542b 100644
--- a/test/raw_path_test.cpp
+++ b/test/raw_path_test.cpp
@@ -8,6 +8,7 @@
 #include <catch.hpp>
 #include <cstdio>
 #include <limits>
+#include <tuple>
 
 using namespace rive;
 
@@ -78,58 +79,23 @@
 
 //////////////////////////////////////////////////////////////////////////
 
-static bool is_move(const RawPath::Iter::Rec& rec) {
-    if (rec.verb == PathVerb::move) {
-        REQUIRE(rec.count == 1);
-        return true;
+static void check_iter(RawPath::Iter& iter,
+                       const RawPath::Iter& end,
+                       PathVerb expectedVerb,
+                       std::vector<Vec2D> expectedPts) {
+    REQUIRE(iter != end);
+    auto [verb, pts] = *iter;
+    REQUIRE(verb == expectedVerb);
+    for (size_t i = 0; i < expectedPts.size(); ++i) {
+        CHECK(pts[i] == expectedPts[i]);
     }
-    return false;
+    ++iter;
 }
 
-static bool is_line(const RawPath::Iter::Rec& rec) {
-    if (rec.verb == PathVerb::line) {
-        REQUIRE(rec.count == 1);
-        return true;
-    }
-    return false;
-}
-
-static bool is_quad(const RawPath::Iter::Rec& rec) {
-    if (rec.verb == PathVerb::quad) {
-        REQUIRE(rec.count == 2);
-        return true;
-    }
-    return false;
-}
-
-static bool is_cubic(const RawPath::Iter::Rec& rec) {
-    if (rec.verb == PathVerb::cubic) {
-        REQUIRE(rec.count == 3);
-        return true;
-    }
-    return false;
-}
-
-static bool is_close(const RawPath::Iter::Rec& rec) {
-    if (rec.verb == PathVerb::close) {
-        REQUIRE(rec.count == 0);
-        return true;
-    }
-    return false;
-}
-
-// clang-format off
-static inline bool eq(Vec2D p, float x, float y) {
-    return p.x == x && p.y == y;
-}
-// clang-format on
-
 TEST_CASE("rawpath-iter", "[rawpath]") {
     {
         RawPath rp;
-        RawPath::Iter iter(rp);
-        REQUIRE(iter.next() == false);
-        REQUIRE(iter.next() == false); // should be safe to call again
+        REQUIRE(rp.begin() == rp.end());
     }
     {
         RawPath rp;
@@ -138,105 +104,58 @@
         rp.quadTo(5, 6, 7, 8);
         rp.cubicTo(9, 10, 11, 12, 13, 14);
         rp.close();
-        RawPath::Iter iter(rp);
-        auto rec = iter.next();
-        REQUIRE((rec && is_move(rec) && eq(rec.pts[0], 1, 2)));
-        rec = iter.next();
-        REQUIRE((rec && is_line(rec) && eq(rec.pts[0], 3, 4)));
-        rec = iter.next();
-        REQUIRE((rec && is_quad(rec) && eq(rec.pts[0], 5, 6) && eq(rec.pts[1], 7, 8)));
-        rec = iter.next();
-        REQUIRE((rec && is_cubic(rec) && eq(rec.pts[0], 9, 10) && eq(rec.pts[1], 11, 12) &&
-                 eq(rec.pts[2], 13, 14)));
-        rec = iter.next();
-        REQUIRE((rec && is_close(rec)));
-        rec = iter.next();
-        REQUIRE(rec == false);
-        REQUIRE(iter.next() == false); // should be safe to call again
+        auto [iter, end] = std::make_tuple(rp.begin(), rp.end());
+        check_iter(iter, end, PathVerb::move, {{1, 2}});
+        check_iter(iter, end, PathVerb::line, {{1, 2}, {3, 4}});
+        check_iter(iter, end, PathVerb::quad, {{3, 4}, {5, 6}, {7, 8}});
+        check_iter(iter, end, PathVerb::cubic, {{7, 8}, {9, 10}, {11, 12}, {13, 14}});
+        check_iter(iter, end, PathVerb::close, {});
+        REQUIRE(iter == end);
+
+        // Moves are never discarded.
+        rp.reset();
+        rp.moveTo(1, 2);
+        rp.moveTo(3, 4);
+        rp.moveTo(5, 6);
+        rp.close();
+        std::tie(iter, end) = std::make_tuple(rp.begin(), rp.end());
+        check_iter(iter, end, PathVerb::move, {{1, 2}});
+        check_iter(iter, end, PathVerb::move, {{3, 4}});
+        check_iter(iter, end, PathVerb::move, {{5, 6}});
+        check_iter(iter, end, PathVerb::close, {});
+        REQUIRE(iter == end);
+
+        // lineTo, quadTo, and cubicTo can inject implicit moveTos.
+        rp.rewind();
+        rp.close();                   // discarded
+        rp.close();                   // discarded
+        rp.close();                   // discarded
+        rp.close();                   // discarded
+        rp.lineTo(1, 2);              // injects moveTo(0, 0)
+        rp.close();                   // kept
+        rp.close();                   // discarded
+        rp.cubicTo(3, 4, 5, 6, 7, 8); // injects moveTo(0, 0)
+        rp.moveTo(9, 10);
+        rp.moveTo(11, 12);
+        rp.quadTo(13, 14, 15, 16);
+        rp.close();        // kept
+        rp.lineTo(17, 18); // injects moveTo(11, 12)
+        std::tie(iter, end) = std::make_tuple(rp.begin(), rp.end());
+        check_iter(iter, end, PathVerb::move, {{0, 0}});
+        check_iter(iter, end, PathVerb::line, {{0, 0}, {1, 2}});
+        check_iter(iter, end, PathVerb::close, {});
+        check_iter(iter, end, PathVerb::move, {{0, 0}});
+        check_iter(iter, end, PathVerb::cubic, {{0, 0}, {3, 4}, {5, 6}, {7, 8}});
+        check_iter(iter, end, PathVerb::move, {{9, 10}});
+        check_iter(iter, end, PathVerb::move, {{11, 12}});
+        check_iter(iter, end, PathVerb::quad, {{11, 12}, {13, 14}, {15, 16}});
+        check_iter(iter, end, PathVerb::close, {});
+        check_iter(iter, end, PathVerb::move, {{11, 12}});
+        check_iter(iter, end, PathVerb::line, {{11, 12}, {17, 18}});
+        REQUIRE(iter == end);
     }
 }
 
-TEST_CASE("isDone", "[rawpath::iter]") {
-    RawPath rp;
-    rp.moveTo(1, 2);
-    rp.lineTo(3, 4);
-    RawPath::Iter iter(rp);
-
-    REQUIRE(!iter.isDone()); // moveTo
-    REQUIRE(iter.next());
-
-    REQUIRE(!iter.isDone()); // lineTo
-    REQUIRE(iter.next());
-
-    REQUIRE(iter.isDone()); // now we're done
-    REQUIRE(!iter.next());
-    REQUIRE(iter.isDone()); // ensure we 'still' think we're done
-}
-
-TEST_CASE("reset", "[rawpath]") {
-    RawPath path;
-    path.moveTo(1, 2);
-    path.lineTo(3, 4);
-    RawPath::Iter iter(path);
-    auto rec = iter.next();
-    REQUIRE((rec && is_move(rec) && eq(rec.pts[0], 1, 2)));
-    rec = iter.next();
-    REQUIRE((rec && is_line(rec) && eq(rec.pts[0], 3, 4)));
-    REQUIRE(!iter.next());
-
-    // now change the path (not required for the test per-se)
-    path = RawPath();
-    path.moveTo(0, 0);
-    path.close();
-
-    iter.reset(path);
-    rec = iter.next();
-    REQUIRE((rec && is_move(rec) && eq(rec.pts[0], 0, 0)));
-    rec = iter.next();
-    REQUIRE((rec && is_close(rec)));
-    REQUIRE(!iter.next());
-}
-
-TEST_CASE("backup", "[rawpath]") {
-    RawPath rp;
-    rp.moveTo(1, 2);
-    rp.lineTo(3, 4);
-    rp.close();
-    RawPath::Iter iter(rp);
-
-    auto rec = iter.next();
-    REQUIRE((rec && is_move(rec) && eq(rec.pts[0], 1, 2)));
-    const Vec2D* move_pts = rec.pts;
-
-    rec = iter.next();
-    REQUIRE((rec && is_line(rec) && eq(rec.pts[0], 3, 4)));
-    const Vec2D* line_pts = rec.pts;
-
-    rec = iter.next();
-    REQUIRE((rec && is_close(rec)));
-
-    rec = iter.next();
-    REQUIRE(!rec);
-
-    // Now try backing up
-
-    iter.backUp(); // go back to 'close'
-    rec = iter.next();
-    REQUIRE((rec && is_close(rec)));
-
-    iter.backUp(); // go back to 'close'
-    iter.backUp(); // go back to 'line'
-    rec = iter.next();
-    REQUIRE((rec && is_line(rec) && eq(rec.pts[0], 3, 4)));
-    REQUIRE(rec.pts == line_pts);
-
-    iter.backUp(); // go back to 'line'
-    iter.backUp(); // go back to 'move'
-    rec = iter.next();
-    REQUIRE((rec && is_move(rec) && eq(rec.pts[0], 1, 2)));
-    REQUIRE(rec.pts == move_pts);
-}
-
 TEST_CASE("addPath", "[rawpath]") {
     using PathMaker = void (*)(RawPath * sink);
 
@@ -318,9 +237,24 @@
         for (int i = 0; i < numVerbs; ++i) {
             switch (rand() % 5) {
                 case 0: path.move(randPt()); break;
-                case 1: path.line(randPt()); break;
-                case 2: path.quad(randPt(), randPt()); break;
-                case 3: path.cubic(randPt(), randPt(), randPt()); break;
+                case 1:
+                    if (path.empty()) { // Account for the implicit moveTo(0).
+                        bounds = {};
+                    }
+                    path.line(randPt());
+                    break;
+                case 2:
+                    if (path.empty()) { // Account for the implicit moveTo(0).
+                        bounds = {};
+                    }
+                    path.quad(randPt(), randPt());
+                    break;
+                case 3:
+                    if (path.empty()) { // Account for the implicit moveTo(0).
+                        bounds = {};
+                    }
+                    path.cubic(randPt(), randPt(), randPt());
+                    break;
                 case 4: path.close(); break;
             }
         }