Add the ability to iterate through a path without modification. This change is
required by WebKit SVG in order to correctly draw markers and endcaps.

BUG=415
TEST=TestPath in the unit tests
Review URL: http://codereview.appspot.com/5505097

git-svn-id: http://skia.googlecode.com/svn/trunk@2962 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index 407fcf4..270fa68 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -9,6 +9,7 @@
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkParse.h"
+#include "SkRandom.h"
 #include "SkReader32.h"
 #include "SkSize.h"
 #include "SkWriter32.h"
@@ -523,21 +524,28 @@
 }
 
 static void test_zero_length_paths(skiatest::Reporter* reporter) {
-    SkPath p;
-    SkRect bounds;
+    SkPath  p;
+    SkPoint pt;
+    SkRect  bounds;
 
     // Lone moveTo case
     p.moveTo(SK_Scalar1, SK_Scalar1);
-    bounds.set(0, 0, 0, 0);
     REPORTER_ASSERT(reporter, !p.isEmpty());
     REPORTER_ASSERT(reporter, 1 == p.countPoints());
+    p.getLastPt(&pt);
+    REPORTER_ASSERT(reporter, pt.fX == SK_Scalar1);
+    REPORTER_ASSERT(reporter, pt.fY == SK_Scalar1);
+    bounds.set(0, 0, 0, 0);
     REPORTER_ASSERT(reporter, bounds == p.getBounds());
 
     // MoveTo-MoveTo case
     p.moveTo(SK_Scalar1*2, SK_Scalar1);
-    bounds.set(SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1);
     REPORTER_ASSERT(reporter, !p.isEmpty());
     REPORTER_ASSERT(reporter, 2 == p.countPoints());
+    p.getLastPt(&pt);
+    REPORTER_ASSERT(reporter, pt.fX == SK_Scalar1*2);
+    REPORTER_ASSERT(reporter, pt.fY == SK_Scalar1);
+    bounds.set(SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1);
     REPORTER_ASSERT(reporter, bounds == p.getBounds()); 
 
     // moveTo-close case
@@ -659,6 +667,302 @@
 
 #define kCurveSegmentMask   (SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask)
 
+static void test_segment_masks(skiatest::Reporter* reporter) {
+    SkPath p;
+    p.moveTo(0, 0);
+    p.quadTo(100, 100, 200, 200);
+    REPORTER_ASSERT(reporter, SkPath::kQuad_SegmentMask == p.getSegmentMasks());
+    REPORTER_ASSERT(reporter, !p.isEmpty());
+    p.cubicTo(100, 100, 200, 200, 300, 300);
+    REPORTER_ASSERT(reporter, kCurveSegmentMask == p.getSegmentMasks());
+    REPORTER_ASSERT(reporter, !p.isEmpty());
+    p.reset();
+    p.moveTo(0, 0);
+    p.cubicTo(100, 100, 200, 200, 300, 300);
+    REPORTER_ASSERT(reporter, SkPath::kCubic_SegmentMask == p.getSegmentMasks());
+    REPORTER_ASSERT(reporter, !p.isEmpty());
+}
+
+static void test_iter(skiatest::Reporter* reporter) {
+    SkPath p;
+    SkPoint pts[4];
+
+    // Test an iterator with no path
+    SkPath::Iter noPathIter;
+    REPORTER_ASSERT(reporter, noPathIter.next(pts) == SkPath::kDone_Verb);
+    // Test that setting an empty path works
+    noPathIter.setPath(p, false);
+    REPORTER_ASSERT(reporter, noPathIter.next(pts) == SkPath::kDone_Verb);
+    // Test that close path makes no difference for an empty path
+    noPathIter.setPath(p, true);
+    REPORTER_ASSERT(reporter, noPathIter.next(pts) == SkPath::kDone_Verb);
+    
+    // Test an iterator with an initial empty path
+    SkPath::Iter iter(p, false);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
+
+    // Test that close path makes no difference
+    SkPath::Iter forceCloseIter(p, true);
+    REPORTER_ASSERT(reporter, forceCloseIter.next(pts) == SkPath::kDone_Verb);
+
+    // Test that a move-only path produces nothing when iterated.
+    p.moveTo(SK_Scalar1, 0);
+    iter.setPath(p, false);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
+
+    // No matter how many moves we add, we should still get nothing back.
+    p.moveTo(SK_Scalar1*2, 0);
+    p.moveTo(SK_Scalar1*3, 0);
+    p.moveTo(SK_Scalar1*4, 0);
+    p.moveTo(SK_Scalar1*5, 0);
+    iter.setPath(p, false);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
+
+    // Nor should force closing
+    forceCloseIter.setPath(p, true);
+    REPORTER_ASSERT(reporter, forceCloseIter.next(pts) == SkPath::kDone_Verb);
+
+    // Initial closes should be ignored
+    p.reset();
+    p.close();
+    iter.setPath(p, false);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
+    // Even if force closed
+    forceCloseIter.setPath(p, true);
+    REPORTER_ASSERT(reporter, forceCloseIter.next(pts) == SkPath::kDone_Verb);
+
+    // Move/close sequences should also be ignored
+    p.reset();
+    p.close();
+    p.moveTo(SK_Scalar1, 0);
+    p.close();
+    p.close();
+    p.moveTo(SK_Scalar1*2, 0);
+    p.close();
+    p.moveTo(SK_Scalar1*3, 0);
+    p.moveTo(SK_Scalar1*4, 0);
+    p.close();
+    iter.setPath(p, false);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
+    // Even if force closed
+    forceCloseIter.setPath(p, true);
+    REPORTER_ASSERT(reporter, forceCloseIter.next(pts) == SkPath::kDone_Verb);
+
+    // The GM degeneratesegments.cpp test is more extensive
+}
+
+static void test_raw_iter(skiatest::Reporter* reporter) {
+    SkPath p;
+    SkPoint pts[4];
+
+    // Test an iterator with no path
+    SkPath::RawIter noPathIter;
+    REPORTER_ASSERT(reporter, noPathIter.next(pts) == SkPath::kDone_Verb);
+    // Test that setting an empty path works
+    noPathIter.setPath(p);
+    REPORTER_ASSERT(reporter, noPathIter.next(pts) == SkPath::kDone_Verb);
+    
+    // Test an iterator with an initial empty path
+    SkPath::RawIter iter(p);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
+
+    // Test that a move-only path returns the move.
+    p.moveTo(SK_Scalar1, 0);
+    iter.setPath(p);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1);
+    REPORTER_ASSERT(reporter, pts[0].fY == 0);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
+
+    // No matter how many moves we add, we should get them all back
+    p.moveTo(SK_Scalar1*2, SK_Scalar1);
+    p.moveTo(SK_Scalar1*3, SK_Scalar1*2);
+    iter.setPath(p);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1);
+    REPORTER_ASSERT(reporter, pts[0].fY == 0);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*2);
+    REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*3);
+    REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1*2);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
+
+    // Initial close is never ever stored
+    p.reset();
+    p.close();
+    iter.setPath(p);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
+
+    // Move/close sequences
+    p.reset();
+    p.close(); // Not stored, no purpose
+    p.moveTo(SK_Scalar1, 0);
+    p.close();
+    p.close(); // Not stored, no purpose
+    p.moveTo(SK_Scalar1*2, SK_Scalar1);
+    p.close();
+    p.moveTo(SK_Scalar1*3, SK_Scalar1*2);
+    p.moveTo(SK_Scalar1*4, SK_Scalar1*3);
+    p.close();
+    iter.setPath(p);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1);
+    REPORTER_ASSERT(reporter, pts[0].fY == 0);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kClose_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1);
+    REPORTER_ASSERT(reporter, pts[0].fY == 0);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*2);
+    REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kClose_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*2);
+    REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*3);
+    REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1*2);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kMove_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*4);
+    REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1*3);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kClose_Verb);
+    REPORTER_ASSERT(reporter, pts[0].fX == SK_Scalar1*4);
+    REPORTER_ASSERT(reporter, pts[0].fY == SK_Scalar1*3);
+    REPORTER_ASSERT(reporter, iter.next(pts) == SkPath::kDone_Verb);
+
+    // Generate random paths and verify
+    SkPoint randomPts[25];
+    for (int i = 0; i < 5; ++i) {
+        for (int j = 0; j < 5; ++j) {
+            randomPts[i*5+j].set(SK_Scalar1*i, SK_Scalar1*j);
+        }
+    }
+
+    // Max of 10 segments, max 3 points per segment
+    SkRandom rand(9876543);
+    SkPoint          expectedPts[31]; // May have leading moveTo
+    SkPath::Verb     expectedVerbs[11]; // May have leading moveTo
+    SkPath::Verb     nextVerb;
+    for (int i = 0; i < 500; ++i) {
+        p.reset();
+        bool lastWasClose = true;
+        bool haveMoveTo = false;
+        int numPoints = 0;
+        int numVerbs = (rand.nextU() >> 16) % 10;
+        int numIterVerbs = 0;
+        for (int j = 0; j < numVerbs; ++j) {
+            do {
+                nextVerb = static_cast<SkPath::Verb>((rand.nextU() >> 16) % SkPath::kDone_Verb);
+            } while (lastWasClose && nextVerb == SkPath::kClose_Verb);
+            int numRequiredPts;
+            switch (nextVerb) {
+                case SkPath::kMove_Verb:
+                    expectedPts[numPoints] = randomPts[(rand.nextU() >> 16) % 25];
+                    p.moveTo(expectedPts[numPoints]);
+                    numPoints += 1;
+                    lastWasClose = false;
+                    haveMoveTo = true;
+                    break;
+                case SkPath::kLine_Verb:
+                    if (!haveMoveTo) {
+                        expectedPts[numPoints++].set(0, 0);
+                        expectedVerbs[numIterVerbs++] = SkPath::kMove_Verb;
+                        haveMoveTo = true;
+                    }
+                    expectedPts[numPoints] = randomPts[(rand.nextU() >> 16) % 25];
+                    p.lineTo(expectedPts[numPoints]);
+                    numPoints += 1;
+                    lastWasClose = false;
+                    break;
+                case SkPath::kQuad_Verb:
+                    if (!haveMoveTo) {
+                        expectedPts[numPoints++].set(0, 0);
+                        expectedVerbs[numIterVerbs++] = SkPath::kMove_Verb;
+                        haveMoveTo = true;
+                    }
+                    expectedPts[numPoints] = randomPts[(rand.nextU() >> 16) % 25];
+                    expectedPts[numPoints + 1] = randomPts[(rand.nextU() >> 16) % 25];
+                    p.quadTo(expectedPts[numPoints], expectedPts[numPoints + 1]);
+                    numPoints += 2;
+                    lastWasClose = false;
+                    break;
+                case SkPath::kCubic_Verb:
+                    if (!haveMoveTo) {
+                        expectedPts[numPoints++].set(0, 0);
+                        expectedVerbs[numIterVerbs++] = SkPath::kMove_Verb;
+                        haveMoveTo = true;
+                    }
+                    expectedPts[numPoints] = randomPts[(rand.nextU() >> 16) % 25];
+                    expectedPts[numPoints + 1] = randomPts[(rand.nextU() >> 16) % 25];
+                    expectedPts[numPoints + 2] = randomPts[(rand.nextU() >> 16) % 25];
+                    p.cubicTo(expectedPts[numPoints], expectedPts[numPoints + 1],
+                              expectedPts[numPoints + 2]);
+                    numPoints += 3;
+                    lastWasClose = false;
+                    break;
+                case SkPath::kClose_Verb:
+                    p.close();
+                    lastWasClose = true;
+                    break;
+                default:;
+            }
+            expectedVerbs[numIterVerbs++] = nextVerb;
+        }
+        
+        iter.setPath(p);
+        numVerbs = numIterVerbs;
+        numIterVerbs = 0;
+        int numIterPts = 0;
+        SkPoint lastMoveTo;
+        SkPoint lastPt;
+        lastMoveTo.set(0, 0);
+        lastPt.set(0, 0);
+        while ((nextVerb = iter.next(pts)) != SkPath::kDone_Verb) {
+            REPORTER_ASSERT(reporter, nextVerb == expectedVerbs[numIterVerbs]);
+            numIterVerbs++;
+            switch (nextVerb) {
+                case SkPath::kMove_Verb:
+                    REPORTER_ASSERT(reporter, numIterPts < numPoints);
+                    REPORTER_ASSERT(reporter, pts[0] == expectedPts[numIterPts]);
+                    lastPt = lastMoveTo = pts[0];
+                    numIterPts += 1;
+                    break;
+                case SkPath::kLine_Verb:
+                    REPORTER_ASSERT(reporter, numIterPts < numPoints + 1);
+                    REPORTER_ASSERT(reporter, pts[0] == lastPt);
+                    REPORTER_ASSERT(reporter, pts[1] == expectedPts[numIterPts]);
+                    lastPt = pts[1];
+                    numIterPts += 1;
+                    break;
+                case SkPath::kQuad_Verb:
+                    REPORTER_ASSERT(reporter, numIterPts < numPoints + 2);
+                    REPORTER_ASSERT(reporter, pts[0] == lastPt);
+                    REPORTER_ASSERT(reporter, pts[1] == expectedPts[numIterPts]);
+                    REPORTER_ASSERT(reporter, pts[2] == expectedPts[numIterPts + 1]);
+                    lastPt = pts[2];
+                    numIterPts += 2;
+                    break;
+                case SkPath::kCubic_Verb:
+                    REPORTER_ASSERT(reporter, numIterPts < numPoints + 3);
+                    REPORTER_ASSERT(reporter, pts[0] == lastPt);
+                    REPORTER_ASSERT(reporter, pts[1] == expectedPts[numIterPts]);
+                    REPORTER_ASSERT(reporter, pts[2] == expectedPts[numIterPts + 1]);
+                    REPORTER_ASSERT(reporter, pts[3] == expectedPts[numIterPts + 2]);
+                    lastPt = pts[3];
+                    numIterPts += 3;
+                    break;
+                case SkPath::kClose_Verb:
+                    REPORTER_ASSERT(reporter, pts[0] == lastMoveTo);
+                    lastPt = lastMoveTo;
+                    break;
+                default:;
+            }
+        }
+        REPORTER_ASSERT(reporter, numIterPts == numPoints);
+        REPORTER_ASSERT(reporter, numIterVerbs == numVerbs);
+    }
+}
+
 void TestPath(skiatest::Reporter* reporter);
 void TestPath(skiatest::Reporter* reporter) {
     {
@@ -734,35 +1038,16 @@
     REPORTER_ASSERT(reporter, !p.isRect(NULL));
     test_isRect(reporter);
 
-    SkPoint pt;
-
-    p.moveTo(SK_Scalar1, 0);
-    p.getLastPt(&pt);
-    REPORTER_ASSERT(reporter, pt.fX == SK_Scalar1);
-    REPORTER_ASSERT(reporter, !p.isEmpty());
-
     test_zero_length_paths(reporter);
     test_convexity(reporter);
     test_convexity2(reporter);
     test_close(reporter);
-
-    p.reset();
-    p.moveTo(0, 0);
-    p.quadTo(100, 100, 200, 200);
-    REPORTER_ASSERT(reporter, SkPath::kQuad_SegmentMask == p.getSegmentMasks());
-    REPORTER_ASSERT(reporter, !p.isEmpty());
-    p.cubicTo(100, 100, 200, 200, 300, 300);
-    REPORTER_ASSERT(reporter, kCurveSegmentMask == p.getSegmentMasks());
-    REPORTER_ASSERT(reporter, !p.isEmpty());
-    p.reset();
-    p.moveTo(0, 0);
-    p.cubicTo(100, 100, 200, 200, 300, 300);
-    REPORTER_ASSERT(reporter, SkPath::kCubic_SegmentMask == p.getSegmentMasks());
-    REPORTER_ASSERT(reporter, !p.isEmpty());
-
+    test_segment_masks(reporter);
     test_flattening(reporter);
     test_transform(reporter);
     test_bounds(reporter);
+    test_iter(reporter);
+    test_raw_iter(reporter);
 }
 
 #include "TestClassDef.h"