[canvaskit] Expose AsWinding

Bug: skia:11858
Change-Id: Iedbc2333779f2fac5029779bae44da48d8dd7b8f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/430956
Reviewed-by: Ben Wagner <bungeman@google.com>
diff --git a/modules/canvaskit/CHANGELOG.md b/modules/canvaskit/CHANGELOG.md
index afcc3bb..7258b2e 100644
--- a/modules/canvaskit/CHANGELOG.md
+++ b/modules/canvaskit/CHANGELOG.md
@@ -6,9 +6,13 @@
 
 ## [Unreleased]
 
+### Added
+ - `Path.makeAsWinding` has been added to convert paths with an EvenOdd FillType to the
+   equivalent area using the Winding FillType.
+
 ### Breaking
- - Paint.getBlendMode() has been removed.
- - Canvas.drawImageAtCurrentFrame() has been removed.
+ - `Paint.getBlendMode()` has been removed.
+ - `Canvas.drawImageAtCurrentFrame()` has been removed.
  - FilterQuality enum removed -- pass FilterOptions | CubicResampler instead.
 
 ## [0.28.1] - 2021-06-28
diff --git a/modules/canvaskit/canvaskit_bindings.cpp b/modules/canvaskit/canvaskit_bindings.cpp
index 3898aad..ef5538d 100644
--- a/modules/canvaskit/canvaskit_bindings.cpp
+++ b/modules/canvaskit/canvaskit_bindings.cpp
@@ -329,6 +329,14 @@
     }
     return emscripten::val::null();
 }
+
+SkPathOrNull MakeAsWinding(const SkPath& self) {
+    SkPath out;
+    if (AsWinding(self, &out)) {
+        return emscripten::val(out);
+    }
+    return emscripten::val::null();
+}
 #endif
 
 JSString ToSVGString(const SkPath& path) {
@@ -1511,6 +1519,7 @@
         // PathOps
         .function("_simplify", &ApplySimplify)
         .function("_op", &ApplyPathOp)
+        .function("makeAsWinding", &MakeAsWinding)
 #endif
         // Exporting
         .function("toSVGString", &ToSVGString)
diff --git a/modules/canvaskit/externs.js b/modules/canvaskit/externs.js
index c9e8980..3c7e5d6 100644
--- a/modules/canvaskit/externs.js
+++ b/modules/canvaskit/externs.js
@@ -529,6 +529,7 @@
     getFillType: function() {},
     isEmpty: function() {},
     isVolatile: function() {},
+    makeAsWinding: function() {},
     reset: function() {},
     rewind: function() {},
     setFillType: function() {},
diff --git a/modules/canvaskit/npm_build/types/canvaskit-wasm-tests.ts b/modules/canvaskit/npm_build/types/canvaskit-wasm-tests.ts
index c3e1adb..d27c73a 100644
--- a/modules/canvaskit/npm_build/types/canvaskit-wasm-tests.ts
+++ b/modules/canvaskit/npm_build/types/canvaskit-wasm-tests.ts
@@ -429,6 +429,7 @@
     const p4 = CK.Path.MakeFromVerbsPointsWeights([CK.CONIC_VERB], points, [2.3]);
     const p5 = CK.Path.MakeFromOp(p4, p2!, CK.PathOp.ReverseDifference); // $ExpectType Path | null
     const p6 = CK.Path.MakeFromSVGString('M 205,5 L 795,5 z'); // $ExpectType Path | null
+    const p7 = p3.makeAsWinding(); // $ExpectType Path | null
 
     const someRect = CK.LTRBRect(10, 20, 30, 40);
     // Making sure arrays are accepted as rrects.
diff --git a/modules/canvaskit/npm_build/types/index.d.ts b/modules/canvaskit/npm_build/types/index.d.ts
index af8904a..fafe8b0 100644
--- a/modules/canvaskit/npm_build/types/index.d.ts
+++ b/modules/canvaskit/npm_build/types/index.d.ts
@@ -2318,7 +2318,15 @@
     lineTo(x: number, y: number): Path;
 
     /**
-     * Adds begininning of contour at the given point.
+     * Returns a new path that covers the same area as the original path, but with the
+     * Winding FillType. This may re-draw some contours in the path as counter-clockwise
+     * instead of clockwise to achieve that effect. If such a transformation cannot
+     * be done, null is returned.
+     */
+    makeAsWinding(): Path | null;
+
+    /**
+     * Adds beginning of contour at the given point.
      * Returns the modified path for easier chaining.
      * @param x
      * @param y
diff --git a/modules/canvaskit/tests/path.spec.js b/modules/canvaskit/tests/path.spec.js
index 944038c..8388c67 100644
--- a/modules/canvaskit/tests/path.spec.js
+++ b/modules/canvaskit/tests/path.spec.js
@@ -488,4 +488,93 @@
         path.delete();
         paint.delete();
     });
+
+    gm('winding_example', (canvas) => {
+        // Inspired by https://fiddle.skia.org/c/@Path_FillType_a
+        const path = new CanvasKit.Path();
+        // Draw overlapping rects on top
+        path.addRect(CanvasKit.LTRBRect(10, 10, 30, 30), false);
+        path.addRect(CanvasKit.LTRBRect(20, 20, 40, 40), false);
+        // Draw overlapping rects on bottom, with different direction lines.
+        path.addRect(CanvasKit.LTRBRect(10, 60, 30, 80), false);
+        path.addRect(CanvasKit.LTRBRect(20, 70, 40, 90), true);
+
+        expect(path.getFillType()).toEqual(CanvasKit.FillType.Winding);
+
+        // Draw the two rectangles on the left side.
+        const paint = new CanvasKit.Paint();
+        paint.setStyle(CanvasKit.PaintStyle.Stroke);
+        canvas.drawPath(path, paint);
+
+        const clipRect = CanvasKit.LTRBRect(0, 0, 51, 100);
+        paint.setStyle(CanvasKit.PaintStyle.Fill);
+
+        for (const fillType of [CanvasKit.FillType.Winding, CanvasKit.FillType.EvenOdd]) {
+            canvas.translate(51, 0);
+            canvas.save();
+            canvas.clipRect(clipRect, CanvasKit.ClipOp.Intersect, false);
+            path.setFillType(fillType);
+            canvas.drawPath(path, paint);
+            canvas.restore();
+        }
+
+        path.delete();
+        paint.delete();
+    });
+
+    gm('as_winding', (canvas) => {
+        const evenOddPath = new CanvasKit.Path();
+        // Draw overlapping rects
+        evenOddPath.addRect(CanvasKit.LTRBRect(10, 10, 70, 70), false);
+        evenOddPath.addRect(CanvasKit.LTRBRect(30, 30, 50, 50), false);
+        evenOddPath.setFillType(CanvasKit.FillType.EvenOdd);
+
+        const evenOddCmds = evenOddPath.toCmds();
+        expect(evenOddCmds).toEqual(Float32Array.of(
+          CanvasKit.MOVE_VERB, 10, 10,
+          CanvasKit.LINE_VERB, 70, 10,
+          CanvasKit.LINE_VERB, 70, 70,
+          CanvasKit.LINE_VERB, 10, 70,
+          CanvasKit.CLOSE_VERB,
+          CanvasKit.MOVE_VERB, 30, 30, // This contour is drawn
+          CanvasKit.LINE_VERB, 50, 30, // clockwise, as specified.
+          CanvasKit.LINE_VERB, 50, 50,
+          CanvasKit.LINE_VERB, 30, 50,
+          CanvasKit.CLOSE_VERB
+        ));
+
+        const windingPath = evenOddPath.makeAsWinding();
+
+        expect(windingPath.getFillType()).toBe(CanvasKit.FillType.Winding);
+        const windingCmds = windingPath.toCmds();
+        expect(windingCmds).toEqual(Float32Array.of(
+          CanvasKit.MOVE_VERB, 10, 10,
+          CanvasKit.LINE_VERB, 70, 10,
+          CanvasKit.LINE_VERB, 70, 70,
+          CanvasKit.LINE_VERB, 10, 70,
+          CanvasKit.CLOSE_VERB,
+          CanvasKit.MOVE_VERB, 30, 50, // This contour has been
+          CanvasKit.LINE_VERB, 50, 50, // re-drawn counter-clockwise
+          CanvasKit.LINE_VERB, 50, 30, // so that it covers the same
+          CanvasKit.LINE_VERB, 30, 30, // area, but with the winding fill type.
+          CanvasKit.CLOSE_VERB
+        ));
+
+        const paint = new CanvasKit.Paint();
+        paint.setStyle(CanvasKit.PaintStyle.Fill);
+        const font = new CanvasKit.Font(null, 20);
+
+        canvas.drawText('Original path (even odd)', 5, 20, paint, font);
+        canvas.translate(0, 50);
+        canvas.drawPath(evenOddPath, paint);
+
+        canvas.translate(300, 0);
+        canvas.drawPath(windingPath, paint);
+
+        canvas.translate(0, -50);
+        canvas.drawText('makeAsWinding path', 5, 20, paint, font);
+
+        evenOddPath.delete();
+        windingPath.delete();
+    });
 });