[CanvasKit] Implement some basic Canvas/Surface things.


drawText is having issues in a release build.  Skottie sometimes
asserts in debug mode. This possibly has something to do with
memory alignment - like https://skia-review.googlesource.com/c/skia/+/155980
helped fix.

Patchset 9 shows off integrating Skia drawing to
an HTML canvas using Ganesh.

To see it locally, set up https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html
and then set $EMSDK to be that directory.  Then run

   make clean
   make local-example

and navigate to http://localhost:8000/skia-wasm/example.html

Patchset 20 shows off Skottie animating directly to a Canvas.

Docs-Preview: https://skia.org/?cl=153882
Bug: skia:
Change-Id: I2ad2f4ffac00925ee901982ccbaeb7aa63b1ea23
Reviewed-on: https://skia-review.googlesource.com/153882
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Kevin Lubick <kjlubick@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 67195fc..fe37aed 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -399,7 +399,8 @@
 }
 
 optional("fontmgr_custom") {
-  enabled = is_linux && skia_use_freetype && !skia_use_fontconfig
+  enabled = (is_linux || target_cpu == "wasm") && skia_use_freetype &&
+            !skia_use_fontconfig
 
   deps = [
     ":typeface_freetype",
@@ -929,7 +930,7 @@
     ]
   }
 
-  if (is_linux) {
+  if (is_linux || target_cpu == "wasm") {
     sources += [ "src/ports/SkDebug_stdio.cpp" ]
     if (skia_use_egl) {
       libs += [ "GLESv2" ]
diff --git a/experimental/canvaskit/Makefile b/experimental/canvaskit/Makefile
new file mode 100644
index 0000000..dc76224
--- /dev/null
+++ b/experimental/canvaskit/Makefile
@@ -0,0 +1,25 @@
+clean:
+	rm -rf ../../out/canvaskit_wasm
+	rm -rf ./canvas-kit/bin
+	$(MAKE) release
+
+release:
+	# Does an incremental build where possible.
+	./compile.sh
+	mkdir -p ./canvas-kit/bin
+	cp ../../out/canvaskit_wasm/skia.js   ./canvas-kit/bin
+	cp ../../out/canvaskit_wasm/skia.wasm ./canvas-kit/bin
+
+debug:
+	# Does an incremental build where possible.
+	./compile.sh debug
+	mkdir -p ./canvas-kit/bin
+	cp ../../out/canvaskit_wasm/skia.js   ./canvas-kit/bin
+	cp ../../out/canvaskit_wasm/skia.wasm ./canvas-kit/bin
+
+local-example:
+	rm -rf node_modules/canvas-kit
+	mkdir -p node_modules
+	ln -s -T ../canvas-kit  node_modules/canvas-kit
+	echo "Go check out http://localhost:8000/canvas-kit/example.html"
+	python serve.py
diff --git a/experimental/canvaskit/canvas-kit/.gitignore b/experimental/canvaskit/canvas-kit/.gitignore
new file mode 100644
index 0000000..6dd29b7
--- /dev/null
+++ b/experimental/canvaskit/canvas-kit/.gitignore
@@ -0,0 +1 @@
+bin/
\ No newline at end of file
diff --git a/experimental/canvaskit/canvas-kit/example.html b/experimental/canvaskit/canvas-kit/example.html
new file mode 100644
index 0000000..6b92186
--- /dev/null
+++ b/experimental/canvaskit/canvas-kit/example.html
@@ -0,0 +1,305 @@
+<!DOCTYPE html>
+<title>CanvasKit (Skia via Web Assembly)</title>
+<meta charset="utf-8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+<style>
+  svg, canvas {
+    border: 1px dashed #AAA;
+  }
+
+  #patheffect,#paths,#sk_drinks,#sk_party, #sk_legos {
+    width: 300px;
+    height: 300px;
+  }
+
+</style>
+
+<h2> CanvasKit draws Paths to WebGL</h2>
+<canvas id=patheffect width=300 height=300></canvas>
+<canvas id=paths width=200 height=200></canvas>
+<canvas id=ink width=300 height=300></canvas>
+
+<h2> Skottie </h2>
+<canvas id=sk_legos width=300 height=300></canvas>
+<canvas id=sk_drinks width=500 height=500></canvas>
+<canvas id=sk_party width=800 height=800></canvas>
+
+<!-- Doesn't work yet. -->
+<button id=lego_btn>Take a picture of the legos</button>
+
+<script type="text/javascript" src="/node_modules/canvas-kit/bin/skia.js"></script>
+
+<script type="text/javascript" charset="utf-8">
+
+  var CanvasKit = null;
+  var legoJSON = null;
+  var drinksJSON = null;
+  var confettiJSON = null;
+  CanvasKitInit({
+    locateFile: (file) => '/node_modules/canvas-kit/bin/'+file,
+  }).then((CK) => {
+    CK.initFonts();
+    CanvasKit = CK;
+    DrawingExample(CanvasKit);
+    PathExample(CanvasKit);
+    InkExample(CanvasKit);
+    // Set bounds to fix the 4:3 resolution of the legos
+    SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
+    SkottieExample(CanvasKit, 'sk_drinks', drinksJSON);
+    SkottieExample(CanvasKit, 'sk_party', confettiJSON);
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
+    resp.text().then((str) => {
+      legoJSON = str;
+      SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
+    });
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => {
+    resp.text().then((str) => {
+      drinksJSON = str;
+      SkottieExample(CanvasKit, 'sk_drinks', drinksJSON);
+    });
+  });
+
+  fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => {
+    resp.text().then((str) => {
+      confettiJSON = str;
+      SkottieExample(CanvasKit, 'sk_party', confettiJSON);
+    });
+  });
+
+  // crashes the lego drawing
+  const btn = document.getElementById('lego_btn');
+  btn.addEventListener('click', () => {
+    const surface = CanvasKit.getWebGLSurface('sk_legos');
+    if (!surface) {
+      console.log('Could not get lego surface');
+    }
+
+    const img = surface.makeImageSnapshot()
+    if (!img) { return }
+    const png = img.encodeToData()
+    if (!png) { return }
+    const pngBytes = CanvasKit.getSkDataBytes(png);
+    // See https://stackoverflow.com/a/12713326
+    let b64encoded = btoa(String.fromCharCode.apply(null, pngBytes));
+    console.log("base64 encoded image", b64encoded);
+  });
+
+  function DrawingExample(CanvasKit) {
+    const surface = CanvasKit.getWebGLSurface('patheffect');
+    if (!surface) {
+      console.log('Could not make surface');
+    }
+    const context = CanvasKit.currentContext();
+
+    const canvas = surface.getCanvas();
+
+    const paint = new CanvasKit.SkPaint();
+
+    const textPaint = new CanvasKit.SkPaint();
+    textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
+    textPaint.setTextSize(30);
+    textPaint.setAntiAlias(true);
+
+    let i = 0;
+
+    let X = 128;
+    let Y = 128;
+
+    function drawFrame() {
+      const path = starPath(CanvasKit, X, Y);
+      CanvasKit.setCurrentContext(context);
+      const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5);
+      i++;
+
+      paint.setPathEffect(dpe);
+      paint.setStyle(CanvasKit.PaintStyle.STROKE);
+      paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
+      paint.setAntiAlias(true);
+      paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
+
+      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
+
+      canvas.drawPath(path, paint);
+      canvas.drawText('Try mousing over!', 10, 280, textPaint);
+      canvas.flush();
+      dpe.delete();
+      path.delete();
+      window.requestAnimationFrame(drawFrame);
+    }
+    window.requestAnimationFrame(drawFrame);
+
+    // Make
+    document.getElementById('patheffect').addEventListener('mousemove', (e) => {
+      X = e.offsetX;
+      Y = e.offsetY;
+    });
+
+    // A client would need to delete this if it didn't go on for ever.
+    //paint.delete();
+  }
+
+  function PathExample(CanvasKit) {
+    const surface = CanvasKit.getWebGLSurface('paths');
+    if (!surface) {
+      console.log('Could not make surface');
+    }
+    const context = CanvasKit.currentContext();
+
+    const canvas = surface.getCanvas();
+
+    function drawFrame() {
+      CanvasKit.setCurrentContext(context);
+      const paint = new CanvasKit.SkPaint();
+      paint.setStrokeWidth(1.0);
+      paint.setAntiAlias(true);
+      paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+      paint.setStyle(CanvasKit.PaintStyle.STROKE);
+
+      const path = new CanvasKit.SkPath();
+      path.moveTo(20, 5);
+      path.lineTo(30, 20);
+      path.lineTo(40, 10);
+      path.lineTo(50, 20);
+      path.lineTo(60, 0);
+      path.lineTo(20, 5);
+
+      path.moveTo(20, 80);
+      path.cubicTo(90, 10, 160, 150, 190, 10);
+
+      path.moveTo(36, 148);
+      path.quadTo(66, 188, 120, 136);
+      path.lineTo(36, 148);
+
+      path.moveTo(150, 180);
+      path.arcTo(150, 100, 50, 200, 20);
+      path.lineTo(160, 160);
+
+      path.moveTo(20, 120);
+      path.lineTo(20, 120);
+
+      canvas.drawPath(path, paint);
+
+      canvas.flush();
+
+      path.delete();
+      paint.delete();
+      // Intentionally just draw frame once
+    }
+    window.requestAnimationFrame(drawFrame);
+  }
+
+
+  function InkExample(CanvasKit) {
+    const surface = CanvasKit.getWebGLSurface('ink');
+    if (!surface) {
+      console.log('Could not make surface');
+    }
+    const context = CanvasKit.currentContext();
+
+    const canvas = surface.getCanvas();
+
+    let paint = new CanvasKit.SkPaint();
+    paint.setAntiAlias(true);
+    paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+    paint.setStyle(CanvasKit.PaintStyle.STROKE);
+    paint.setStrokeWidth(4.0);
+    paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
+
+    let path = new CanvasKit.SkPath();
+    path.moveTo(30, 30);
+    path.lineTo(60, 80);
+
+    let paths = [path];
+    let paints = [paint];
+
+    function drawFrame() {
+      CanvasKit.setCurrentContext(context);
+
+      for (let i = 0; i < paints.length && i < paths.length; i++) {
+        canvas.drawPath(paths[i], paints[i]);
+      }
+      canvas.flush();
+
+      window.requestAnimationFrame(drawFrame);
+    }
+
+    let hold = false;
+    document.getElementById('ink').addEventListener('mousemove', (e) => {
+      if (!e.buttons) {
+        hold = false;
+        return;
+      }
+      if (hold) {
+        path.lineTo(e.offsetX, e.offsetY);
+      } else {
+        paint = paint.copy();
+        paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
+        paints.push(paint);
+        path = new CanvasKit.SkPath();
+        paths.push(path);
+        path.moveTo(e.offsetX, e.offsetY);
+      }
+      hold = true;
+    });
+    window.requestAnimationFrame(drawFrame);
+  }
+
+  function starPath(CanvasKit, X=128, Y=128, R=116) {
+    let p = new CanvasKit.SkPath();
+    p.moveTo(X + R, Y);
+    for (let i = 1; i < 8; i++) {
+      let a = 2.6927937 * i;
+      p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
+    }
+    return p;
+  }
+
+  function fps(frameTimes) {
+    let total = 0;
+    for (let ft of frameTimes) {
+      total += ft;
+    }
+    return frameTimes.length / total;
+  }
+
+  function SkottieExample(CanvasKit, id, jsonStr, bounds) {
+    if (!CanvasKit || !jsonStr) {
+      return;
+    }
+    const animation = CanvasKit.MakeAnimation(jsonStr);
+    const duration = animation.duration() * 1000;
+    const size = animation.size();
+    let c = document.getElementById(id);
+    bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
+
+    const surface = CanvasKit.getWebGLSurface(id);
+    if (!surface) {
+      console.log('Could not make surface');
+    }
+    const context = CanvasKit.currentContext();
+    const canvas = surface.getCanvas();
+
+    let firstFrame = new Date().getTime();
+
+    function drawFrame() {
+      let now = new Date().getTime();
+      let seek = ((now - firstFrame) / duration) % 1.0;
+      CanvasKit.setCurrentContext(context);
+      animation.seek(seek);
+
+      animation.render(canvas, bounds);
+      canvas.flush();
+      window.requestAnimationFrame(drawFrame);
+    }
+    window.requestAnimationFrame(drawFrame);
+
+    //animation.delete();
+  }
+
+</script>
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
new file mode 100644
index 0000000..203d41c
--- /dev/null
+++ b/experimental/canvaskit/canvaskit_bindings.cpp
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrBackendSurface.h"
+#include "GrContext.h"
+#include "GrGLInterface.h"
+#include "GrGLTypes.h"
+#include "SkCanvas.h"
+#include "SkCanvas.h"
+#include "SkDashPathEffect.h"
+#include "SkCornerPathEffect.h"
+#include "SkDiscretePathEffect.h"
+#include "SkFontMgr.h"
+#include "SkFontMgrPriv.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPathEffect.h"
+#include "SkScalar.h"
+#include "SkSurface.h"
+#include "SkSurfaceProps.h"
+#include "SkTestFontMgr.h"
+#include "Skottie.h"
+
+#include <iostream>
+#include <string>
+#include <GL/gl.h>
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include <emscripten/html5.h>
+
+using namespace emscripten;
+
+using JSColor = int32_t;
+
+
+void EMSCRIPTEN_KEEPALIVE initFonts() {
+    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
+}
+
+// Wraps the WebGL context in an SkSurface and returns it.
+sk_sp<SkSurface> getWebGLSurface(std::string id, int width, int height) {
+    // Context configurations
+    EmscriptenWebGLContextAttributes attrs;
+    emscripten_webgl_init_context_attributes(&attrs);
+    attrs.alpha = true;
+    attrs.premultipliedAlpha = true;
+    attrs.majorVersion = 1;
+    attrs.enableExtensionsByDefault = true;
+
+    EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(id.c_str(), &attrs);
+    if (context < 0) {
+        printf("failed to create webgl context %d\n", context);
+        return nullptr;
+    }
+    EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
+    if (r < 0) {
+        printf("failed to make webgl current %d\n", r);
+        return nullptr;
+    }
+
+    glClearColor(0, 0, 0, 0);
+    glClearStencil(0);
+    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+    // setup GrContext
+    auto interface = GrGLMakeNativeInterface();
+
+    // setup contexts
+    sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
+
+    // Wrap the frame buffer object attached to the screen in a Skia render target so Skia can
+    // render to it
+    GrGLint buffer;
+    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
+    GrGLFramebufferInfo info;
+    info.fFBOID = (GrGLuint) buffer;
+    SkColorType colorType;
+
+    info.fFormat = GL_RGBA8;
+    colorType = kRGBA_8888_SkColorType;
+
+    GrBackendRenderTarget target(width, height, 0, 8, info);
+
+    sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
+                                                                    kBottomLeft_GrSurfaceOrigin,
+                                                                    colorType, nullptr, nullptr));
+    return surface;
+}
+
+sk_sp<skottie::Animation> MakeAnimation(std::string json) {
+    return skottie::Animation::Make(json.c_str(), json.length());
+}
+
+//========================================================================================
+// Path things
+//========================================================================================
+
+// All these Apply* methods are simple wrappers to avoid returning an object.
+// The default WASM bindings produce code that will leak if a return value
+// isn't assigned to a JS variable and has delete() called on it.
+// These Apply methods, combined with the smarter binding code allow for chainable
+// commands that don't leak if the return value is ignored (i.e. when used intuitively).
+
+void ApplyAddPath(SkPath& orig, const SkPath& newPath,
+                   SkScalar scaleX, SkScalar skewX,  SkScalar transX,
+                   SkScalar skewY,  SkScalar scaleY, SkScalar transY,
+                   SkScalar pers0, SkScalar pers1, SkScalar pers2) {
+    SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
+                                   skewY , scaleY, transY,
+                                   pers0 , pers1 , pers2);
+    orig.addPath(newPath, m);
+}
+
+void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+                SkScalar radius) {
+    p.arcTo(x1, y1, x2, y2, radius);
+}
+
+void ApplyClose(SkPath& p) {
+    p.close();
+}
+
+void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+                  SkScalar w) {
+    p.conicTo(x1, y1, x2, y2, w);
+}
+
+void ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+                  SkScalar x3, SkScalar y3) {
+    p.cubicTo(x1, y1, x2, y2, x3, y3);
+}
+
+void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
+    p.lineTo(x, y);
+}
+
+void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
+    p.moveTo(x, y);
+}
+
+void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
+    p.quadTo(x1, y1, x2, y2);
+}
+
+void ApplyTransform(SkPath& orig,
+                    SkScalar scaleX, SkScalar skewX,  SkScalar transX,
+                    SkScalar skewY,  SkScalar scaleY, SkScalar transY,
+                    SkScalar pers0, SkScalar pers1, SkScalar pers2) {
+    SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
+                                   skewY , scaleY, transY,
+                                   pers0 , pers1 , pers2);
+    orig.transform(m);
+}
+
+SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
+    SkPath copy(a);
+    return copy;
+}
+
+bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
+    return a == b;
+}
+
+// to map from raw memory to a uint8array
+val getSkDataBytes(const SkData *data) {
+    return val(typed_memory_view(data->size(), data->bytes()));
+}
+
+// Hack to avoid embind creating a binding for SkData destructor
+namespace emscripten {
+    namespace internal {
+        template<typename ClassType>
+        void raw_destructor(ClassType *);
+
+        template<>
+        void raw_destructor<SkData>(SkData *ptr) {
+        }
+    }
+}
+
+// Some timesignatures below have uintptr_t instead of a pointer to a primative
+// type (e.g. SkScalar). This is necessary because we can't use "bind" (EMSCRIPTEN_BINDINGS)
+// and pointers to primitive types (Only bound types like SkPoint). We could if we used
+// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
+// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
+// SkPath or SkCanvas.
+//
+// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
+// 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.
+EMSCRIPTEN_BINDINGS(Skia) {
+    function("initFonts", &initFonts);
+    function("_getWebGLSurface", &getWebGLSurface, allow_raw_pointers());
+    function("MakeSkCornerPathEffect", &SkCornerPathEffect::Make, allow_raw_pointers());
+    function("MakeSkDiscretePathEffect", &SkDiscretePathEffect::Make, allow_raw_pointers());
+    // Won't be called directly, there's a JS helper to deal with typed arrays.
+    function("_MakeSkDashPathEffect", optional_override([](uintptr_t /* float* */ cptr, int count, SkScalar phase)->sk_sp<SkPathEffect> {
+        // See comment above for uintptr_t explanation
+        const float* intervals = reinterpret_cast<const float*>(cptr);
+        return SkDashPathEffect::Make(intervals, count, phase);
+    }), allow_raw_pointers());
+    function("getSkDataBytes", &getSkDataBytes, allow_raw_pointers());
+
+    class_<SkCanvas>("SkCanvas")
+        .constructor<>()
+        .function("clear", optional_override([](SkCanvas& self, JSColor color)->void {
+            // JS side gives us a signed int instead of an unsigned int for color
+            // Add a lambda to change it out.
+            self.clear(SkColor(color));
+        }))
+        .function("drawPaint", &SkCanvas::drawPaint)
+        .function("drawPath", &SkCanvas::drawPath)
+        .function("drawRect", &SkCanvas::drawRect)
+        .function("drawText", optional_override([](SkCanvas& self, std::string text, SkScalar x, SkScalar y, const SkPaint& p) {
+            return; // Currently broken, some memory things seem off.
+            //self.drawText(text.c_str(), text.length(), x, y, p);
+        }))
+        .function("flush", &SkCanvas::flush)
+        .function("save", &SkCanvas::save)
+        .function("translate", &SkCanvas::translate);
+
+    class_<SkData>("SkData")
+        .smart_ptr<sk_sp<SkData>>("sk_sp<SkData>>")
+        .function("size", &SkData::size);
+
+    class_<SkImage>("SkImage")
+        .smart_ptr<sk_sp<SkImage>>("sk_sp<SkImage>")
+        .function("encodeToData", select_overload<sk_sp<SkData>()const>(&SkImage::encodeToData));
+
+    class_<SkPaint>("SkPaint")
+        .constructor<>()
+        .function("copy", optional_override([](const SkPaint& self)->SkPaint {
+            SkPaint p(self);
+            return p;
+        }))
+        .function("setAntiAlias", &SkPaint::setAntiAlias)
+        .function("setColor", optional_override([](SkPaint& self, JSColor color)->void {
+            // JS side gives us a signed int instead of an unsigned int for color
+            // Add a lambda to change it out.
+            self.setColor(SkColor(color));
+        }))
+        .function("setPathEffect", &SkPaint::setPathEffect)
+        .function("setShader", &SkPaint::setShader)
+        .function("setStrokeWidth", &SkPaint::setStrokeWidth)
+        .function("setStyle", &SkPaint::setStyle)
+        .function("setTextSize", &SkPaint::setTextSize);
+
+    class_<SkPathEffect>("SkPathEffect")
+        .smart_ptr<sk_sp<SkPathEffect>>("sk_sp<SkPathEffect>");
+
+    //TODO make these chainable like PathKit
+    class_<SkPath>("SkPath")
+        .constructor<>()
+        .constructor<const SkPath&>()
+        // interface.js has 3 overloads of addPath
+        .function("_addPath", &ApplyAddPath)
+        .function("_arcTo", &ApplyArcTo)
+        .function("_close", &ApplyClose)
+        .function("_conicTo", &ApplyConicTo)
+        .function("_cubicTo", &ApplyCubicTo)
+        .function("_lineTo", &ApplyLineTo)
+        .function("_moveTo", &ApplyMoveTo)
+        .function("_quadTo", &ApplyQuadTo)
+        .function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
+
+        .function("setFillType", &SkPath::setFillType)
+        .function("getFillType", &SkPath::getFillType)
+        .function("getBounds", &SkPath::getBounds)
+        .function("computeTightBounds", &SkPath::computeTightBounds)
+        .function("equals", &Equals)
+        .function("copy", &CopyPath);
+
+    class_<SkSurface>("SkSurface")
+        .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
+        .function("width", &SkSurface::width)
+        .function("height", &SkSurface::height)
+        .function("makeImageSnapshot", &SkSurface::makeImageSnapshot)
+        .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers());
+
+
+    enum_<SkPaint::Style>("PaintStyle")
+        .value("FILL",              SkPaint::Style::kFill_Style)
+        .value("STROKE",            SkPaint::Style::kStroke_Style)
+        .value("STROKE_AND_FILL",   SkPaint::Style::kStrokeAndFill_Style);
+
+    enum_<SkPath::FillType>("FillType")
+        .value("WINDING",            SkPath::FillType::kWinding_FillType)
+        .value("EVENODD",            SkPath::FillType::kEvenOdd_FillType)
+        .value("INVERSE_WINDING",    SkPath::FillType::kInverseWinding_FillType)
+        .value("INVERSE_EVENODD",    SkPath::FillType::kInverseEvenOdd_FillType);
+
+    // A value object is much simpler than a class - it is returned as a JS
+    // object and does not require delete().
+    // https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
+    value_object<SkRect>("SkRect")
+        .field("fLeft",   &SkRect::fLeft)
+        .field("fTop",    &SkRect::fTop)
+        .field("fRight",  &SkRect::fRight)
+        .field("fBottom", &SkRect::fBottom);
+
+    // SkPoints can be represented by [x, y]
+    value_array<SkPoint>("SkPoint")
+        .element(&SkPoint::fX)
+        .element(&SkPoint::fY);
+
+    // {"w": Number, "h", Number}
+    value_object<SkSize>("SkSize")
+        .field("w",   &SkSize::fWidth)
+        .field("h",   &SkSize::fHeight);
+
+    value_object<SkISize>("SkISize")
+        .field("w",   &SkISize::fWidth)
+        .field("h",   &SkISize::fHeight);
+
+    // Animation things (may eventually go in own library)
+    class_<skottie::Animation>("Animation")
+        .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
+        .function("version", optional_override([](skottie::Animation& self)->std::string {
+            return std::string(self.version().c_str());
+        }))
+        .function("size", &skottie::Animation::size)
+        .function("duration", &skottie::Animation::duration)
+        .function("seek", &skottie::Animation::seek)
+        .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas)->void {
+            self.render(canvas, nullptr);
+        }), allow_raw_pointers())
+        .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas, const SkRect r)->void {
+            self.render(canvas, &r);
+        }), allow_raw_pointers());
+
+    function("MakeAnimation", &MakeAnimation);
+
+    function("currentContext", &emscripten_webgl_get_current_context);
+    function("setCurrentContext", &emscripten_webgl_make_context_current);
+}
diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh
new file mode 100755
index 0000000..0c45186
--- /dev/null
+++ b/experimental/canvaskit/compile.sh
@@ -0,0 +1,154 @@
+#!/bin/bash
+# Copyright 2018 Google LLC
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -ex
+
+BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
+# This expects the environment variable EMSDK to be set
+if [[ ! -d $EMSDK ]]; then
+  echo "Be sure to set the EMSDK environment variable."
+  exit 1
+fi
+
+BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm"}
+
+# Navigate to SKIA_HOME from where this file is located.
+pushd $BASE_DIR/../..
+
+source $EMSDK/emsdk_env.sh
+
+RELEASE_CONF="-Oz --closure 1 --llvm-lto 3"
+
+if [[ $@ == *debug* ]]; then
+  echo "Building a Debug build"
+  RELEASE_CONF="-O0 --js-opts 0 -s SAFE_HEAP=1 -s ASSERTIONS=1 -g3 -DPATHKIT_TESTING -DSK_DEBUG"
+fi
+
+
+echo "Compiling bitcode"
+
+EMCC=`which emcc`
+EMCXX=`which em++`
+
+# Inspired by https://github.com/Zubnix/skia-wasm-port/blob/master/build_bindings.sh
+./bin/gn gen ${BUILD_DIR} \
+  --args="cc=\"${EMCC}\" \
+  cxx=\"${EMCXX}\" \
+  extra_cflags_cc=[\"-frtti\",\"-s\",\"USE_FREETYPE=1\", \"-DIS_WEBGL=1\"] \
+  extra_cflags=[\"-s\",\"USE_FREETYPE=1\",\"-s\",\"USE_LIBPNG=1\", \"-DIS_WEBGL=1\"] \
+  is_debug=false \
+  is_official_build=true \
+  is_component_build=false \
+  target_cpu=\"wasm\" \
+  \
+  skia_use_egl=true \
+  skia_use_vulkan=false \
+  skia_use_libwebp=false \
+  skia_use_libpng=true \
+  skia_use_lua=false \
+  skia_use_dng_sdk=false \
+  skia_use_fontconfig=false \
+  skia_use_libjpeg_turbo=false \
+  skia_use_libheif=false \
+  skia_use_expat=false \
+  skia_use_vulkan=false \
+  skia_use_freetype=true \
+  skia_use_icu=false \
+  skia_use_expat=false \
+  skia_use_piex=false \
+  skia_use_zlib=true \
+  \
+  skia_enable_gpu=true \
+  skia_enable_fontmgr_empty=false \
+  skia_enable_pdf=false"
+
+ninja -C ${BUILD_DIR} libskia.a
+
+export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/externs.js "
+
+# inspired by https://github.com/Zubnix/skia-wasm-port/blob/master/build_skia_wasm_bitcode.sh
+echo "Compiling bindings"
+${EMCC} \
+    $RELEASE_CONF \
+    -std=c++11 \
+    -Iinclude/c \
+    -Iinclude/codec \
+    -Iinclude/config \
+    -Iinclude/core \
+    -Iinclude/effects \
+    -Iinclude/gpu \
+    -Iinclude/gpu/gl \
+    -Iinclude/pathops \
+    -Iinclude/private \
+    -Iinclude/utils/ \
+    -Imodules/skottie/include \
+    -Isrc/core/ \
+    -Itools/fonts \
+    $BASE_DIR/canvaskit_bindings.cpp \
+    -o $BUILD_DIR/canvaskit_bindings.bc
+
+echo "Generating final wasm"
+
+# Skottie doesn't end up in libskia and is currently not its own library
+# so we just hack in the .cpp files we need for now.
+${EMCC} \
+    $RELEASE_CONF \
+    -Iinclude/c \
+    -Iinclude/codec \
+    -Iinclude/config \
+    -Iinclude/core \
+    -Iinclude/effects \
+    -Iinclude/gpu \
+    -Iinclude/gpu/gl \
+    -Iinclude/pathops \
+    -Iinclude/private \
+    -Iinclude/utils/ \
+    -Imodules/skottie/include \
+    -Imodules/sksg/include \
+    -Isrc/core/ \
+    -Isrc/utils/ \
+    -Isrc/sfnt/ \
+    -Itools/fonts \
+    -Itools \
+    -lEGL \
+    -lGLESv2 \
+    -std=c++11 \
+    --bind \
+    --pre-js $BASE_DIR/helper.js \
+    --pre-js $BASE_DIR/interface.js \
+    -DSKOTTIE_HACK \
+    $BUILD_DIR/canvaskit_bindings.bc \
+    $BUILD_DIR/libskia.a \
+    modules/skottie/src/Skottie.cpp \
+    modules/skottie/src/SkottieAdapter.cpp \
+    modules/skottie/src/SkottieAnimator.cpp \
+    modules/skottie/src/SkottieJson.cpp \
+    modules/skottie/src/SkottieLayer.cpp \
+    modules/skottie/src/SkottieLayerEffect.cpp \
+    modules/skottie/src/SkottiePrecompLayer.cpp \
+    modules/skottie/src/SkottieShapeLayer.cpp \
+    modules/skottie/src/SkottieTextLayer.cpp \
+    modules/skottie/src/SkottieValue.cpp \
+    modules/sksg/src/*.cpp \
+    src/core/SkCubicMap.cpp \
+    src/core/SkTime.cpp \
+    src/pathops/SkOpBuilder.cpp \
+    src/pathops/SkPathOpsTypes.cpp \
+    tools/fonts/SkTestFontMgr.cpp \
+    tools/fonts/SkTestTypeface.cpp \
+    src/utils/SkJSON.cpp \
+    src/utils/SkParse.cpp \
+    -s ALLOW_MEMORY_GROWTH=1 \
+    -s TOTAL_MEMORY=64MB \
+    -s EXPORT_NAME="CanvasKitInit" \
+    -s FORCE_FILESYSTEM=0 \
+    -s MODULARIZE=1 \
+    -s NO_EXIT_RUNTIME=1 \
+    -s STRICT=1 \
+    -s USE_FREETYPE=1 \
+    -s USE_LIBPNG=1 \
+    -s WASM=1 \
+    -o $BUILD_DIR/skia.js
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
new file mode 100644
index 0000000..5d07f0d
--- /dev/null
+++ b/experimental/canvaskit/externs.js
@@ -0,0 +1,80 @@
+/*
+ * This externs file prevents the Closure JS compiler from minifying away
+ * names of objects created by Emscripten.
+ * Basically, by defining empty objects and functions here, Closure will
+ * know not to rename them.  This is needed because of our pre-js files,
+ * that is, the JS we hand-write to bundle into the output. That JS will be
+ * hit by the closure compiler and thus needs to know about what functions
+ * have special names and should not be minified.
+ *
+ * Emscripten does not support automatically generating an externs file, so we
+ * do it by hand. The general process is to write some JS code, and then put any
+ * calls to CanvasKit or related things in here. Running ./compile.sh and then
+ * looking at the minified results or running the Release trybot should
+ * verify nothing was missed. Optionally, looking directly at the minified
+ * pathkit.js can be useful when developing locally.
+ *
+ * Docs:
+ *   https://github.com/cljsjs/packages/wiki/Creating-Externs
+ *   https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System
+ *
+ * Example externs:
+ *   https://github.com/google/closure-compiler/tree/master/externs
+ */
+
+var CanvasKit = {
+	// public API (i.e. things we declare in the pre-js file)
+	Color: function(r, g, b, a) {},
+	currentContext: function() {},
+	getWebGLSurface: function(htmlID) {},
+	MakeSkDashPathEffect: function(intervals, phase) {},
+	setCurrentContext: function() {},
+	LTRBRect: function(l, t, r, b) {},
+
+	// private API (i.e. things declared in the bindings that we use
+	// in the pre-js file)
+	_getWebGLSurface: function(htmlID, w, h) {},
+	_malloc: function(size) {},
+	onRuntimeInitialized: function() {},
+	_MakeSkDashPathEffect: function(ptr, len, phase) {},
+
+	// Objects and properties on CanvasKit
+
+	HEAPF32: {}, // only needed for TypedArray mallocs
+
+	SkPath: {
+		// public API should go below because closure still will
+		// remove things declared here and not on the prototype.
+
+		// private API
+		_addPath: function(path, scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2) {},
+		_arcTo: function(x1, y1, x2, y2, radius) {},
+		_close: function() {},
+		_conicTo: function(x1, y1, x2, y2, w) {},
+		_cubicTo: function(cp1x, cp1y, cp2x, cp2y, x, y) {},
+		_lineTo: function(x1, y1) {},
+		_moveTo: function(x1, y1) {},
+		_op: function(otherPath, op) {},
+		_quadTo: function(cpx, cpy, x, y) {},
+		_rect: function(x, y, w, h) {},
+		_simplify: function() {},
+		_transform: function(scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2) {},
+	}
+}
+
+// Path public API
+CanvasKit.SkPath.prototype.addPath = function() {};
+CanvasKit.SkPath.prototype.arcTo = function(x1, y1, x2, y2, radius) {};
+CanvasKit.SkPath.prototype.close = function() {};
+CanvasKit.SkPath.prototype.conicTo = function(x1, y1, x2, y2, w) {};
+CanvasKit.SkPath.prototype.cubicTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {};
+CanvasKit.SkPath.prototype.lineTo = function(x, y) {};
+CanvasKit.SkPath.prototype.moveTo = function(x, y) {};
+CanvasKit.SkPath.prototype.op = function(otherPath, op) {};
+CanvasKit.SkPath.prototype.quadTo = function(x1, y1, x2, y2) {};
+CanvasKit.SkPath.prototype.rect = function(x, y, w, h) {};
+CanvasKit.SkPath.prototype.simplify = function() {};
+CanvasKit.SkPath.prototype.transform = function() {};
+
+// Not sure why this is needed - might be a bug in emsdk that this isn't properly declared.
+function loadWebAssemblyModule() {}
diff --git a/experimental/canvaskit/helper.js b/experimental/canvaskit/helper.js
new file mode 100644
index 0000000..6cc1564
--- /dev/null
+++ b/experimental/canvaskit/helper.js
@@ -0,0 +1,16 @@
+
+// Adds any extra JS functions/helpers we want to CanvasKit.
+// Wrapped in a function to avoid leaking global variables.
+(function(CanvasKit){
+
+  function clamp(c) {
+    return Math.round(Math.max(0, Math.min(c || 0, 255)));
+  }
+
+  // Colors are just a 32 bit number with 8 bits each of a, r, g, b
+  // The API is the same as CSS's representation of color rgba(), that is
+  // r,g,b are 0-255, and a is 0.0 to 1.0.
+  CanvasKit.Color = function(r, g, b, a) {
+    return (clamp(a*255) << 24) | (clamp(r) << 16) | (clamp(g) << 8) | (clamp(b) << 0);
+  }
+}(Module)); // When this file is loaded in, the high level object is "Module";
\ No newline at end of file
diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js
new file mode 100644
index 0000000..f0e5b0c
--- /dev/null
+++ b/experimental/canvaskit/interface.js
@@ -0,0 +1,147 @@
+// Adds JS functions to augment the CanvasKit interface.
+// For example, if there is a wrapper around the C++ call or logic to allow
+// chaining, it should go here.
+(function(CanvasKit){
+  // CanvasKit.onRuntimeInitialized is called after the WASM library has loaded.
+  // Anything that modifies an exposed class (e.g. SkPath) should be set
+  // after onRuntimeInitialized, otherwise, it can happen outside of that scope.
+  CanvasKit.onRuntimeInitialized = function() {
+    // All calls to 'this' need to go in externs.js so closure doesn't minify them away.
+    CanvasKit.SkPath.prototype.addPath = function() {
+      // Takes 1, 2, or 10 args, where the first arg is always the path.
+      // The options for the remaining args are:
+      //   - an array of 9 parameters
+      //   - the 9 parameters of a full Matrix
+      if (arguments.length === 1) {
+        // Add path, unchanged.  Use identify matrix
+        this._addPath(arguments[0], 1, 0, 0,
+                                    0, 1, 0,
+                                    0, 0, 1);
+      } else if (arguments.length === 2) {
+        // User provided the 9 params of a full matrix as an array.
+        var sm = arguments[1];
+        this._addPath(arguments[0], a[1], a[2], a[3],
+                                    a[4], a[5], a[6],
+                                    a[7], a[8], a[9]);
+      } else if (arguments.length === 10) {
+        // User provided the 9 params of a (full) matrix directly.
+        // These are in the same order as what Skia expects.
+        var a = arguments;
+        this._addPath(arguments[0], a[1], a[2], a[3],
+                                    a[4], a[5], a[6],
+                                    a[7], a[8], a[9]);
+      } else {
+        console.err('addPath expected to take 1, 2, or 10 args. Got ' + arguments.length);
+        return null;
+      }
+      return this;
+    };
+
+    CanvasKit.SkPath.prototype.arcTo = function(x1, y1, x2, y2, radius) {
+      this._arcTo(x1, y1, x2, y2, radius);
+      return this;
+    };
+
+    CanvasKit.SkPath.prototype.close = function() {
+      this._close();
+      return this;
+    };
+
+    CanvasKit.SkPath.prototype.conicTo = function(x1, y1, x2, y2, w) {
+      this._conicTo(x1, y1, x2, y2, w);
+      return this;
+    };
+
+    CanvasKit.SkPath.prototype.cubicTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
+      this._cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
+      return this;
+    };
+
+    CanvasKit.SkPath.prototype.lineTo = function(x, y) {
+      this._lineTo(x, y);
+      return this;
+    };
+
+    CanvasKit.SkPath.prototype.moveTo = function(x, y) {
+      this._moveTo(x, y);
+      return this;
+    };
+
+    CanvasKit.SkPath.prototype.op = function(otherPath, op) {
+      if (this._op(otherPath, op)) {
+        return this;
+      }
+      return null;
+    };
+
+    CanvasKit.SkPath.prototype.quadTo = function(cpx, cpy, x, y) {
+      this._quadTo(cpx, cpy, x, y);
+      return this;
+    };
+
+    CanvasKit.SkPath.prototype.simplify = function() {
+      if (this._simplify()) {
+        return this;
+      }
+      return null;
+    };
+
+    CanvasKit.SkPath.prototype.transform = function() {
+      // Takes 1 or 9 args
+      if (arguments.length === 1) {
+        // argument 1 should be a 9 element array.
+        var a = arguments[0];
+        this._transform(a[0], a[1], a[2],
+                        a[3], a[4], a[5],
+                        a[6], a[7], a[8]);
+      } else if (arguments.length === 9) {
+        // these arguments are the 9 members of the matrix
+        var a = arguments;
+        this._transform(a[0], a[1], a[2],
+                        a[3], a[4], a[5],
+                        a[6], a[7], a[8]);
+      } else {
+        console.err('transform expected to take 1 or 9 arguments. Got ' + arguments.length);
+        return null;
+      }
+      return this;
+    };
+  }
+
+  CanvasKit.getWebGLSurface = function(htmlID) {
+    var canvas = document.getElementById(htmlID);
+    if (!canvas) {
+      throw 'Canvas with id ' + htmlID + ' was not found';
+    }
+    // Maybe better to use clientWidth/height.  See:
+    // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
+    return this._getWebGLSurface(htmlID, canvas.width, canvas.height);
+  }
+
+  // Likely only used for tests.
+  CanvasKit.LTRBRect = function(l, t, r, b) {
+    return {
+      fLeft: l,
+      fTop: t,
+      fRight: r,
+      fBottom: b,
+    };
+  }
+
+  CanvasKit.MakeSkDashPathEffect = function(intervals, phase) {
+    if (!phase) {
+      phase = 0;
+    }
+    if (!intervals.length || intervals.length % 2 === 1) {
+      throw 'Intervals array must have even length';
+    }
+    if (!(intervals instanceof Float32Array)) {
+      intervals = Float32Array.from(intervals);
+    }
+    var BYTES_PER_ELEMENT = 4; // Float32Array always has 4 bytes per element
+    var ptr = CanvasKit._malloc(intervals.length * BYTES_PER_ELEMENT);
+    CanvasKit.HEAPF32.set(intervals, ptr / BYTES_PER_ELEMENT);
+    return CanvasKit._MakeSkDashPathEffect(ptr, intervals.length, phase);
+  }
+
+}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/canvaskit/serve.py b/experimental/canvaskit/serve.py
new file mode 100644
index 0000000..8c6541f
--- /dev/null
+++ b/experimental/canvaskit/serve.py
@@ -0,0 +1,20 @@
+# Copyright 2018 Google LLC
+
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import SimpleHTTPServer
+import SocketServer
+
+PORT = 8000
+
+class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+    pass
+
+Handler.extensions_map['.js'] = 'application/javascript'
+# Without the correct MIME type, async compilation doesn't work
+Handler.extensions_map['.wasm'] = 'application/wasm'
+
+httpd = SocketServer.TCPServer(("", PORT), Handler)
+
+httpd.serve_forever()
diff --git a/modules/pathkit/compile.sh b/modules/pathkit/compile.sh
index 25bc3d5..ea6f146 100755
--- a/modules/pathkit/compile.sh
+++ b/modules/pathkit/compile.sh
@@ -89,13 +89,13 @@
 -DWEB_ASSEMBLY=1 \
 -fno-rtti -fno-exceptions -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0 \
 $WASM_CONF \
--s MODULARIZE=1 \
--s EXPORT_NAME="PathKitInit" \
--s NO_EXIT_RUNTIME=1 \
--s ERROR_ON_UNDEFINED_SYMBOLS=1 \
--s ERROR_ON_MISSING_LIBRARIES=1 \
--s NO_FILESYSTEM=1 \
 -s BINARYEN_IGNORE_IMPLICIT_TRAPS=1 \
+-s ERROR_ON_MISSING_LIBRARIES=1 \
+-s ERROR_ON_UNDEFINED_SYMBOLS=1 \
+-s EXPORT_NAME="PathKitInit" \
+-s MODULARIZE=1 \
+-s NO_EXIT_RUNTIME=1 \
+-s NO_FILESYSTEM=1 \
 -s STRICT=1 \
 $OUTPUT \
 $BASE_DIR/pathkit_wasm_bindings.cpp \
diff --git a/modules/pathkit/npm-asmjs/example.html b/modules/pathkit/npm-asmjs/example.html
index f51c628..c50c453 100644
--- a/modules/pathkit/npm-asmjs/example.html
+++ b/modules/pathkit/npm-asmjs/example.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<title>PathKit (Skia + asm.js)</title>
+<title>PathKit (Skia's Geometry + asm.js)</title>
 <meta charset="utf-8" />
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
diff --git a/modules/pathkit/npm-wasm/example.html b/modules/pathkit/npm-wasm/example.html
index 09af83b..488f6ec 100644
--- a/modules/pathkit/npm-wasm/example.html
+++ b/modules/pathkit/npm-wasm/example.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<title>PathKit (Skia + WASM)</title>
+<title>PathKit (Skia's Geometry + WASM)</title>
 <meta charset="utf-8" />
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
diff --git a/modules/pathkit/pathkit_wasm_bindings.cpp b/modules/pathkit/pathkit_wasm_bindings.cpp
index 38af2c6..d69e6dd 100644
--- a/modules/pathkit/pathkit_wasm_bindings.cpp
+++ b/modules/pathkit/pathkit_wasm_bindings.cpp
@@ -5,19 +5,19 @@
  * found in the LICENSE file.
  */
 
+#include "SkCubicMap.h"
 #include "SkDashPathEffect.h"
 #include "SkFloatBits.h"
 #include "SkFloatingPoint.h"
 #include "SkMatrix.h"
 #include "SkPaint.h"
+#include "SkPaintDefaults.h"
 #include "SkParsePath.h"
-#include "SkStrokeRec.h"
 #include "SkPath.h"
 #include "SkPathOps.h"
-#include "SkCubicMap.h"
 #include "SkRect.h"
-#include "SkPaintDefaults.h"
 #include "SkString.h"
+#include "SkStrokeRec.h"
 #include "SkTrimPathEffect.h"
 
 #include <emscripten/emscripten.h>
diff --git a/site/user/modules/canvaskit.md b/site/user/modules/canvaskit.md
index 69658a6..60e53bf 100644
--- a/site/user/modules/canvaskit.md
+++ b/site/user/modules/canvaskit.md
@@ -8,7 +8,7 @@
 enabling fast-paced development on the web platform.
 It can also be used as a deployment mechanism for custom web apps requiring
 cutting-edge features, like Skia's [Lottie
-animation](https://skia.org/user/modules/skottie) support.  
+animation](https://skia.org/user/modules/skottie) support.
 
 
 Features
@@ -26,30 +26,28 @@
 <style>
   #demo canvas {
     border: 1px dashed #AAA;
+    margin: 2px;
   }
 
-  #patheffect {
-    width: 400px
+  #patheffect, #ink {
+    width: 400px;
     height: 400px;
   }
 
-  #sk_drinks,#sk_party {
+  #sk_legos, #sk_drinks,#sk_party {
     width: 300px;
     height: 300px;
   }
 
-  #sk_legos {
-    width: 300px;
-    height: 300px;
-  }
 </style>
 
 <div id=demo>
   <h3>An Interactive Path (try mousing over)</h3>
   <canvas id=patheffect width=400 height=400></canvas>
+  <canvas id=ink width=400 height=400></canvas>
 
   <h3>Skottie</h3>
-  <canvas id=sk_legos width=600 height=600></canvas>
+  <canvas id=sk_legos width=300 height=300></canvas>
   <canvas id=sk_drinks width=500 height=500></canvas>
   <canvas id=sk_party width=800 height=800></canvas>
 </div>
@@ -61,7 +59,7 @@
   var locate_file = '';
   if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') {
     console.log('WebAssembly is supported!');
-    locate_file = 'https://storage.googleapis.com/skia-cdn/canvaskit-wasm/0.0.1/bin/';
+    locate_file = 'https://storage.googleapis.com/skia-cdn/canvaskit-wasm/0.0.2/bin/';
   } else {
     console.log('WebAssembly is not supported (yet) on this browser.');
     document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>";
@@ -78,7 +76,9 @@
   }).then((CK) => {
     CanvasKit = CK;
     DrawingExample(CanvasKit);
-    SkottieExample(CanvasKit, 'sk_legos', legoJSON);
+    InkExample(CanvasKit);
+     // Set bounds to fix the 4:3 resolution of the legos
+    SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
     SkottieExample(CanvasKit, 'sk_drinks', drinksJSON);
     SkottieExample(CanvasKit, 'sk_party', confettiJSON);
   });
@@ -115,11 +115,6 @@
 
     const paint = new CanvasKit.SkPaint();
 
-    const textPaint = new CanvasKit.SkPaint();
-    textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
-    textPaint.setStrokeWidth(2);
-    textPaint.setTextSize(60);
-
     let i = 0;
 
     let X = 128;
@@ -140,8 +135,6 @@
       canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
 
       canvas.drawPath(path, paint);
-      // Currently does not work.
-      canvas.drawText('Some Text', 10, 10, textPaint);
       canvas.flush();
       dpe.delete();
       path.delete();
@@ -159,6 +152,62 @@
     //paint.delete();
   }
 
+  function InkExample(CanvasKit) {
+    const surface = CanvasKit.getWebGLSurface('ink');
+    if (!surface) {
+      console.log('Could not make surface');
+    }
+    const context = CanvasKit.currentContext();
+
+    const canvas = surface.getCanvas();
+
+    let paint = new CanvasKit.SkPaint();
+    paint.setAntiAlias(true);
+    paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+    paint.setStyle(CanvasKit.PaintStyle.STROKE);
+    paint.setStrokeWidth(4.0);
+    // This effect smooths out the drawn lines a bit.
+    paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
+
+    let path = new CanvasKit.SkPath();
+    path.moveTo(30, 30);
+    path.lineTo(60, 80);
+
+    let paths = [path];
+    let paints = [paint];
+
+    function drawFrame() {
+      CanvasKit.setCurrentContext(context);
+
+      for (let i = 0; i < paints.length && i < paths.length; i++) {
+        canvas.drawPath(paths[i], paints[i]);
+      }
+      canvas.flush();
+
+      window.requestAnimationFrame(drawFrame);
+    }
+
+    let hold = false;
+    document.getElementById('ink').addEventListener('mousemove', (e) => {
+      if (!e.buttons) {
+        hold = false;
+        return;
+      }
+      if (hold) {
+        path.lineTo(e.offsetX, e.offsetY);
+      } else {
+        paint = paint.copy();
+        paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
+        paints.push(paint);
+        path = new CanvasKit.SkPath();
+        paths.push(path);
+        path.moveTo(e.offsetX, e.offsetY);
+      }
+      hold = true;
+    });
+    window.requestAnimationFrame(drawFrame);
+  }
+
   function starPath(CanvasKit, X=128, Y=128, R=116) {
     let p = new CanvasKit.SkPath();
     p.moveTo(X + R, Y);
@@ -169,32 +218,36 @@
     return p;
   }
 
-  function SkottieExample(CanvasKit, id, jsonStr) {
+  function SkottieExample(CanvasKit, id, jsonStr, bounds) {
     if (!CanvasKit || !jsonStr) {
       return;
     }
+    const animation = CanvasKit.MakeAnimation(jsonStr);
+    const duration = animation.duration() * 1000;
+    const size = animation.size();
+    let c = document.getElementById(id);
+    bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
+
     const surface = CanvasKit.getWebGLSurface(id);
     if (!surface) {
       console.log('Could not make surface');
     }
     const context = CanvasKit.currentContext();
     const canvas = surface.getCanvas();
-    const animation = CanvasKit.MakeAnimation(jsonStr);
 
-    let i = 0;
+    let firstFrame = new Date().getTime();
+
     function drawFrame() {
+      let now = new Date().getTime();
+      let seek = ((now - firstFrame) / duration) % 1.0;
       CanvasKit.setCurrentContext(context);
-      animation.seek(i);
-      i += 1/300.0;
-      if (i > 1.0) {
-        i = 0;
-      }
-      animation.render(canvas);
+      animation.seek(seek);
+
+      animation.render(canvas, bounds);
       canvas.flush();
       window.requestAnimationFrame(drawFrame);
     }
     window.requestAnimationFrame(drawFrame);
-
     //animation.delete();
   }
   }
diff --git a/src/effects/SkDashPathEffect.cpp b/src/effects/SkDashPathEffect.cpp
index cc414f7..8c4abdd 100644
--- a/src/effects/SkDashPathEffect.cpp
+++ b/src/effects/SkDashPathEffect.cpp
@@ -375,7 +375,7 @@
 
 sk_sp<SkFlattenable> SkDashImpl::CreateProc(SkReadBuffer& buffer) {
 #ifdef WEB_ASSEMBLY
-    // Should not be reachable by WebAssembly Code.
+    // Should not be reachable by PathKit WebAssembly Code.
     SkASSERT(false);
     return nullptr;
 #else
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 2fa332f..5bc07e4 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -1407,7 +1407,12 @@
         // We require some form of FBO support and all GLs with FBO support can render to RGBA8
         fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= allRenderFlags;
     } else {
-        if (version >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8") ||
+        bool isWebGL = false;
+        // hack for skbug:8378
+        #if IS_WEBGL==1
+        isWebGL = true;
+        #endif
+        if (isWebGL || version >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8") ||
             ctxInfo.hasExtension("GL_ARM_rgba8")) {
             fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= allRenderFlags;
         }
diff --git a/src/pathops/SkPathOpsTypes.cpp b/src/pathops/SkPathOpsTypes.cpp
index df46c20..5ced583 100644
--- a/src/pathops/SkPathOpsTypes.cpp
+++ b/src/pathops/SkPathOpsTypes.cpp
@@ -103,6 +103,7 @@
     return aBits < bBits + epsilon;
 }
 
+#ifndef SKOTTIE_HACK
 // equality using the same error term as between
 bool AlmostBequalUlps(float a, float b) {
     const int UlpsEpsilon = 2;
@@ -227,6 +228,7 @@
     }
     return result;
 }
+#endif
 
 SkOpGlobalState::SkOpGlobalState(SkOpContourHead* head,
                                  SkArenaAlloc* allocator