[canvaskit] Expand canvas2d API

Made addPath take one more arg to allow for append/expand
(which makes emulating the HTML canvas easier).

Add Gold test for various lineTo/pathTo, etc.

Make CanvasKit.Color() choose a better value for alpha
when omitted (was 0, should be 1).

Add some parsing logic to deal with colors/font sizes.
Fonts are going to be rather complex it seems.

Moves some arc-related logic to the JS side, (although
this should preserve the behavior of CanvasKit.arc() to
behave like the Canvas implementation)

Make Examples and tests to a side-by-side comparison to
HTML canvas where applicable.

Add a Changelog for PathKit.  There was a bug (I thought), but
turns out I was wrong.  The Changelog will be for future
bug fixes.

Bug: skia:
Change-Id: I1bd603fdb518232604b098e24543e3453015b504
Reviewed-on: https://skia-review.googlesource.com/c/170446
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/experimental/canvaskit/.gitignore b/experimental/canvaskit/.gitignore
new file mode 100644
index 0000000..d8b83df
--- /dev/null
+++ b/experimental/canvaskit/.gitignore
@@ -0,0 +1 @@
+package-lock.json
diff --git a/experimental/canvaskit/canvaskit/example.html b/experimental/canvaskit/canvaskit/example.html
index 6173ff9..fc387ad 100644
--- a/experimental/canvaskit/canvaskit/example.html
+++ b/experimental/canvaskit/canvaskit/example.html
@@ -9,7 +9,8 @@
     border: 1px dashed #AAA;
   }
 
-  #patheffect,#paths,#sk_drinks,#sk_party, #sk_legos, #sk_onboarding {
+  #patheffect,#paths,#sk_drinks,#sk_party, #sk_legos, #sk_onboarding
+  #api1_c, #api2_c {
     width: 300px;
     height: 300px;
   }
@@ -38,7 +39,10 @@
 <canvas id=nima_example width=300 height=300></canvas>
 
 <h2>Drop in replacement for HTML Canvas (e.g. node.js)</h2>
-<img id=api1 width=300 height=300/>
+<img id=api1 width=300 height=300>
+<canvas id=api1_c width=300 height=300></canvas>
+<img id=api2 width=300 height=300>
+<canvas id=api2_c width=300 height=300></canvas>
 
 <script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
 
@@ -81,6 +85,7 @@
       NimaExample(CanvasKit, nimaFile, nimaTexture);
 
       CanvasAPI1(CanvasKit);
+      CanvasAPI2(CanvasKit);
 
       VertexAPI1(CanvasKit);
       VertexAPI2(CanvasKit, bonesImage);
@@ -424,26 +429,77 @@
 
   function CanvasAPI1(CanvasKit) {
     if (CanvasKit.gpu) {
+      console.warn("Skipping canvas example because its GPU build");
       return;
     }
-    let canvas = CanvasKit.MakeCanvas(300, 300);
+    let skcanvas = CanvasKit.MakeCanvas(300, 300);
+    let realCanvas = document.getElementById('api1_c');
 
-    let ctx = canvas.getContext('2d');
-    ctx.font = '30px Impact'
-    ctx.rotate(.1);
-    let text = ctx.measureText('Awesome');
-    ctx.fillText('Awesome ', 50, 100);
-    ctx.strokeText('Groovy!', 60+text.width, 100);
+    for (let canvas of [skcanvas, realCanvas]) {
+      let ctx = canvas.getContext('2d');
+      ctx.font = '30px Impact'
+      ctx.rotate(.1);
+      let text = ctx.measureText('Awesome');
+      ctx.fillText('Awesome ', 50, 100);
+      ctx.strokeText('Groovy!', 60+text.width, 100);
 
-    // Draw line under Awesome
-    ctx.strokeStyle = 'rgba(125,0,0,0.5)';
-    ctx.beginPath();
-    ctx.lineTo(50, 102);
-    ctx.lineTo(50 + text.width, 102);
-    ctx.stroke();
+      // Draw line under Awesome
+      ctx.strokeStyle = 'rgba(125,0,0,0.5)';
+      ctx.beginPath();
+      ctx.lineWidth=6;
+      ctx.lineTo(50, 105);
+      ctx.lineTo(50 + text.width, 105);
+      ctx.stroke();
+    }
 
     // TODO load image
-    document.getElementById('api1').src = canvas.toDataURL();
+    document.getElementById('api1').src = skcanvas.toDataURL();
+  }
+
+  function CanvasAPI2(CanvasKit) {
+    if (CanvasKit.gpu) {
+      console.warn("Skipping canvas example because its GPU build");
+      return;
+    }
+    let skcanvas = CanvasKit.MakeCanvas(200, 200);
+    let realCanvas = document.getElementById('api2_c');
+    realCanvas.width = 200;
+    realCanvas.height = 200;
+
+    for (let canvas of [skcanvas, realCanvas]) {
+      let ctx = canvas.getContext('2d');
+      ctx.moveTo(20, 5);
+      ctx.lineTo(30, 20);
+      ctx.lineTo(40, 10);
+      ctx.lineTo(50, 20);
+      ctx.lineTo(60, 0);
+      ctx.lineTo(20, 5);
+
+      ctx.moveTo(20, 80);
+      ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
+
+      ctx.moveTo(36, 148);
+      ctx.quadraticCurveTo(66, 188, 120, 136);
+      ctx.lineTo(36, 148);
+
+      ctx.rect(5, 170, 20, 25);
+
+      ctx.moveTo(150, 180);
+      ctx.arcTo(150, 100, 50, 200, 20);
+      ctx.lineTo(160, 160);
+
+      ctx.moveTo(20, 120);
+      ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
+      ctx.lineTo(20, 120);
+
+      ctx.moveTo(150, 5);
+      ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI);
+
+      ctx.lineWidth = 4/3;
+
+      ctx.stroke();
+    }
+    document.getElementById('api2').src = skcanvas.toDataURL();
   }
 
   function NimaExample(CanvasKit, nimaFile, nimaTexture) {
@@ -522,7 +578,6 @@
     shader.delete();
     paint.delete();
     surface.delete();
-
   }
 
   function VertexAPI2(CanvasKit, bonesImage) {
@@ -589,13 +644,7 @@
     //surface.delete();
 
   }
-  /**
-  SkColor colors[2] = {SK_ColorBLUE, SK_ColorYELLOW};
-    SkPaint paint;
-    paint.setShader(SkGradientShader::MakeRadial(
-                SkPoint::Make(128.0f, 128.0f), 180.0f,
-                colors, nullptr, 2, SkShader::kClamp_TileMode, 0, nullptr));
-    canvas->drawPaint(paint);*/
+
   function GradiantAPI1(CanvasKit) {
     const surface = CanvasKit.MakeCanvasSurface('gradient1');
     if (!surface) {
diff --git a/experimental/canvaskit/canvaskit/node.example.js b/experimental/canvaskit/canvaskit/node.example.js
index 55140e6..955b281 100644
--- a/experimental/canvaskit/canvaskit/node.example.js
+++ b/experimental/canvaskit/canvaskit/node.example.js
@@ -18,6 +18,7 @@
   // Draw line under Awesome
   ctx.strokeStyle = 'rgba(125,0,0,0.5)';
   ctx.beginPath();
+  ctx.lineWidth=6;
   ctx.lineTo(50, 102);
   ctx.lineTo(50 + text.width, 102);
   ctx.stroke();
@@ -36,7 +37,7 @@
   const paint = new CanvasKit.SkPaint();
 
   const textPaint = new CanvasKit.SkPaint();
-  textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
+  textPaint.setColor(CanvasKit.Color(40, 0, 0));
   textPaint.setTextSize(30);
   textPaint.setAntiAlias(true);
 
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
index 15d64bf..a7fb875 100644
--- a/experimental/canvaskit/canvaskit_bindings.cpp
+++ b/experimental/canvaskit/canvaskit_bindings.cpp
@@ -151,23 +151,27 @@
 // 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 ApplyAddArc(SkPath& orig, const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
+    orig.addArc(oval, startAngle, sweepAngle);
+}
+
 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) {
+                   SkScalar pers0, SkScalar pers1, SkScalar pers2,
+                   bool extendPath) {
     SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
                                    skewY , scaleY, transY,
                                    pers0 , pers1 , pers2);
-    orig.addPath(newPath, m);
+    orig.addPath(newPath, m, extendPath ? SkPath::kExtend_AddPathMode :
+                                          SkPath::kAppend_AddPathMode);
 }
 
-void ApplyAddArc(SkPath& path, SkScalar x, SkScalar y, SkScalar radius,
-              SkScalar startAngle, SkScalar endAngle, bool ccw) {
-    SkPath temp;
-    SkRect bounds = SkRect::MakeLTRB(x-radius, y-radius, x+radius, y+radius);
-    const auto sweep = SkRadiansToDegrees(endAngle - startAngle) - 360 * ccw;
-    temp.addArc(bounds, SkRadiansToDegrees(startAngle), sweep);
-    path.addPath(temp, SkPath::kExtend_AddPathMode);
+void ApplyAddRect(SkPath& path, SkScalar left, SkScalar top,
+                  SkScalar right, SkScalar bottom, bool ccw) {
+    path.addRect(left, top, right, bottom,
+                 ccw ? SkPath::Direction::kCW_Direction :
+                 SkPath::Direction::kCCW_Direction);
 }
 
 void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
@@ -495,9 +499,11 @@
     class_<SkPath>("SkPath")
         .constructor<>()
         .constructor<const SkPath&>()
+        .function("_addArc", &ApplyAddArc)
         // interface.js has 3 overloads of addPath
         .function("_addPath", &ApplyAddPath)
-        .function("_arc", &ApplyAddArc)
+        // interface.js has 4 overloads of addRect
+        .function("_addRect", &ApplyAddRect)
         .function("_arcTo", &ApplyArcTo)
         .function("_close", &ApplyClose)
         .function("_conicTo", &ApplyConicTo)
@@ -505,7 +511,7 @@
         .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("_transform", select_overload<void(SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
 
         // PathEffects
         .function("_dash", &ApplyDash)
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
index b1b8cc2..7c5159f 100644
--- a/experimental/canvaskit/externs.js
+++ b/experimental/canvaskit/externs.js
@@ -52,6 +52,10 @@
 	_getRasterN32PremulSurface: function() {},
 	_getWebGLSurface: function() {},
 
+	// The testing object is meant to expose internal functions
+	// for more fine-grained testing, e.g. parseColor
+	_testing: {},
+
 	// Objects and properties on CanvasKit
 
 	NimaActor: {
@@ -90,11 +94,17 @@
 		_encodeToDataWithFormat: function() {},
 	},
 
+	SkMatrix: {
+		rotated: function() {},
+	},
+
 	SkPath: {
 		// public API (from C++ bindings)
 
 		// private API
+		_addArc: function() {},
 		_addPath: function() {},
+		_addRect: function() {},
 		_arc: function() {},
 		_arcTo: function() {},
 		_close: function() {},
@@ -165,16 +175,16 @@
 	gpu: {},
 	skottie: {},
 	PaintStyle: {
-		FILL: {},
-		STROKE: {},
-		STROKE_AND_FILL: {},
+		Fill: {},
+		Stroke: {},
+		StrokeAndFill: {},
 	},
 
 	FillType: {
-		WINDING: {},
-		EVENODD: {},
-		INVERSE_WINDING: {},
-		INVERSE_EVENODD: {},
+		Winding: {},
+		EvenOdd: {},
+		InverseWinding: {},
+		InverseEvenOdd: {},
 	},
 
 	ImageFormat: {
@@ -213,7 +223,9 @@
 // Public API things that are newly declared in the JS should go here.
 // It's not enough to declare them above, because closure can still erase them
 // unless they go on the prototype.
+CanvasKit.SkPath.prototype.addArc = function() {};
 CanvasKit.SkPath.prototype.addPath = function() {};
+CanvasKit.SkPath.prototype.addRect = function() {};
 CanvasKit.SkPath.prototype.arc = function() {};
 CanvasKit.SkPath.prototype.arcTo = function() {};
 CanvasKit.SkPath.prototype.close = function() {};
diff --git a/experimental/canvaskit/helper.js b/experimental/canvaskit/helper.js
index 6cc1564..fa035ab 100644
--- a/experimental/canvaskit/helper.js
+++ b/experimental/canvaskit/helper.js
@@ -1,5 +1,5 @@
 
-// Adds any extra JS functions/helpers we want to CanvasKit.
+// Adds any extra JS functions/helpers we want to add to CanvasKit.
 // Wrapped in a function to avoid leaking global variables.
 (function(CanvasKit){
 
@@ -10,7 +10,11 @@
   // 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.
+  // if a is omitted, it will be assumed to be 1.0
   CanvasKit.Color = function(r, g, b, a) {
+    if (a === undefined) {
+        a = 1;
+    }
     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/htmlcanvas/_namedcolors.js b/experimental/canvaskit/htmlcanvas/_namedcolors.js
new file mode 100644
index 0000000..f7c9ac8
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/_namedcolors.js
@@ -0,0 +1,165 @@
+// This node script is meant to pre-compute the named colors.
+// node ./htmlcanvas/_namedcolors.js --expose-wasm
+// Put the result in canvas2d.js
+// This should likely never need to be re-run.
+
+const CanvasKitInit = require('../canvaskit/bin/canvaskit.js');
+
+CanvasKitInit({
+  locateFile: (file) => __dirname + '/../canvaskit/bin/'+file,
+}).then((CanvasKit) => {
+  let colorMap = {
+    // From https://drafts.csswg.org/css-color/#named-colors
+    'aliceblue': CanvasKit.Color(240, 248, 255),
+    'antiquewhite': CanvasKit.Color(250, 235, 215),
+    'aqua': CanvasKit.Color(0, 255, 255),
+    'aquamarine': CanvasKit.Color(127, 255, 212),
+    'azure': CanvasKit.Color(240, 255, 255),
+    'beige': CanvasKit.Color(245, 245, 220),
+    'bisque': CanvasKit.Color(255, 228, 196),
+    'black': CanvasKit.Color(0, 0, 0),
+    'blanchedalmond': CanvasKit.Color(255, 235, 205),
+    'blue': CanvasKit.Color(0, 0, 255),
+    'blueviolet': CanvasKit.Color(138, 43, 226),
+    'brown': CanvasKit.Color(165, 42, 42),
+    'burlywood': CanvasKit.Color(222, 184, 135),
+    'cadetblue': CanvasKit.Color(95, 158, 160),
+    'chartreuse': CanvasKit.Color(127, 255, 0),
+    'chocolate': CanvasKit.Color(210, 105, 30),
+    'coral': CanvasKit.Color(255, 127, 80),
+    'cornflowerblue': CanvasKit.Color(100, 149, 237),
+    'cornsilk': CanvasKit.Color(255, 248, 220),
+    'crimson': CanvasKit.Color(220, 20, 60),
+    'cyan': CanvasKit.Color(0, 255, 255),
+    'darkblue': CanvasKit.Color(0, 0, 139),
+    'darkcyan': CanvasKit.Color(0, 139, 139),
+    'darkgoldenrod': CanvasKit.Color(184, 134, 11),
+    'darkgray': CanvasKit.Color(169, 169, 169),
+    'darkgreen': CanvasKit.Color(0, 100, 0),
+    'darkgrey': CanvasKit.Color(169, 169, 169),
+    'darkkhaki': CanvasKit.Color(189, 183, 107),
+    'darkmagenta': CanvasKit.Color(139, 0, 139),
+    'darkolivegreen': CanvasKit.Color(85, 107, 47),
+    'darkorange': CanvasKit.Color(255, 140, 0),
+    'darkorchid': CanvasKit.Color(153, 50, 204),
+    'darkred': CanvasKit.Color(139, 0, 0),
+    'darksalmon': CanvasKit.Color(233, 150, 122),
+    'darkseagreen': CanvasKit.Color(143, 188, 143),
+    'darkslateblue': CanvasKit.Color(72, 61, 139),
+    'darkslategray': CanvasKit.Color(47, 79, 79),
+    'darkslategrey': CanvasKit.Color(47, 79, 79),
+    'darkturquoise': CanvasKit.Color(0, 206, 209),
+    'darkviolet': CanvasKit.Color(148, 0, 211),
+    'deeppink': CanvasKit.Color(255, 20, 147),
+    'deepskyblue': CanvasKit.Color(0, 191, 255),
+    'dimgray': CanvasKit.Color(105, 105, 105),
+    'dimgrey': CanvasKit.Color(105, 105, 105),
+    'dodgerblue': CanvasKit.Color(30, 144, 255),
+    'firebrick': CanvasKit.Color(178, 34, 34),
+    'floralwhite': CanvasKit.Color(255, 250, 240),
+    'forestgreen': CanvasKit.Color(34, 139, 34),
+    'fuchsia': CanvasKit.Color(255, 0, 255),
+    'gainsboro': CanvasKit.Color(220, 220, 220),
+    'ghostwhite': CanvasKit.Color(248, 248, 255),
+    'gold': CanvasKit.Color(255, 215, 0),
+    'goldenrod': CanvasKit.Color(218, 165, 32),
+    'gray': CanvasKit.Color(128, 128, 128),
+    'green': CanvasKit.Color(0, 128, 0),
+    'greenyellow': CanvasKit.Color(173, 255, 47),
+    'grey': CanvasKit.Color(128, 128, 128),
+    'honeydew': CanvasKit.Color(240, 255, 240),
+    'hotpink': CanvasKit.Color(255, 105, 180),
+    'indianred': CanvasKit.Color(205, 92, 92),
+    'indigo': CanvasKit.Color(75, 0, 130),
+    'ivory': CanvasKit.Color(255, 255, 240),
+    'khaki': CanvasKit.Color(240, 230, 140),
+    'lavender': CanvasKit.Color(230, 230, 250),
+    'lavenderblush': CanvasKit.Color(255, 240, 245),
+    'lawngreen': CanvasKit.Color(124, 252, 0),
+    'lemonchiffon': CanvasKit.Color(255, 250, 205),
+    'lightblue': CanvasKit.Color(173, 216, 230),
+    'lightcoral': CanvasKit.Color(240, 128, 128),
+    'lightcyan': CanvasKit.Color(224, 255, 255),
+    'lightgoldenrodyellow': CanvasKit.Color(250, 250, 210),
+    'lightgray': CanvasKit.Color(211, 211, 211),
+    'lightgreen': CanvasKit.Color(144, 238, 144),
+    'lightgrey': CanvasKit.Color(211, 211, 211),
+    'lightpink': CanvasKit.Color(255, 182, 193),
+    'lightsalmon': CanvasKit.Color(255, 160, 122),
+    'lightseagreen': CanvasKit.Color(32, 178, 170),
+    'lightskyblue': CanvasKit.Color(135, 206, 250),
+    'lightslategray': CanvasKit.Color(119, 136, 153),
+    'lightslategrey': CanvasKit.Color(119, 136, 153),
+    'lightsteelblue': CanvasKit.Color(176, 196, 222),
+    'lightyellow': CanvasKit.Color(255, 255, 224),
+    'lime': CanvasKit.Color(0, 255, 0),
+    'limegreen': CanvasKit.Color(50, 205, 50),
+    'linen': CanvasKit.Color(250, 240, 230),
+    'magenta': CanvasKit.Color(255, 0, 255),
+    'maroon': CanvasKit.Color(128, 0, 0),
+    'mediumaquamarine': CanvasKit.Color(102, 205, 170),
+    'mediumblue': CanvasKit.Color(0, 0, 205),
+    'mediumorchid': CanvasKit.Color(186, 85, 211),
+    'mediumpurple': CanvasKit.Color(147, 112, 219),
+    'mediumseagreen': CanvasKit.Color(60, 179, 113),
+    'mediumslateblue': CanvasKit.Color(123, 104, 238),
+    'mediumspringgreen': CanvasKit.Color(0, 250, 154),
+    'mediumturquoise': CanvasKit.Color(72, 209, 204),
+    'mediumvioletred': CanvasKit.Color(199, 21, 133),
+    'midnightblue': CanvasKit.Color(25, 25, 112),
+    'mintcream': CanvasKit.Color(245, 255, 250),
+    'mistyrose': CanvasKit.Color(255, 228, 225),
+    'moccasin': CanvasKit.Color(255, 228, 181),
+    'navajowhite': CanvasKit.Color(255, 222, 173),
+    'navy': CanvasKit.Color(0, 0, 128),
+    'oldlace': CanvasKit.Color(253, 245, 230),
+    'olive': CanvasKit.Color(128, 128, 0),
+    'olivedrab': CanvasKit.Color(107, 142, 35),
+    'orange': CanvasKit.Color(255, 165, 0),
+    'orangered': CanvasKit.Color(255, 69, 0),
+    'orchid': CanvasKit.Color(218, 112, 214),
+    'palegoldenrod': CanvasKit.Color(238, 232, 170),
+    'palegreen': CanvasKit.Color(152, 251, 152),
+    'paleturquoise': CanvasKit.Color(175, 238, 238),
+    'palevioletred': CanvasKit.Color(219, 112, 147),
+    'papayawhip': CanvasKit.Color(255, 239, 213),
+    'peachpuff': CanvasKit.Color(255, 218, 185),
+    'peru': CanvasKit.Color(205, 133, 63),
+    'pink': CanvasKit.Color(255, 192, 203),
+    'plum': CanvasKit.Color(221, 160, 221),
+    'powderblue': CanvasKit.Color(176, 224, 230),
+    'purple': CanvasKit.Color(128, 0, 128),
+    'rebeccapurple': CanvasKit.Color(102, 51, 153),
+    'red': CanvasKit.Color(255, 0, 0),
+    'rosybrown': CanvasKit.Color(188, 143, 143),
+    'royalblue': CanvasKit.Color(65, 105, 225),
+    'saddlebrown': CanvasKit.Color(139, 69, 19),
+    'salmon': CanvasKit.Color(250, 128, 114),
+    'sandybrown': CanvasKit.Color(244, 164, 96),
+    'seagreen': CanvasKit.Color(46, 139, 87),
+    'seashell': CanvasKit.Color(255, 245, 238),
+    'sienna': CanvasKit.Color(160, 82, 45),
+    'silver': CanvasKit.Color(192, 192, 192),
+    'skyblue': CanvasKit.Color(135, 206, 235),
+    'slateblue': CanvasKit.Color(106, 90, 205),
+    'slategray': CanvasKit.Color(112, 128, 144),
+    'slategrey': CanvasKit.Color(112, 128, 144),
+    'snow': CanvasKit.Color(255, 250, 250),
+    'springgreen': CanvasKit.Color(0, 255, 127),
+    'steelblue': CanvasKit.Color(70, 130, 180),
+    'tan': CanvasKit.Color(210, 180, 140),
+    'teal': CanvasKit.Color(0, 128, 128),
+    'thistle': CanvasKit.Color(216, 191, 216),
+    'transparent': CanvasKit.Color(0, 0, 0, 0),
+    'tomato': CanvasKit.Color(255, 99, 71),
+    'turquoise': CanvasKit.Color(64, 224, 208),
+    'violet': CanvasKit.Color(238, 130, 238),
+    'wheat': CanvasKit.Color(245, 222, 179),
+    'white': CanvasKit.Color(255, 255, 255),
+    'whitesmoke': CanvasKit.Color(245, 245, 245),
+    'yellow': CanvasKit.Color(255, 255, 0),
+    'yellowgreen': CanvasKit.Color(154, 205, 50)
+  };
+  console.log(JSON.stringify(colorMap));
+
+});
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/canvas2d.js b/experimental/canvaskit/htmlcanvas/canvas2d.js
index 0d13fb0..9a91fde 100644
--- a/experimental/canvaskit/htmlcanvas/canvas2d.js
+++ b/experimental/canvaskit/htmlcanvas/canvas2d.js
@@ -6,6 +6,17 @@
 
   var isNode = typeof btoa === undefined;
 
+  function argsAreFinite(args) {
+    for (var i = 0; i < args.length; i++) {
+      if (!Number.isFinite(args[0])) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  CanvasKit._testing = {};
+
   function HTMLCanvas(skSurface) {
     this._surface = skSurface;
     this._context = new CanvasRenderingContext2D(skSurface.getCanvas());
@@ -18,7 +29,9 @@
       return null;
     }
 
-    this.toDataURL = function() {
+    this.toDataURL = function(codec) {
+      // TODO(kjlubick): maybe support other codecs?
+      // For now, just to png
       this._surface.flush();
 
       var img = this._surface.makeImageSnapshot();
@@ -41,47 +54,125 @@
       }
       return 'data:image/png;base64,' + b64encoded;
     }
+
+    this.dispose = function() {
+      this._context._dispose();
+      this._surface.dispose();
+    }
   }
 
   function CanvasRenderingContext2D(skcanvas) {
     this._canvas = skcanvas;
     this._paint = new CanvasKit.SkPaint();
     this._paint.setAntiAlias(true);
-    this._currentPath = null;
+    this._paint.setStrokeWidth(2);
+    this._currentSubPath = null;
+    this._paths = [];
     this._pathStarted = false;
 
+    this._dispose = function() {
+      this._paths.forEach(function(path) {
+        path.delete();
+      });
+      this._paint.delete();
+      // Don't delete this._canvas as it will be disposed
+      // by the surface of which it is based.
+    }
+
     Object.defineProperty(this, 'font', {
       enumerable: true,
       set: function(newStyle) {
-        //TODO
-        this._paint.setTextSize(30);
+        var size = parseFontSize(newStyle);
+        // TODO styles
+        this._paint.setTextSize(size);
+      }
+    });
+
+    Object.defineProperty(this, 'lineWidth', {
+      enumerable: true,
+      set: function(newWidth) {
+        if (newWidth <= 0 || !newWidth) {
+          // Spec says to ignore NaN/Inf/0/negative values
+          return;
+        }
+        this._paint.setStrokeWidth(newWidth);
       }
     });
 
     Object.defineProperty(this, 'strokeStyle', {
       enumerable: true,
       set: function(newStyle) {
-        // TODO
+        this._paint.setColor(parseColor(newStyle));
       }
     });
 
-    this.beginPath = function() {
-      if (this._currentPath) {
-        this._currentPath.delete();
+    this.arc = function(x, y, radius, startAngle, endAngle, ccw) {
+      // As per  https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arc
+      // arc is essentially a simpler version of ellipse.
+      this.ellipse(x, y, radius, radius, 0, startAngle, endAngle, ccw);
+    }
+
+    this.arcTo = function(x1, y1, x2, y2, radius) {
+      if (!argsAreFinite(arguments)) {
+        return;
       }
-      this._currentPath = new CanvasKit.SkPath();
+      this._ensureSubpath(x1, y1);
+      if (radius < 0) {
+        throw 'radii cannot be negative';
+      }
+      this._currentSubPath.arcTo(x1, y1, x2, y2, radius);
+    }
+
+    this.beginPath = function() {
+      this._currentSubPath = new CanvasKit.SkPath();
+      this._paths.push(this._currentSubPath);
       this._pathStarted = false;
     }
 
+    this.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
+      if (!argsAreFinite(arguments)) {
+        return;
+      }
+      this._ensureSubpath(cp1x, cp1y);
+      this._currentSubPath.cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
+    }
+
+    this.closePath = function() {
+      if (this._currentSubPath) {
+        this._currentSubPath.close();
+        this._currentSubPath = null;
+        this._pathStarted = false;
+      }
+    }
+
+    this.ellipse = function(x, y, radiusX, radiusY, rotation,
+                            startAngle, endAngle, ccw) {
+      if (!argsAreFinite(arguments)) {
+        return;
+      }
+      if (radiusX < 0 || radiusY < 0) {
+        throw 'radii cannot be negative';
+      }
+      this._pathStarted = true;
+
+      var bounds = CanvasKit.LTRBRect(x-radiusX, y-radiusY, x+radiusX, y+radiusY);
+      var sweep = radiansToDegrees(endAngle - startAngle) - (360 * !!ccw);
+      var temp = new CanvasKit.SkPath();
+      temp.addArc(bounds, radiansToDegrees(startAngle), sweep);
+      var m = CanvasKit.SkMatrix.rotated(radiansToDegrees(rotation), x, y);
+      this._currentSubPath.addPath(temp, m, true);
+      temp.delete();
+    }
+
     // ensureSubpath makes SkPath behave like the browser's path object
-    // in that the first lineTo/cubicTo, etc, really acts like a moveTo.
+    // in that the first lineTo really acts like a moveTo.
     // ensureSubpath is the term used in the canvas spec:
     // https://html.spec.whatwg.org/multipage/canvas.html#ensure-there-is-a-subpath
     // ensureSubpath returns true if the drawing command can proceed,
-    // false otherwise (i.e. it was the first command and replaced
+    // false otherwise (i.e. it was the first command and may be replaced
     // with a moveTo).
     this._ensureSubpath = function(x, y) {
-      if (!this._currentPath) {
+      if (!this._currentSubPath) {
         this.beginPath();
       }
       if (!this._pathStarted) {
@@ -98,8 +189,13 @@
     }
 
     this.lineTo = function(x, y) {
+      if (!argsAreFinite(arguments)) {
+        return;
+      }
+      // lineTo is the odd-ball in the sense that a line-to without
+      // a previous subpath is the same as a moveTo.
       if (this._ensureSubpath(x, y)) {
-        this._currentPath.lineTo(x, y);
+        this._currentSubPath.lineTo(x, y);
       }
     }
 
@@ -111,9 +207,31 @@
     }
 
     this.moveTo = function(x, y) {
-      if (this._ensureSubpath(x, y)) {
-        this._currentPath.moveTo(x, y);
+      if (!argsAreFinite(arguments)) {
+        return;
       }
+      if (this._ensureSubpath(x, y)) {
+        this._currentSubPath.moveTo(x, y);
+      }
+    }
+
+    this.quadraticCurveTo = function(cpx, cpy, x, y) {
+      if (!argsAreFinite(arguments)) {
+        return;
+      }
+      this._ensureSubpath(cpx, cpy);
+      this._currentSubPath.quadTo(cpx, cpy, x, y);
+    }
+
+    this.rect = function(x, y, width, height) {
+      if (!argsAreFinite(arguments)) {
+        return;
+      }
+      // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rect
+      this.beginPath();
+      this._currentSubPath.addRect(x, y, x+width, y+height);
+      this.beginPath();
+      this._currentSubPath.moveTo(x, y);
     }
 
     this.resetTransform = function() {
@@ -129,6 +247,10 @@
       this._canvas.scale(sx, sy);
     }
 
+    this.scale = function(sx, sy) {
+      this._canvas.scale(sx, sy);
+    }
+
     this.setTransform = function(a, b, c, d, e, f) {
       this._canvas.setMatrix([a, c, e,
                               b, d, f,
@@ -140,9 +262,12 @@
     }
 
     this.stroke = function() {
-      if (this._currentPath) {
+      if (this._currentSubPath) {
         this._paint.setStyle(CanvasKit.PaintStyle.Stroke);
-        this._canvas.drawPath(this._currentPath, this._paint);
+        this._paint.setLine
+        for (var i = 0; i < this._paths.length; i++) {
+          this._canvas.drawPath(this._paths[i], this._paint);
+        }
       }
     }
 
@@ -155,10 +280,23 @@
     this.translate = function(dx, dy) {
       this._canvas.translate(dx, dy);
     }
+
+
+    // Not supported operations (e.g. for Web only)
+    this.addHitRegion = function() {};
+    this.clearHitRegions = function() {};
+    this.drawFocusIfNeeded = function() {};
+    this.removeHitRegion = function() {};
+    this.scrollPathIntoView = function() {};
+
+    Object.defineProperty(this, 'canvas', {
+      value: null,
+      writable: false
+    });
   }
 
   CanvasKit.MakeCanvas = function(width, height) {
-    // TODO do fonts the "correct" way
+    // TODO(kjlubick) do fonts the "correct" way
     CanvasKit.initFonts();
     var surf = CanvasKit.MakeSurface(width, height);
     if (surf) {
@@ -167,4 +305,114 @@
     return null;
   }
 
+  var units = 'px|pt|pc|in|cm|mm|%|em|ex|ch|rem|q';
+  var fontSizeRegex = new RegExp('([\\d\\.]+)(' + units + ')');
+  var defaultHeight = 12;
+  // Based off of node-canvas's parseFont
+  // returns font size in *points* (original impl was in px);
+  function parseFontSize(fontStr) {
+    // This is naive and doesn't account for line-height yet
+    // (but neither does node-canvas's?)
+    var fontSize = fontSizeRegex.exec(fontStr);
+    if (!fontSize) {
+      console.error('Could not parse font size', fontStr);
+      return 16;
+    }
+    var size = fontSize[1];
+    var unit = fontSize[2];
+    switch (unit) {
+      case 'pt':
+        return size;
+      case 'px':
+        return size * 3/4;
+      case 'pc':
+        return size * 12;
+      case 'in':
+        return size * 72;
+      case 'cm':
+        return size * 72.0 / 2.54;
+      case 'mm':
+        return size * (72.0 / 25.4);
+      case '%':
+        return size * (defaultHeight / 100);
+      case 'em':
+      case 'rem':
+        return size * defaultHeight;
+      case 'q':
+        return size * (96 / 25.4 / 3);
+    }
+  }
+
+  function valueOrPercent(aStr) {
+    var a = parseFloat(aStr) || 1;
+    if (aStr && aStr.indexOf('%') !== -1) {
+      return a / 100;
+    }
+    return a;
+  }
+
+  function parseColor(colorStr) {
+    colorStr = colorStr.toLowerCase();
+    // See https://drafts.csswg.org/css-color/#typedef-hex-color
+    if (colorStr.startsWith('#')) {
+      var r, g, b, a = 255;
+      switch (colorStr.length) {
+        case 9: // 8 hex chars #RRGGBBAA
+          a = parseInt(colorStr.slice(7, 9), 16);
+        case 7: // 6 hex chars #RRGGBB
+          r = parseInt(colorStr.slice(1, 3), 16);
+          g = parseInt(colorStr.slice(3, 5), 16);
+          b = parseInt(colorStr.slice(5, 7), 16);
+          break;
+        case 5: // 4 hex chars #RGBA
+          // multiplying by 17 is the same effect as
+          // appending another character of the same value
+          // e.g. e => ee == 14 => 238
+          a = parseInt(colorStr.slice(4, 5), 16) * 17;
+        case 4: // 6 hex chars #RGB
+          r = parseInt(colorStr.slice(1, 2), 16) * 17;
+          g = parseInt(colorStr.slice(2, 3), 16) * 17;
+          b = parseInt(colorStr.slice(3, 4), 16) * 17;
+          break;
+      }
+      return CanvasKit.Color(r, g, b, a/255);
+
+    } else if (colorStr.startsWith('rgba')) {
+      // Trim off rgba( and the closing )
+      colorStr = colorStr.slice(5, -1);
+      var nums = colorStr.split(',');
+      return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
+                             valueOrPercent(nums[3]));
+    } else if (colorStr.startsWith('rgb')) {
+      // Trim off rgba( and the closing )
+      colorStr = colorStr.slice(4, -1);
+      var nums = colorStr.split(',');
+      // rgb can take 3 or 4 arguments
+      return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
+                             valueOrPercent(nums[3]));
+    } else if (colorStr.startsWith('gray(')) {
+      // TODO
+    } else if (colorStr.startsWith('hsl')) {
+      // TODO
+    } else {
+      // Try for named color
+      var nc = colorMap[colorStr];
+      if (nc !== undefined) {
+        return nc;
+      }
+    }
+    console.error('unrecognized color ', colorStr);
+    return CanvasKit.BLACK;
+  }
+
+  CanvasKit._testing['parseColor'] = parseColor;
+
+  // Create the following with
+  // node ./htmlcanvas/_namedcolors.js --expose-wasm
+  // JS/closure doesn't have a constexpr like thing which
+  // would really help here. Since we don't, we pre-compute
+  // the map, which saves (a tiny amount of) startup time
+  // and (a small amount of) code size.
+  var colorMap = {"aliceblue":-984833,"antiquewhite":-332841,"aqua":-16711681,"aquamarine":-8388652,"azure":-983041,"beige":-657956,"bisque":-6972,"black":-16777216,"blanchedalmond":-5171,"blue":-16776961,"blueviolet":-7722014,"brown":-5952982,"burlywood":-2180985,"cadetblue":-10510688,"chartreuse":-8388864,"chocolate":-2987746,"coral":-32944,"cornflowerblue":-10185235,"cornsilk":-1828,"crimson":-2354116,"cyan":-16711681,"darkblue":-16777077,"darkcyan":-16741493,"darkgoldenrod":-4684277,"darkgray":-5658199,"darkgreen":-16751616,"darkgrey":-5658199,"darkkhaki":-4343957,"darkmagenta":-7667573,"darkolivegreen":-11179217,"darkorange":-29696,"darkorchid":-6737204,"darkred":-7667712,"darksalmon":-1468806,"darkseagreen":-7357297,"darkslateblue":-12042869,"darkslategray":-13676721,"darkslategrey":-13676721,"darkturquoise":-16724271,"darkviolet":-7077677,"deeppink":-60269,"deepskyblue":-16728065,"dimgray":-9868951,"dimgrey":-9868951,"dodgerblue":-14774017,"firebrick":-5103070,"floralwhite":-1296,"forestgreen":-14513374,"fuchsia":-65281,"gainsboro":-2302756,"ghostwhite":-460545,"gold":-10496,"goldenrod":-2448096,"gray":-8355712,"green":-16744448,"greenyellow":-5374161,"grey":-8355712,"honeydew":-983056,"hotpink":-38476,"indianred":-3318692,"indigo":-11861886,"ivory":-16,"khaki":-989556,"lavender":-1644806,"lavenderblush":-3851,"lawngreen":-8586240,"lemonchiffon":-1331,"lightblue":-5383962,"lightcoral":-1015680,"lightcyan":-2031617,"lightgoldenrodyellow":-329006,"lightgray":-2894893,"lightgreen":-7278960,"lightgrey":-2894893,"lightpink":-18751,"lightsalmon":-24454,"lightseagreen":-14634326,"lightskyblue":-7876870,"lightslategray":-8943463,"lightslategrey":-8943463,"lightsteelblue":-5192482,"lightyellow":-32,"lime":-16711936,"limegreen":-13447886,"linen":-331546,"magenta":-65281,"maroon":-8388608,"mediumaquamarine":-10039894,"mediumblue":-16777011,"mediumorchid":-4565549,"mediumpurple":-7114533,"mediumseagreen":-12799119,"mediumslateblue":-8689426,"mediumspringgreen":-16713062,"mediumturquoise":-12004916,"mediumvioletred":-3730043,"midnightblue":-15132304,"mintcream":-655366,"mistyrose":-6943,"moccasin":-6987,"navajowhite":-8531,"navy":-16777088,"oldlace":-133658,"olive":-8355840,"olivedrab":-9728477,"orange":-23296,"orangered":-47872,"orchid":-2461482,"palegoldenrod":-1120086,"palegreen":-6751336,"paleturquoise":-5247250,"palevioletred":-2396013,"papayawhip":-4139,"peachpuff":-9543,"peru":-3308225,"pink":-16181,"plum":-2252579,"powderblue":-5185306,"purple":-8388480,"rebeccapurple":-10079335,"red":-65536,"rosybrown":-4419697,"royalblue":-12490271,"saddlebrown":-7650029,"salmon":-360334,"sandybrown":-744352,"seagreen":-13726889,"seashell":-2578,"sienna":-6270419,"silver":-4144960,"skyblue":-7876885,"slateblue":-9807155,"slategray":-9404272,"slategrey":-9404272,"snow":-1286,"springgreen":-16711809,"steelblue":-12156236,"tan":-2968436,"teal":-16744320,"thistle":-2572328,"transparent":0,"tomato":-40121,"turquoise":-12525360,"violet":-1146130,"wheat":-663885,"white":-1,"whitesmoke":-657931,"yellow":-256,"yellowgreen":-6632142};
+
 }(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js
index b671369..9106ae7 100644
--- a/experimental/canvaskit/interface.js
+++ b/experimental/canvaskit/interface.js
@@ -7,41 +7,114 @@
   // 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.
+
+
+    // Add some helpers for matrices. This is ported from SkMatrix.cpp
+    // to save complexity and overhead of going back and forth between
+    // C++ and JS layers.
+    CanvasKit.SkMatrix = {};
+    function sdot(a, b, c, d, e, f) {
+      e = e || 0;
+      f = f || 0;
+      return a * b + c * d + e * f;
+    }
+
+    // Return a matrix representing a rotation by n degrees.
+    // px, py optionally say which point the rotation should be around
+    // with the default being (0, 0);
+    CanvasKit.SkMatrix.rotated = function(degrees, px, py) {
+      px = px || 0;
+      py = py || 0;
+      var rad = degreesToRadians(degrees);
+      var sinV = Math.sin(rad);
+      var cosV = Math.cos(rad);
+      return [
+        cosV, -sinV, sdot( sinV, py, 1 - cosV, px),
+        sinV,  cosV, sdot(-sinV, px, 1 - cosV, py),
+        0,        0,                             1,
+      ];
+    };
+    // TODO(kjlubick): translated, scaled
+
+
+    CanvasKit.SkPath.prototype.addArc = function(oval, startAngle, sweepAngle) {
+      // see arc() for the HTMLCanvas version
+      // note input angles are degrees.
+      this._addArc(oval, startAngle, sweepAngle);
+      return this;
+    };
+
     CanvasKit.SkPath.prototype.addPath = function() {
-      // Takes 1, 2, or 10 args, where the first arg is always the path.
+      // Takes 1, 2, 7, or 10 required args, where the first arg is always the path.
+      // The last arg is optional and chooses between add or extend mode.
       // The options for the remaining args are:
-      //   - an array of 9 parameters
-      //   - the 9 parameters of a full matrix
-      //     an array of 6 parameters (omitting perspective)
+      //   - an array of 6 or 9 parameters (perspective is optional)
+      //   - the 9 parameters of a full matrix or
       //     the 6 non-perspective params of a 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) {
+      var args = Array.prototype.slice.call(arguments);
+      var path = args[0];
+      var extend = false;
+      if (typeof args[args.length-1] === "boolean") {
+        extend = args.pop();
+      }
+      if (args.length === 1) {
+        // Add path, unchanged.  Use identity matrix
+        this._addPath(path, 1, 0, 0,
+                            0, 1, 0,
+                            0, 0, 1,
+                            extend);
+      } else if (args.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] || 0, a[8] || 0, a[9] || 1);
-      } else if (arguments.length === 7 || arguments.length === 10) {
+        var a = args[1];
+        this._addPath(path, a[0],      a[1],      a[2],
+                            a[3],      a[4],      a[5],
+                            a[6] || 0, a[7] || 0, a[8] || 1,
+                            extend);
+      } else if (args.length === 7 || args.length === 10) {
         // User provided the 9 params of a (full) matrix directly.
         // (or just the 6 non perspective ones)
         // 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] || 0, a[8] || 0, a[9] || 1);
+        var a = args;
+        this._addPath(path, a[1],      a[2],      a[3],
+                            a[4],      a[5],      a[6],
+                            a[7] || 0, a[8] || 0, a[9] || 1,
+                            extend);
       } else {
-        console.err('addPath expected to take 1, 2, 7, or 10 args. Got ' + arguments.length);
+        console.error('addPath expected to take 1, 2, 7, or 10 required args. Got ' + args.length);
+        return null;
+      }
+      return this;
+    };
+
+    CanvasKit.SkPath.prototype.addRect = function() {
+      // Takes 1, 2, 4 or 5 args
+      //  - SkRect
+      //  - SkRect, isCCW
+      //  - left, top, right, bottom
+      //  - left, top, right, bottom, isCCW
+      if (arguments.length === 1 || arguments.length === 2) {
+        var r = arguments[0];
+        var ccw = arguments[1] || false;
+        this._addRect(r.fLeft, r.fTop, r.fRight, r.fBottom, ccw);
+      } else if (arguments.length === 4 || arguments.length === 5) {
+        var a = arguments;
+        this._addRect(a[0], a[1], a[2], a[3], a[4] || false);
+      } else {
+        console.error('addRect expected to take 1, 2, 4, or 5 args. Got ' + arguments.length);
         return null;
       }
       return this;
     };
 
     CanvasKit.SkPath.prototype.arc = function(x, y, radius, startAngle, endAngle, ccw) {
-      this._arc(x, y, radius, startAngle, endAngle, !!ccw);
+      // emulates the HTMLCanvas behavior.  See addArc() for the SkPath version.
+      // Note input angles are radians.
+      var bounds = CanvasKit.LTRBRect(x-radius, y-radius, x+radius, y+radius);
+      var sweep = radiansToDegrees(endAngle - startAngle) - (360 * !!ccw);
+      var temp = new CanvasKit.SkPath();
+      temp.addArc(bounds, radiansToDegrees(startAngle), sweep);
+      this.addPath(temp, true);
+      temp.delete();
       return this;
     };
 
@@ -179,7 +252,6 @@
     }
   } // end CanvasKit.onRuntimeInitialized, that is, anything changing prototypes or dynamic.
 
-  // Likely only used for tests.
   CanvasKit.LTRBRect = function(l, t, r, b) {
     return {
       fLeft: l,
@@ -356,3 +428,14 @@
   }
 
 }(Module)); // When this file is loaded in, the high level object is "Module";
+
+// Intentionally added outside the scope to allow usage in canvas2d.js and other
+// pre-js files. These names are unlikely to cause emscripten collisions.
+function radiansToDegrees(rad) {
+  return (rad / Math.PI) * 180;
+}
+
+function degreesToRadians(deg) {
+  return (deg / 180) * Math.PI;
+}
+
diff --git a/experimental/canvaskit/tests/canvas2d.spec.js b/experimental/canvaskit/tests/canvas2d.spec.js
new file mode 100644
index 0000000..c763362
--- /dev/null
+++ b/experimental/canvaskit/tests/canvas2d.spec.js
@@ -0,0 +1,154 @@
+// The increased timeout is especially needed with larger binaries
+// like in the debug/gpu build
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
+
+describe('CanvasKit\'s Canvas 2d Behavior', function() {
+    // Note, don't try to print the CanvasKit object - it can cause Karma/Jasmine to lock up.
+    var CanvasKit = null;
+    const LoadCanvasKit = new Promise(function(resolve, reject) {
+        if (CanvasKit) {
+            resolve();
+        } else {
+            CanvasKitInit({
+                locateFile: (file) => '/canvaskit/'+file,
+            }).then((_CanvasKit) => {
+                CanvasKit = _CanvasKit;
+                CanvasKit.initFonts();
+                resolve();
+            });
+        }
+    });
+
+    let container = document.createElement('div');
+    document.body.appendChild(container);
+    const CANVAS_WIDTH = 600;
+    const CANVAS_HEIGHT = 600;
+
+    beforeEach(function() {
+        container.innerHTML = `
+            <canvas width=600 height=600 id=test></canvas>`;
+    });
+
+    afterEach(function() {
+        container.innerHTML = '';
+    });
+
+    describe('color string parsing', function() {
+        function hex(s) {
+            return parseInt(s, 16);
+        }
+
+        it('parses hex color strings', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                let parseColor = CanvasKit._testing.parseColor;
+                expect(parseColor('#FED')).toEqual(
+                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
+                expect(parseColor('#FEDC')).toEqual(
+                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
+                expect(parseColor('#fed')).toEqual(
+                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
+                expect(parseColor('#fedc')).toEqual(
+                    CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
+                done();
+            }));
+        });
+        it('parses rgba color strings', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                let parseColor = CanvasKit._testing.parseColor;
+                expect(parseColor('rgba(117, 33, 64, 0.75)')).toEqual(
+                    CanvasKit.Color(117, 33, 64, 0.75));
+                expect(parseColor('rgb(117, 33, 64, 0.75)')).toEqual(
+                    CanvasKit.Color(117, 33, 64, 0.75));
+                expect(parseColor('rgba(117,33,64)')).toEqual(
+                    CanvasKit.Color(117, 33, 64, 1.0));
+                expect(parseColor('rgb(117,33, 64)')).toEqual(
+                    CanvasKit.Color(117, 33, 64, 1.0));
+                done();
+            }));
+        });
+        it('parses named color strings', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                let parseColor = CanvasKit._testing.parseColor;
+                expect(parseColor('grey')).toEqual(
+                    CanvasKit.Color(128, 128, 128, 1.0));
+                expect(parseColor('blanchedalmond')).toEqual(
+                    CanvasKit.Color(255, 235, 205, 1.0));
+                expect(parseColor('transparent')).toEqual(
+                    CanvasKit.Color(0, 0, 0, 0));
+                done();
+            }));
+        });
+    }); // end describe('color string parsing')
+
+    function multipleCanvasTest(testname, done, test) {
+        if (CanvasKit.gpu) {
+            // TODO(kjlubick): add Software backend to GPU build skia:8548
+            console.log(`SKIPPING ${testname} on GPU`);
+            done();
+            return;
+        }
+        const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
+        skcanvas._config = 'software_canvas';
+        const realCanvas = document.getElementById('test');
+        realCanvas._config = 'html_canvas';
+        realCanvas.width = CANVAS_WIDTH;
+        realCanvas.height = CANVAS_HEIGHT;
+
+        let promises = [];
+
+        for (let canvas of [skcanvas, realCanvas]) {
+            test(canvas);
+            // canvas has .toDataURL (even though skcanvas is not a real Canvas)
+            // so this will work.
+            promises.push(reportCanvas(canvas, 'all_path_operations', canvas._config));
+        }
+        Promise.all(promises).then(() => {
+            skcanvas.dispose();
+            done();
+        }).catch(reportError(done));
+    }
+
+    describe('Path drawing API', function() {
+        it('supports all the line types', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('all_line_drawing_operations', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    // TODO(kjlubick): scale doesn't work quite right yet, but putting
+                    // it before all draws makes it consistent for now.
+                    ctx.scale(3.0, 3.0);
+                    ctx.moveTo(20, 5);
+                    ctx.lineTo(30, 20);
+                    ctx.lineTo(40, 10);
+                    ctx.lineTo(50, 20);
+                    ctx.lineTo(60, 0);
+                    ctx.lineTo(20, 5);
+
+                    ctx.moveTo(20, 80);
+                    ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
+
+                    ctx.moveTo(36, 148);
+                    ctx.quadraticCurveTo(66, 188, 120, 136);
+                    ctx.lineTo(36, 148);
+
+                    ctx.rect(5, 170, 20, 25);
+
+                    ctx.moveTo(150, 180);
+                    ctx.arcTo(150, 100, 50, 200, 20);
+                    ctx.lineTo(160, 160);
+
+                    ctx.moveTo(20, 120);
+                    ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
+                    ctx.lineTo(20, 120);
+
+                    ctx.moveTo(150, 5);
+                    ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
+
+                    ctx.lineWidth = 2;
+                    ctx.stroke();
+                });
+            }));
+        });
+    }); // end describe('Path drawing API')
+
+
+});
diff --git a/modules/pathkit/CHANGELOG.md b/modules/pathkit/CHANGELOG.md
new file mode 100644
index 0000000..4ea69d1
--- /dev/null
+++ b/modules/pathkit/CHANGELOG.md
@@ -0,0 +1,15 @@
+
+
+Trunk
+-----
+
+New Features:
+
+
+Bug Fixes:
+
+
+v0.4.2: 2018-11-07
+------------------
+
+Beginning of changelog.
diff --git a/modules/pathkit/chaining.js b/modules/pathkit/chaining.js
index f901d38..c181e44 100644
--- a/modules/pathkit/chaining.js
+++ b/modules/pathkit/chaining.js
@@ -12,11 +12,12 @@
       //   - an SVGMatrix
       //   - the 6 parameters of an SVG Matrix
       //   - the 9 parameters of a full Matrix
+      var path = arguments[0];
       if (arguments.length === 1) {
-        // Add path, unchanged.  Use identify matrix
-        this._addPath(arguments[0], 1, 0, 0,
-                                    0, 1, 0,
-                                    0, 0, 1);
+        // Add path, unchanged.  Use identity matrix
+        this._addPath(path, 1, 0, 0,
+                            0, 1, 0,
+                            0, 0, 1);
       } else if (arguments.length === 2) {
         // Takes SVGMatrix, which has its args in a counter-intuitive order
         // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#Transform_functions
@@ -24,22 +25,22 @@
          * @type {SVGMatrix}
          */
         var sm = arguments[1];
-        this._addPath(arguments[0], sm.a, sm.c, sm.e,
-                                    sm.b, sm.d, sm.f,
-                                    0,    0,    1);
+        this._addPath(path, sm.a, sm.c, sm.e,
+                            sm.b, sm.d, sm.f,
+                               0,    0,    1);
       } else if (arguments.length === 7) {
         // User provided the 6 params for an SVGMatrix directly.
         var a = arguments;
-        this._addPath(arguments[0], a[1], a[3], a[5],
-                                    a[2], a[4], a[6],
-                                    0,     0,     1);
+        this._addPath(path, a[1], a[3], a[5],
+                            a[2], a[4], a[6],
+                              0 ,   0 ,   1 );
       } 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]);
+        this._addPath(path, 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, 7, or 10 args. Got ' + arguments.length);
         return null;
diff --git a/modules/pathkit/tests/testReporter.js b/modules/pathkit/tests/testReporter.js
index 3879ec1..530b712 100644
--- a/modules/pathkit/tests/testReporter.js
+++ b/modules/pathkit/tests/testReporter.js
@@ -3,9 +3,9 @@
 // Typically used for debugging.
 const fail_on_no_gold = false;
 
-function reportCanvas(canvas, testname) {
+function reportCanvas(canvas, testname, outputType='canvas') {
     let b64 = canvas.toDataURL('image/png');
-    return _report(b64, 'canvas', testname);
+    return _report(b64, outputType, testname);
 }
 
 function reportSVG(svg, testname) {
@@ -71,6 +71,8 @@
             }).catch(reportError(done));
 }
 
+// data is a base64 encoded png, outputType is the value that goes with the
+// key 'config' when reporting.
 function _report(data, outputType, testname) {
     return fetch(REPORT_URL, {
         method: 'POST',
@@ -116,6 +118,8 @@
             fn()
         } catch (e) {
             console.log('Failed with the following error', e);
+            expect(e).toBeFalsy();
+            debugger;
             done();
         }
         // We don't call done with finally because