pathops remove degenerate line on close

If first and last point are very nearly the same,
pathops considers the contour closed, but SkPath
adds a degenerate line to connect the two.

Change pathops to emit the first point rather than
a point very nearly the same as the last point
in a contour to avoid the degenerate line.

TBR=reed@google.com
Bug: skia:8249
Change-Id: Ibcc18643c78db4955c9eda9ca90219aad81d56d5
Reviewed-on: https://skia-review.googlesource.com/147720
Reviewed-by: Cary Clark <caryclark@skia.org>
Commit-Queue: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
diff --git a/src/pathops/SkPathWriter.cpp b/src/pathops/SkPathWriter.cpp
index 797f42f..5729827 100644
--- a/src/pathops/SkPathWriter.cpp
+++ b/src/pathops/SkPathWriter.cpp
@@ -32,21 +32,21 @@
 }
 
 void SkPathWriter::conicTo(const SkPoint& pt1, const SkOpPtT* pt2, SkScalar weight) {
-    this->update(pt2);
+    SkPoint pt2pt = this->update(pt2);
 #if DEBUG_PATH_CONSTRUCTION
     SkDebugf("path.conicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g);\n",
-            pt1.fX, pt1.fY, pt2->fPt.fX, pt2->fPt.fY, weight);
+            pt1.fX, pt1.fY, pt2pt.fX, pt2pt.fY, weight);
 #endif
-    fCurrent.conicTo(pt1, pt2->fPt, weight);
+    fCurrent.conicTo(pt1, pt2pt, weight);
 }
 
 void SkPathWriter::cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkOpPtT* pt3) {
-    this->update(pt3);
+    SkPoint pt3pt = this->update(pt3);
 #if DEBUG_PATH_CONSTRUCTION
     SkDebugf("path.cubicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g,%1.9g);\n",
-            pt1.fX, pt1.fY, pt2.fX, pt2.fY, pt3->fPt.fX, pt3->fPt.fY);
+            pt1.fX, pt1.fY, pt2.fX, pt2.fY, pt3pt.fX, pt3pt.fY);
 #endif
-    fCurrent.cubicTo(pt1, pt2, pt3->fPt);
+    fCurrent.cubicTo(pt1, pt2, pt3pt);
 }
 
 bool SkPathWriter::deferredLine(const SkOpPtT* pt) {
@@ -144,21 +144,28 @@
 }
 
 void SkPathWriter::quadTo(const SkPoint& pt1, const SkOpPtT* pt2) {
-    this->update(pt2);
+    SkPoint pt2pt = this->update(pt2);
 #if DEBUG_PATH_CONSTRUCTION
     SkDebugf("path.quadTo(%1.9g,%1.9g, %1.9g,%1.9g);\n",
-            pt1.fX, pt1.fY, pt2->fPt.fX, pt2->fPt.fY);
+            pt1.fX, pt1.fY, pt2pt.fX, pt2pt.fY);
 #endif
-    fCurrent.quadTo(pt1, pt2->fPt);
+    fCurrent.quadTo(pt1, pt2pt);
 }
 
-void SkPathWriter::update(const SkOpPtT* pt) {
+// if last point to be written matches the current path's first point, alter the
+// last to avoid writing a degenerate lineTo when the path is closed
+SkPoint SkPathWriter::update(const SkOpPtT* pt) {
     if (!fDefer[1]) {
         this->moveTo();
     } else if (!this->matchedLast(fDefer[0])) {
         this->lineTo();
     }
+    SkPoint result = pt->fPt;
+    if (fFirstPtT && result != fFirstPtT->fPt && fFirstPtT->contains(pt)) {
+        result = fFirstPtT->fPt;
+    }
     fDefer[0] = fDefer[1] = pt;  // set both to know that there is not a pending deferred line
+    return result;
 }
 
 bool SkPathWriter::someAssemblyRequired() {
diff --git a/src/pathops/SkPathWriter.h b/src/pathops/SkPathWriter.h
index 6534b84..b3a26b4 100644
--- a/src/pathops/SkPathWriter.h
+++ b/src/pathops/SkPathWriter.h
@@ -41,7 +41,7 @@
     void moveTo();
     const SkTArray<SkPath>& partials() const { return fPartials; }
     bool someAssemblyRequired();
-    void update(const SkOpPtT* pt);
+    SkPoint update(const SkOpPtT* pt);
 
     SkPath fCurrent;  // contour under construction
     SkTArray<SkPath> fPartials;   // contours with mismatched starts and ends
diff --git a/tests/PathOpsSimplifyTest.cpp b/tests/PathOpsSimplifyTest.cpp
index 8a5c937..e758cfb 100644
--- a/tests/PathOpsSimplifyTest.cpp
+++ b/tests/PathOpsSimplifyTest.cpp
@@ -9267,11 +9267,39 @@
 testSimplifyFail(reporter, path, filename);
 }
 
+static void bug8249(skiatest::Reporter* reporter, const char* filename) {
+SkPath path;
+path.setFillType(SkPath::kWinding_FillType);
+path.moveTo(SkBits2Float(0x43310000), SkBits2Float(0x43810000));  // 177, 258
+path.lineTo(SkBits2Float(0x43480000), SkBits2Float(0x43868000));  // 200, 269
+path.cubicTo(SkBits2Float(0x43480000), SkBits2Float(0x43b20000), SkBits2Float(0x437a0000), SkBits2Float(0x43cd0000), SkBits2Float(0x43c80000), SkBits2Float(0x43cd0000));  // 200, 356, 250, 410, 400, 410
+path.cubicTo(SkBits2Float(0x44098000), SkBits2Float(0x43cd0000), SkBits2Float(0x44160000), SkBits2Float(0x43b20000), SkBits2Float(0x44160000), SkBits2Float(0x43868000));  // 550, 410, 600, 356, 600, 269
+path.lineTo(SkBits2Float(0x44160000), SkBits2Float(0x43808000));  // 600, 257
+path.cubicTo(SkBits2Float(0x44160000), SkBits2Float(0x43330000), SkBits2Float(0x44110000), SkBits2Float(0x429c0000), SkBits2Float(0x43cd0000), SkBits2Float(0x429c0000));  // 600, 179, 580, 78, 410, 78
+path.cubicTo(SkBits2Float(0x43700000), SkBits2Float(0x429c0000), SkBits2Float(0x43480000), SkBits2Float(0x431f0000), SkBits2Float(0x43480000), SkBits2Float(0x438a8000));  // 240, 78, 200, 159, 200, 277
+path.lineTo(SkBits2Float(0x43480000), SkBits2Float(0x4401c000));  // 200, 519
+path.cubicTo(SkBits2Float(0x43480000), SkBits2Float(0x441f0000), SkBits2Float(0x43660000), SkBits2Float(0x44340000), SkBits2Float(0x43c80000), SkBits2Float(0x44340000));  // 200, 636, 230, 720, 400, 720
+path.cubicTo(SkBits2Float(0x4404c000), SkBits2Float(0x44340000), SkBits2Float(0x440d0000), SkBits2Float(0x442b8000), SkBits2Float(0x44118000), SkBits2Float(0x4416c000));  // 531, 720, 564, 686, 582, 603
+path.lineTo(SkBits2Float(0x442cc000), SkBits2Float(0x441c8000));  // 691, 626
+path.cubicTo(SkBits2Float(0x44260000), SkBits2Float(0x443d4000), SkBits2Float(0x44114000), SkBits2Float(0x444a8000), SkBits2Float(0x43c88000), SkBits2Float(0x444a8000));  // 664, 757, 581, 810, 401, 810
+path.cubicTo(SkBits2Float(0x43350000), SkBits2Float(0x444a8000), SkBits2Float(0x42c80000), SkBits2Float(0x442e0000), SkBits2Float(0x42c80000), SkBits2Float(0x4401c000));  // 181, 810, 100, 696, 100, 519
+path.lineTo(SkBits2Float(0x42c80000), SkBits2Float(0x438a8000));  // 100, 277
+path.cubicTo(SkBits2Float(0x42c80000), SkBits2Float(0x42cc0000), SkBits2Float(0x433e0000), SkBits2Float(0xc1200000), SkBits2Float(0x43cd0000), SkBits2Float(0xc1200000));  // 100, 102, 190, -10, 410, -10
+path.cubicTo(SkBits2Float(0x441d8000), SkBits2Float(0xc1200000), SkBits2Float(0x442f0000), SkBits2Float(0x42e60000), SkBits2Float(0x442f0000), SkBits2Float(0x437a0000));  // 630, -10, 700, 115, 700, 250
+path.lineTo(SkBits2Float(0x442f0000), SkBits2Float(0x43880000));  // 700, 272
+path.cubicTo(SkBits2Float(0x442f0000), SkBits2Float(0x43d18000), SkBits2Float(0x44164000), SkBits2Float(0x43fa0000), SkBits2Float(0x43c88000), SkBits2Float(0x43fa0000));  // 700, 419, 601, 500, 401, 500
+path.cubicTo(SkBits2Float(0x43490000), SkBits2Float(0x43fa0000), SkBits2Float(0x43160000), SkBits2Float(0x43d00000), SkBits2Float(0x43160000), SkBits2Float(0x43868000));  // 201, 500, 150, 416, 150, 269
+path.lineTo(SkBits2Float(0x43310000), SkBits2Float(0x43810000));  // 177, 258
+path.close();
+testSimplify(reporter, path, filename);
+}
+
 static void (*skipTest)(skiatest::Reporter* , const char* filename) = nullptr;
 static void (*firstTest)(skiatest::Reporter* , const char* filename) = nullptr;
 static void (*stopTest)(skiatest::Reporter* , const char* filename) = nullptr;
 
 static TestDesc tests[] = {
+    TEST(bug8249),
     TEST(grshapearc),
     TEST(coincubics),
     TEST(joel_16x),