[PathKit] Add various path effects

API Changes (nothing should be breaking):
 - Exposes conic, although all output formats (SVG, Canvas) need conics
to be approximated with quads. Tests have been added to verify this
happens.
 - Add .dash(), .trim(), .stroke() and examples for them.
 - Expose Stroke enums (StrokeJoin, StrokeCap)

Adds tests for the cubic part and clean up a few spacing things.

There are some changes to the C++ code to simplify the build -
otherwise, I need to appease the linker and add add in a bunch
of files that may or may not get optimized out.  Best make them
not even be compiled, just to make sure.

Bug: skia:8216
Change-Id: I1da3aaab1891f14a5b3dc01bb6523b4fd9a87b04
Reviewed-on: https://skia-review.googlesource.com/146650
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/experimental/pathkit/compile.sh b/experimental/pathkit/compile.sh
index 2f66588..fc43301 100755
--- a/experimental/pathkit/compile.sh
+++ b/experimental/pathkit/compile.sh
@@ -44,7 +44,7 @@
 # Use -O0 for larger builds (but generally quicker)
 # Use -Oz for (much slower, but smaller/faster) production builds
 export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/helper_externs.js "
-RELEASE_CONF="-Oz --closure 1 -s EVAL_CTORS=1"
+RELEASE_CONF="-Oz --closure 1 -s EVAL_CTORS=1 --llvm-lto 3"
 if [[ $@ == *test* ]]; then
   echo "Building a Testing/Profiling build"
   RELEASE_CONF="-O2 --profiling -DPATHKIT_TESTING -DSK_RELEASE"
@@ -73,12 +73,19 @@
 em++ $RELEASE_CONF -std=c++14 \
 -Iinclude/config \
 -Iinclude/core \
+-Iinclude/effects \
+-Iinclude/gpu \
 -Iinclude/pathops \
 -Iinclude/private \
 -Iinclude/utils \
 -Isrc/core \
+-Isrc/gpu \
+-Isrc/shaders \
+-Isrc/opts \
+-Isrc/utils \
 --bind \
 --pre-js $BASE_DIR/helper.js \
+-DWEB_ASSEMBLY=1 \
 -fno-rtti -fno-exceptions -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0 \
 $WASM_CONF \
 -s MODULARIZE=1 \
@@ -91,22 +98,40 @@
 -s STRICT=1 \
 $OUTPUT \
 $BASE_DIR/pathkit_wasm_bindings.cpp \
+src/core/SkAnalyticEdge.cpp \
 src/core/SkArenaAlloc.cpp \
+src/core/SkEdge.cpp \
+src/core/SkEdgeBuilder.cpp \
+src/core/SkEdgeClipper.cpp \
+src/core/SkFDot6Constants.cpp \
+src/core/SkFlattenable.cpp \
 src/core/SkGeometry.cpp \
+src/core/SkLineClipper.cpp \
 src/core/SkMallocPixelRef.cpp \
 src/core/SkMath.cpp \
 src/core/SkMatrix.cpp \
+src/core/SkOpts.cpp \
+src/core/SkPaint.cpp \
 src/core/SkPath.cpp \
+src/core/SkPathEffect.cpp \
+src/core/SkPathMeasure.cpp \
 src/core/SkPathRef.cpp \
 src/core/SkPoint.cpp \
+src/core/SkRRect.cpp \
 src/core/SkRect.cpp \
 src/core/SkStream.cpp \
 src/core/SkString.cpp \
 src/core/SkStringUtils.cpp \
+src/core/SkStroke.cpp \
+src/core/SkStrokeRec.cpp \
+src/core/SkStrokerPriv.cpp \
 src/core/SkUtils.cpp \
+src/effects/SkDashPathEffect.cpp \
+src/effects/SkTrimPathEffect.cpp \
 src/pathops/*.cpp \
 src/ports/SkDebug_stdio.cpp \
 src/ports/SkMemory_malloc.cpp \
+src/utils/SkDashPath.cpp \
 src/utils/SkParse.cpp \
 src/utils/SkParsePath.cpp \
 src/utils/SkUTF.cpp
diff --git a/experimental/pathkit/karma-docker.conf.js b/experimental/pathkit/karma-docker.conf.js
index 50397cc..4695fd4 100644
--- a/experimental/pathkit/karma-docker.conf.js
+++ b/experimental/pathkit/karma-docker.conf.js
@@ -14,6 +14,10 @@
       'tests/*.spec.js'
     ],
 
+    proxies: {
+      "/pathkit/": "/base/npm-wasm/bin/test/"
+    },
+
     // test results reporter to use
     // possible values: 'dots', 'progress'
     // available reporters: https://npmjs.org/browse/keyword/karma-reporter
diff --git a/experimental/pathkit/karma.conf.js b/experimental/pathkit/karma.conf.js
index bbb5667..c3104b4 100644
--- a/experimental/pathkit/karma.conf.js
+++ b/experimental/pathkit/karma.conf.js
@@ -14,6 +14,10 @@
       'tests/*.spec.js'
     ],
 
+    proxies: {
+      "/pathkit/": "/base/npm-wasm/bin/test/"
+    },
+
     // test results reporter to use
     // possible values: 'dots', 'progress'
     // available reporters: https://npmjs.org/browse/keyword/karma-reporter
diff --git a/experimental/pathkit/npm-wasm/example.html b/experimental/pathkit/npm-wasm/example.html
index c1fa827..ec097b9 100644
--- a/experimental/pathkit/npm-wasm/example.html
+++ b/experimental/pathkit/npm-wasm/example.html
@@ -30,6 +30,14 @@
 <canvas class=big id=canvas3></canvas>
 <canvas class=big id=canvas4></canvas>
 
+<h2> Has various Path Effects </h2>
+<canvas class=big id=canvas5></canvas>
+<canvas class=big id=canvas6></canvas>
+<canvas class=big id=canvas7></canvas>
+<canvas class=big id=canvas8></canvas>
+<canvas class=big id=canvas9></canvas>
+<canvas class=big id=canvas10></canvas>
+
 <script type="text/javascript" src="/node_modules/experimental-pathkit-wasm/bin/pathkit.js"></script>
 
 <script type="text/javascript" charset="utf-8">
@@ -39,6 +47,7 @@
   }).then((PathKit) => {
     OutputsExample(PathKit);
     Path2DExample(PathKit);
+    PathEffectsExample(PathKit);
   });
 
   function setCanvasSize(ctx, width, height) {
@@ -158,4 +167,73 @@
     objs[1].delete();
   }
 
+  // see https://fiddle.skia.org/c/@discrete_path
+  function drawStar(path) {
+    let R = 115.2, C = 128.0;
+    path.moveTo(C + R + 22, C);
+    for (let i = 1; i < 8; i++) {
+        let a = 2.6927937 * i;
+        path.lineTo(C + R * Math.cos(a) + 22, C + R * Math.sin(a));
+    }
+    path.closePath();
+    return path;
+  }
+
+  function PathEffectsExample(PathKit) {
+    let transforms = [
+        // no-op
+        (path) => path,
+        // dash
+        (path) => path.dash(10, 3, 0),
+        // trim (takes optional 3rd param for returning the trimmed part
+        // or the complement)
+        (path) => path.trim(0.25, 0.8, false),
+        // simplify
+        (path) => path.simplify(),
+        // stroke
+        (path) => path.stroke(15, PathKit.StrokeJoin.BEVEL, PathKit.StrokeCap.BUTT),
+        // "offset effect", that is, making a border around the shape.
+        (path) => {
+            let temp = path.stroke(10, PathKit.StrokeJoin.ROUND, PathKit.StrokeCap.SQUARE);
+            let ret = temp.op(path, PathKit.PathOp.DIFFERENCE);
+            temp.delete();
+            return ret;
+        }
+    ];
+
+    let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Offset"];
+
+    for (let i = 0; i < transforms.length; i++) {
+        let path = PathKit.NewPath();
+        drawStar(path);
+
+        let transformedPath = transforms[i](path);
+
+        let ctx = document.getElementById(`canvas${i+5}`).getContext('2d');
+        setCanvasSize(ctx, 300, 300);
+        ctx.strokeStyle = '#3c597a';
+        ctx.fillStyle = '#3c597a';
+        if (i === 4 || i === 5) {
+            ctx.fill(transformedPath.toPath2D(), transformedPath.getCanvasFillType());
+            if (i === 5) {
+                ctx.fillStyle = "#51d9bb";
+                ctx.fill(path.toPath2D());
+            }
+        } else {
+            ctx.stroke(transformedPath.toPath2D());
+        }
+
+        ctx.font = '42px monospace';
+
+        let x = 150-ctx.measureText(names[i]).width/2;
+        ctx.strokeText(names[i], x, 290);
+
+        if (i) {
+            transformedPath.delete();
+        }
+        path.delete();
+    }
+
+  }
+
 </script>
diff --git a/experimental/pathkit/npm-wasm/package.json b/experimental/pathkit/npm-wasm/package.json
index daba5db..b3e42b4 100644
--- a/experimental/pathkit/npm-wasm/package.json
+++ b/experimental/pathkit/npm-wasm/package.json
@@ -1,6 +1,6 @@
 {
   "name": "experimental-pathkit-wasm",
-  "version": "0.1.4",
+  "version": "0.1.7",
   "description": "A WASM version of Skia's PathOps toolkit",
   "main": "bin/pathkit.js",
   "homepage": "https://github.com/google/skia/tree/master/experimental/pathkit",
diff --git a/experimental/pathkit/pathkit_wasm_bindings.cpp b/experimental/pathkit/pathkit_wasm_bindings.cpp
index 5587cad..5909e39 100644
--- a/experimental/pathkit/pathkit_wasm_bindings.cpp
+++ b/experimental/pathkit/pathkit_wasm_bindings.cpp
@@ -5,14 +5,17 @@
  * found in the LICENSE file.
  */
 
+#include "SkDashPathEffect.h"
 #include "SkFloatBits.h"
 #include "SkFloatingPoint.h"
 #include "SkMatrix.h"
+#include "SkPaint.h"
 #include "SkParsePath.h"
 #include "SkPath.h"
 #include "SkPathOps.h"
 #include "SkRect.h"
 #include "SkString.h"
+#include "SkTrimPathEffect.h"
 
 #include <emscripten/emscripten.h>
 #include <emscripten/bind.h>
@@ -22,13 +25,16 @@
 static const int MOVE = 0;
 static const int LINE = 1;
 static const int QUAD = 2;
+static const int CONIC = 3;
 static const int CUBIC = 4;
 static const int CLOSE = 5;
 
 // Just for self-documenting purposes where the main thing being returned is an
-// SkPath, but in an error case, something of type val (e.g. null) could also be
+// SkPath, but in an error case, something of type null (which is val) could also be
 // returned;
-using SkPathOrVal = emscripten::val;
+using SkPathOrNull = emscripten::val;
+// Self-documenting for when we return a string
+using JSString = emscripten::val;
 
 // =================================================================================
 // Creating/Exporting Paths with cmd arrays
@@ -60,16 +66,9 @@
             cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
             break;
         case SkPath::kConic_Verb:
-            SkPoint quads[5];
-            // approximate with 2^1=2 quads.
-            SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
-            cmd.call<void>("push", MOVE, quads[0].x(), quads[0].y());
-            cmds.call<void>("push", cmd);
-            cmd = emscripten::val::array();
-            cmd.call<void>("push", QUAD, quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y());
-            cmds.call<void>("push", cmd);
-            cmd = emscripten::val::array();
-            cmd.call<void>("push", QUAD, quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y());
+            cmd.call<void>("push", CONIC,
+                           pts[1].x(), pts[1].y(),
+                           pts[2].x(), pts[2].y(), iter.conicWeight());
             break;
         case SkPath::kCubic_Verb:
             cmd.call<void>("push", CUBIC,
@@ -99,7 +98,7 @@
 // in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
 // types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and
 // the compiler is happy.
-SkPathOrVal EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
+SkPathOrNull EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
     const auto* cmds = reinterpret_cast<const float*>(cptr);
     SkPath path;
     float x1, y1, x2, y2, x3, y3;
@@ -158,7 +157,7 @@
 // SVG things
 //========================================================================================
 
-emscripten::val EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
+JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
     SkString s;
     SkParsePath::ToSVGString(path, &s);
     // Wrapping it in val automatically turns it into a JS string.
@@ -169,7 +168,7 @@
 }
 
 
-SkPathOrVal EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
+SkPathOrNull EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
     SkPath path;
     if (SkParsePath::FromSVGString(str.c_str(), &path)) {
         return emscripten::val(path);
@@ -181,7 +180,7 @@
 // PATHOP things
 //========================================================================================
 
-SkPathOrVal EMSCRIPTEN_KEEPALIVE SimplifyPath(const SkPath& path) {
+SkPathOrNull EMSCRIPTEN_KEEPALIVE SimplifyPath(const SkPath& path) {
     SkPath simple;
     if (Simplify(path, &simple)) {
         return emscripten::val(simple);
@@ -189,7 +188,7 @@
     return emscripten::val::null();
 }
 
-SkPathOrVal EMSCRIPTEN_KEEPALIVE ApplyPathOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
+SkPathOrNull EMSCRIPTEN_KEEPALIVE ApplyPathOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
     SkPath path;
     if (Op(pathOne, pathTwo, op, &path)) {
         return emscripten::val(path);
@@ -197,7 +196,7 @@
     return emscripten::val::null();
 }
 
-SkPathOrVal EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
+SkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
     SkPath path;
     if (builder.resolve(&path)) {
         return emscripten::val(path);
@@ -228,7 +227,6 @@
                 SkPoint quads[5];
                 // approximate with 2^1=2 quads.
                 SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
-                ctx.call<void>("moveTo", quads[0].x(), quads[0].y());
                 ctx.call<void>("quadraticCurveTo", quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y());
                 ctx.call<void>("quadraticCurveTo", quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y());
                 break;
@@ -326,6 +324,70 @@
     orig.addPath(newPath, m);
 }
 
+JSString GetCanvasFillType(const SkPath& path) {
+    if (path.getFillType() == SkPath::FillType::kWinding_FillType) {
+        return emscripten::val("nonzero");
+    } else if (path.getFillType() == SkPath::FillType::kEvenOdd_FillType) {
+        return emscripten::val("evenodd");
+    } else {
+        SkDebugf("warning: can't translate inverted filltype to HTML Canvas\n");
+        return emscripten::val("nonzero"); //Use default
+    }
+}
+
+//========================================================================================
+// Path Effects
+//========================================================================================
+
+SkPathOrNull PathEffectDash(const SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
+    SkPath output;
+    SkScalar intervals[] = { on, off };
+    auto pe = SkDashPathEffect::Make(intervals, 2, phase);
+    if (!pe) {
+        SkDebugf("Invalid args to dash()\n");
+        return emscripten::val::null();
+    }
+    if (pe->filterPath(&output, path, nullptr, nullptr)) {
+        return emscripten::val(output);
+    }
+    SkDebugf("Could not make dashed path\n");
+    return emscripten::val::null();
+}
+
+SkPathOrNull PathEffectTrim(const SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
+    SkPath output;
+    auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
+    auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
+    if (!pe) {
+        SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
+        return emscripten::val::null();
+    }
+    if (pe->filterPath(&output, path, nullptr, nullptr)) {
+        return emscripten::val(output);
+    }
+    SkDebugf("Could not trim path\n");
+    return emscripten::val::null();
+}
+
+SkPathOrNull PathEffectTrim(const SkPath& path, SkScalar startT, SkScalar stopT) {
+    return PathEffectTrim(path, startT, stopT, false);
+}
+
+SkPathOrNull PathEffectStroke(const SkPath& path, SkScalar width, SkPaint::Join join, SkPaint::Cap cap) {
+    SkPath output;
+    SkPaint p;
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeCap(cap);
+    p.setStrokeJoin(join);
+    p.setStrokeWidth(width);
+
+    if (p.getFillPath(path, &output)) {
+        return emscripten::val(output);
+    }
+    SkDebugf("Could not stroke path\n");
+    return emscripten::val::null();
+}
+
 //========================================================================================
 // Testing things
 //========================================================================================
@@ -384,6 +446,8 @@
         .function("addPath",
             select_overload<void(SkPath&, const SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&Path2DAddPath))
         .function("close", &SkPath::close)
+        .function("conicTo",
+            select_overload<void(SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::conicTo))
         .function("cubicTo",
             select_overload<void(SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::cubicTo))
         .function("quadTo",
@@ -392,9 +456,16 @@
         // Extra features
         .function("setFillType", &SkPath::setFillType)
         .function("getFillType", &SkPath::getFillType)
+        .function("getCanvasFillType", &GetCanvasFillType)
         .function("getBounds", &SkPath::getBounds)
         .function("computeTightBounds", &SkPath::computeTightBounds)
 
+        // PathEffects
+        .function("dash", &PathEffectDash)
+        .function("trim", select_overload<SkPathOrNull(const SkPath&, SkScalar, SkScalar)>(&PathEffectTrim))
+        .function("trim", select_overload<SkPathOrNull(const SkPath&, SkScalar, SkScalar, bool)>(&PathEffectTrim))
+        .function("stroke", &PathEffectStroke)
+
         // PathOps
         .function("simplify", &SimplifyPath)
         .function("op", &ApplyPathOp)
@@ -407,6 +478,7 @@
 
 #ifdef PATHKIT_TESTING
         .function("dump", select_overload<void() const>(&SkPath::dump))
+        .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
 #endif
         ;
 
@@ -444,6 +516,7 @@
     constant("MOVE_VERB",  MOVE);
     constant("LINE_VERB",  LINE);
     constant("QUAD_VERB",  QUAD);
+    constant("CONIC_VERB", CONIC);
     constant("CUBIC_VERB", CUBIC);
     constant("CLOSE_VERB", CLOSE);
 
@@ -458,12 +531,20 @@
 
     function("MakeLTRBRect", &SkRect::MakeLTRB);
 
-    // coming soon - Stroke
+    // Stroke
+    enum_<SkPaint::Join>("StrokeJoin")
+        .value("MITER", SkPaint::Join::kMiter_Join)
+        .value("ROUND", SkPaint::Join::kRound_Join)
+        .value("BEVEL", SkPaint::Join::kBevel_Join);
+
+    enum_<SkPaint::Cap>("StrokeCap")
+        .value("BUTT",   SkPaint::Cap::kButt_Cap)
+        .value("ROUND",  SkPaint::Cap::kRound_Cap)
+        .value("SQUARE", SkPaint::Cap::kSquare_Cap);
+
 
     // coming soon - Matrix
 
-    // coming soon - Trim
-
     // Test Utils
     function("SkBits2FloatUnsigned", &SkBits2FloatUnsigned);
 }
diff --git a/experimental/pathkit/tests/path.spec.js b/experimental/pathkit/tests/path.spec.js
index 660ade5..0cafc38 100644
--- a/experimental/pathkit/tests/path.spec.js
+++ b/experimental/pathkit/tests/path.spec.js
@@ -2,12 +2,12 @@
 describe('PathKit\'s Path Behavior', function() {
     // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
     var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject){
+    const LoadPathKit = new Promise(function(resolve, reject) {
         if (PathKit) {
             resolve();
         } else {
             PathKitInit({
-                locateFile: (file) => '/base/npm-wasm/bin/test/'+file,
+                locateFile: (file) => '/pathkit/'+file,
             }).then((_PathKit) => {
                 PathKit = _PathKit;
                 resolve();
@@ -31,6 +31,10 @@
         });
     });
 
+    function bits2float(str) {
+        return PathKit.SkBits2FloatUnsigned(parseInt(str))
+    }
+
     it('has getBounds() and computeTightBounds()', function(done){
         LoadPathKit.then(() => {
             // Based on PathOpsTightBoundsIllBehaved
@@ -39,12 +43,36 @@
             path.quadraticCurveTo(4, 3, 2, 2);
             expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(1, 1, 4, 3));
             expect(path.computeTightBounds()).toEqual(PathKit.MakeLTRBRect(1, 1,
-                        PathKit.SkBits2FloatUnsigned(parseInt("0x40333334")),  // 2.8
-                        PathKit.SkBits2FloatUnsigned(parseInt("0x40155556")))); // 2.3333333
+                        bits2float("0x40333334"),  // 2.8
+                        bits2float("0x40155556"))); // 2.3333333
             path.delete();
 
             done();
         });
     });
 
+    it('does NOT approximates conics when dumping as toCmds', function(done){
+        LoadPathKit.then(() => {
+            let path = PathKit.NewPath();
+            path.moveTo(20, 120);
+            path.arc(20, 120, 18, 0, 1.75 * Math.PI);
+            path.lineTo(20, 120);
+
+            let expectedCmds = [
+                [PathKit.MOVE_VERB, 20, 120],
+                [PathKit.LINE_VERB, 38, 120],
+                [PathKit.CONIC_VERB, 38, 138, 20, 138, bits2float("0x3f3504f3)")], // 0.707107f
+                [PathKit.CONIC_VERB, 2, 138, 2, 120, bits2float("0x3f3504f3)")],   // 0.707107f
+                [PathKit.CONIC_VERB, 2, 102, 20, 102, bits2float("0x3f3504f3)")],  // 0.707107f
+                [PathKit.CONIC_VERB, bits2float("0x41dba58e"), 102, bits2float("0x4202e962"), bits2float("0x42d68b4d"), bits2float("0x3f6c8361")],  // 27.4558, 102, 32.7279, 107.272, 0.92388
+                [PathKit.LINE_VERB, 20, 120],
+            ];
+            let actual = path.toCmds();
+            expect(actual).toEqual(expectedCmds);
+
+            path.delete();
+            done();
+        });
+    });
+
 });
diff --git a/experimental/pathkit/tests/path2d.spec.js b/experimental/pathkit/tests/path2d.spec.js
index 6e1cf95..303bf87 100644
--- a/experimental/pathkit/tests/path2d.spec.js
+++ b/experimental/pathkit/tests/path2d.spec.js
@@ -10,12 +10,12 @@
 
     // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
     var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject){
+    const LoadPathKit = new Promise(function(resolve, reject) {
         if (PathKit) {
             resolve();
         } else {
             PathKitInit({
-                locateFile: (file) => '/base/npm-wasm/bin/test/'+file,
+                locateFile: (file) => '/pathkit/'+file,
             }).then((_PathKit) => {
                 PathKit = _PathKit;
                 resolve();
@@ -23,7 +23,7 @@
         }
     });
 
-    it('can do everything in the Path2D API w/o crashing', function(done){
+    it('can do everything in the Path2D API w/o crashing', function(done) {
         LoadPathKit.then(() => {
             // This is taken from example.html
             let path = PathKit.NewPath();
@@ -79,4 +79,29 @@
         });
     });
 
-});
\ No newline at end of file
+    it('approximates arcs (conics) with quads', function(done) {
+        LoadPathKit.then(() => {
+            let path = PathKit.NewPath();
+            path.moveTo(20, 120);
+            path.arc(20, 120, 18, 0, 1.75 * Math.PI);
+            path.lineTo(20, 120);
+
+            let canvas = document.createElement('canvas');
+            container.appendChild(canvas);
+            let canvasCtx = canvas.getContext('2d');
+
+            spyOn(canvasCtx, 'quadraticCurveTo');
+
+            canvasCtx.beginPath();
+            path.toCanvas(canvasCtx);
+            canvasCtx.stroke();
+            // No need to check the whole path, as that's more what the
+            // gold correctness tests are for (can account for changes we make
+            // to the approximation algorithms).
+            expect(canvasCtx.quadraticCurveTo).toHaveBeenCalled();
+            path.delete();
+            done();
+        });
+    });
+
+});
diff --git a/experimental/pathkit/tests/pathops.spec.js b/experimental/pathkit/tests/pathops.spec.js
index c9a3d54..3a9228e 100644
--- a/experimental/pathkit/tests/pathops.spec.js
+++ b/experimental/pathkit/tests/pathops.spec.js
@@ -77,12 +77,12 @@
     var PathKit = null;
     var PATHOP_MAP = {};
     var FILLTYPE_MAP = {};
-    const LoadPathKit = new Promise(function(resolve, reject){
+    const LoadPathKit = new Promise(function(resolve, reject) {
         if (PathKit) {
             resolve();
         } else {
             PathKitInit({
-                locateFile: (file) => '/base/npm-wasm/bin/test/'+file,
+                locateFile: (file) => '/pathkit/'+file,
             }).then((_PathKit) => {
                 PathKit = _PathKit;
                 PATHOP_MAP = {
@@ -152,6 +152,9 @@
                             // Do a tolerant match.
                             let diff = diffPaths(expected, combined);
                             if (test.expectMatch === 'yes'){
+                                // Check fill type
+                                expect(combined.getFillType().value).toEqual(getFillType(test.fillTypeOut).value);
+                                // diff should be null if the paths are identical (modulo rounding)
                                 if (diff) {
                                     expect(`[${testName}] ${diff}`).toBe('');
                                     addSVG('[PathOps] ' + testName, expected, combined, diff);
@@ -202,6 +205,9 @@
                             // Do a tolerant match.
                             let diff = diffPaths(expected, simplified);
                             if (test.expectMatch === 'yes'){
+                                // Check fill type
+                                expect(simplified.getFillType().value).toEqual(getFillType(test.fillTypeOut).value);
+                                // diff should be null if the paths are identical (modulo rounding)
                                 if (diff) {
                                     expect(`[${testName}] ${diff}`).toBe('');
                                     addSVG('[Simplify] ' + testName, expected, simplified, diff);
@@ -223,4 +229,4 @@
             });
         });
     });
-});
\ No newline at end of file
+});
diff --git a/experimental/pathkit/tests/svg.spec.js b/experimental/pathkit/tests/svg.spec.js
index f9d1b02..f4c3007 100644
--- a/experimental/pathkit/tests/svg.spec.js
+++ b/experimental/pathkit/tests/svg.spec.js
@@ -2,12 +2,12 @@
 describe('PathKit\'s SVG Behavior', function() {
     // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
     var PathKit = null;
-    const LoadPathKit = new Promise(function(resolve, reject){
+    const LoadPathKit = new Promise(function(resolve, reject) {
         if (PathKit) {
             resolve();
         } else {
             PathKitInit({
-                locateFile: (file) => '/base/npm-wasm/bin/test/'+file,
+                locateFile: (file) => '/pathkit/'+file,
             }).then((_PathKit) => {
                 PathKit = _PathKit;
                 resolve();
@@ -15,7 +15,7 @@
         }
     });
 
-    it('can create a path from an SVG string', function(done){
+    it('can create a path from an SVG string', function(done) {
         LoadPathKit.then(() => {
             //.This is a parallelagram from
             // https://upload.wikimedia.org/wikipedia/commons/e/e7/Simple_parallelogram.svg
@@ -37,7 +37,7 @@
         });
     });
 
-    it('can create an SVG string from a path', function(done){
+    it('can create an SVG string from a path', function(done) {
         LoadPathKit.then(() => {
             let cmds = [[PathKit.MOVE_VERB, 205, 5],
                        [PathKit.LINE_VERB, 795, 5],
@@ -56,7 +56,7 @@
         });
     });
 
-    it('can create an SVG string from hex values', function(done){
+    it('can create an SVG string from hex values', function(done) {
         LoadPathKit.then(() => {
             let cmds = [[PathKit.MOVE_VERB, "0x15e80300", "0x400004dc"], // 9.37088e-26f, 2.0003f
                        [PathKit.LINE_VERB, 795, 5],
@@ -74,7 +74,7 @@
         });
     });
 
-    it('should have input and the output be the same', function(done){
+    it('should have input and the output be the same', function(done) {
         LoadPathKit.then(() => {
             let testCases = [
                 'M0 0L1075 0L1075 242L0 242L0 0Z'
@@ -92,4 +92,20 @@
         });
     });
 
+    it('approximates arcs (conics) with quads', function(done) {
+        LoadPathKit.then(() => {
+            let path = PathKit.NewPath();
+            path.moveTo(20, 120);
+            path.arc(20, 120, 18, 0, 1.75 * Math.PI);
+            path.lineTo(20, 120);
+            let svgStr = path.toSVGString();
+            // Q stands for quad.  No need to check the whole path, as that's more
+            // what the gold correctness tests are for (can account for changes we make
+            // to the approximation algorithms).
+            expect(svgStr).toContain('Q');
+            path.delete();
+            done();
+        });
+    });
+
 });
diff --git a/src/core/SkFlattenable.cpp b/src/core/SkFlattenable.cpp
index c8766f8..3ccbadd 100644
--- a/src/core/SkFlattenable.cpp
+++ b/src/core/SkFlattenable.cpp
@@ -101,6 +101,11 @@
 #endif
 
 SkFlattenable::Factory SkFlattenable::NameToFactory(const char name[]) {
+#ifdef WEB_ASSEMBLY
+    // Should not be reachable by WebAssembly Code.
+    SkASSERT(false);
+    return nullptr;
+#else
     InitializeFlattenablesIfNeeded();
     SkASSERT(std::is_sorted(gEntries, gEntries + gCount, EntryComparator()));
 #ifdef SK_DEBUG
@@ -110,9 +115,15 @@
     if (pair.first == pair.second)
         return nullptr;
     return pair.first->fFactory;
+#endif
 }
 
 bool SkFlattenable::NameToType(const char name[], SkFlattenable::Type* type) {
+#ifdef WEB_ASSEMBLY
+    // Should not be reachable by WebAssembly Code.
+    SkASSERT(false);
+    return false;
+#else
     SkASSERT(type);
     InitializeFlattenablesIfNeeded();
     SkASSERT(std::is_sorted(gEntries, gEntries + gCount, EntryComparator()));
@@ -124,9 +135,15 @@
         return false;
     *type = pair.first->fType;
     return true;
+#endif
 }
 
 const char* SkFlattenable::FactoryToName(Factory fact) {
+#ifdef WEB_ASSEMBLY
+    // Should not be reachable by WebAssembly Code.
+    SkASSERT(false);
+    return nullptr;
+#else
     InitializeFlattenablesIfNeeded();
 #ifdef SK_DEBUG
     report_no_entries(__FUNCTION__);
@@ -138,6 +155,7 @@
         }
     }
     return nullptr;
+#endif
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/effects/SkDashPathEffect.cpp b/src/effects/SkDashPathEffect.cpp
index aa3803a..a243409 100644
--- a/src/effects/SkDashPathEffect.cpp
+++ b/src/effects/SkDashPathEffect.cpp
@@ -374,6 +374,11 @@
 }
 
 sk_sp<SkFlattenable> SkDashImpl::CreateProc(SkReadBuffer& buffer) {
+#ifdef WEB_ASSEMBLY
+    // Should not be reachable by WebAssembly Code.
+    SkASSERT(false);
+    return nullptr;
+#else
     const SkScalar phase = buffer.readScalar();
     uint32_t count = buffer.getArrayCount();
 
@@ -387,6 +392,7 @@
         return SkDashPathEffect::Make(intervals.get(), SkToInt(count), phase);
     }
     return nullptr;
+#endif
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/effects/SkTrimPathEffect.cpp b/src/effects/SkTrimPathEffect.cpp
index 8c3f56e..3a8d094 100644
--- a/src/effects/SkTrimPathEffect.cpp
+++ b/src/effects/SkTrimPathEffect.cpp
@@ -89,12 +89,18 @@
 }
 
 sk_sp<SkFlattenable> SkTrimPE::CreateProc(SkReadBuffer& buffer) {
+#ifdef WEB_ASSEMBLY
+    // Should not be reachable by WebAssembly Code.
+    SkASSERT(false);
+    return nullptr;
+#else
     const auto start = buffer.readScalar(),
                stop  = buffer.readScalar();
     const auto mode  = buffer.readUInt();
 
     return SkTrimPathEffect::Make(start, stop,
         (mode & 1) ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal);
+#endif
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////////////