[canvaskit] Add Path2D to Canvas API

This moves the shared logic into path2d.js from
canvas2dcontext.js.

Bug: skia:
Change-Id: Id63bc52a190109f7cbc4e17ddb5603e6e87d5dc0
Reviewed-on: https://skia-review.googlesource.com/c/178268
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
diff --git a/experimental/canvaskit/canvaskit/example.html b/experimental/canvaskit/canvaskit/example.html
index 7c8416d..9a79935 100644
--- a/experimental/canvaskit/canvaskit/example.html
+++ b/experimental/canvaskit/canvaskit/example.html
@@ -497,6 +497,10 @@
     realCanvas.width = 300;
     realCanvas.height = 300;
 
+    // svg data for a clock
+    skcanvas._path = skcanvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
+    realCanvas._path = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
+
     for (let canvas of [skcanvas, realCanvas]) {
       let ctx = canvas.getContext('2d');
       ctx.scale(1.5, 1.5);
@@ -530,6 +534,9 @@
       ctx.lineWidth = 4/3;
       ctx.stroke();
 
+      // make a clock
+      ctx.stroke(canvas._path);
+
       // Test edgecases and draw direction
       ctx.beginPath();
       ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
index 02616e7..1618d48 100644
--- a/experimental/canvaskit/canvaskit_bindings.cpp
+++ b/experimental/canvaskit/canvaskit_bindings.cpp
@@ -344,6 +344,14 @@
     return emscripten::val(s.c_str());
 }
 
+SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromSVGString(std::string str) {
+    SkPath path;
+    if (SkParsePath::FromSVGString(str.c_str(), &path)) {
+        return emscripten::val(path);
+    }
+    return emscripten::val::null();
+}
+
 SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
     SkPath out;
     if (Op(pathOne, pathTwo, op, &out)) {
@@ -486,6 +494,7 @@
         return SkMaskFilter::MakeBlur(style, sigma, respectCTM);
     }), allow_raw_pointers());
     function("MakePathFromOp", &MakePathFromOp);
+    function("MakePathFromSVGString", &MakePathFromSVGString);
 
     // These 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> {
diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh
index c76d5de..327afd3 100755
--- a/experimental/canvaskit/compile.sh
+++ b/experimental/canvaskit/compile.sh
@@ -90,6 +90,7 @@
 --pre-js $BASE_DIR/htmlcanvas/htmlcanvas.js \
 --pre-js $BASE_DIR/htmlcanvas/imagedata.js \
 --pre-js $BASE_DIR/htmlcanvas/lineargradient.js \
+--pre-js $BASE_DIR/htmlcanvas/path2d.js \
 --pre-js $BASE_DIR/htmlcanvas/pattern.js \
 --pre-js $BASE_DIR/htmlcanvas/radialgradient.js \
 --pre-js $BASE_DIR/htmlcanvas/postamble.js "
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
index 56d2669..f4882c3 100644
--- a/experimental/canvaskit/externs.js
+++ b/experimental/canvaskit/externs.js
@@ -39,6 +39,8 @@
 	MakeImageFromEncoded: function() {},
 	/** @return {LinearCanvasGradient} */
 	MakeLinearGradientShader: function() {},
+	MakePathFromOp: function() {},
+	MakePathFromSVGString: function() {},
 	MakeRadialGradientShader: function() {},
 	MakeSWCanvasSurface: function() {},
 	MakeSkDashPathEffect: function() {},
@@ -447,6 +449,7 @@
 HTMLCanvas.prototype.dispose = function() {};
 HTMLCanvas.prototype.getContext = function() {};
 HTMLCanvas.prototype.loadFont = function() {};
+HTMLCanvas.prototype.makePath2D = function() {};
 HTMLCanvas.prototype.toDataURL = function() {};
 
 var CanvasRenderingContext2D = {};
@@ -494,6 +497,18 @@
 CanvasRenderingContext2D.prototype.transform = function() {};
 CanvasRenderingContext2D.prototype.translate = function() {};
 
+var Path2D = {};
+Path2D.prototype.addPath = function() {};
+Path2D.prototype.arc = function() {};
+Path2D.prototype.arcTo = function() {};
+Path2D.prototype.bezierCurveTo = function() {};
+Path2D.prototype.closePath = function() {};
+Path2D.prototype.ellipse = function() {};
+Path2D.prototype.lineTo = function() {};
+Path2D.prototype.moveTo = function() {};
+Path2D.prototype.quadraticCurveTo = function() {};
+Path2D.prototype.rect = function() {};
+
 var LinearCanvasGradient = {};
 LinearCanvasGradient.prototype.addColorStop = function() {};
 var RadialCanvasGradient = {};
diff --git a/experimental/canvaskit/htmlcanvas/canvas2dcontext.js b/experimental/canvaskit/htmlcanvas/canvas2dcontext.js
index 5a07152..06e47dc 100644
--- a/experimental/canvaskit/htmlcanvas/canvas2dcontext.js
+++ b/experimental/canvaskit/htmlcanvas/canvas2dcontext.js
@@ -488,22 +488,11 @@
   });
 
   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);
+    arc(this._currentPath, x, y, radius, startAngle, endAngle, ccw);
   }
 
   this.arcTo = function(x1, y1, x2, y2, radius) {
-    if (!allAreFinite(arguments)) {
-      return;
-    }
-    if (radius < 0) {
-      throw 'radii cannot be negative';
-    }
-    if (this._currentPath.isEmpty()) {
-      this.moveTo(x1, y1);
-    }
-    this._currentPath.arcTo(x1, y1, x2, y2, radius);
+    arcTo(this._currentPath, x1, y1, x2, y2, radius);
   }
 
   // As per the spec this doesn't begin any paths, it only
@@ -514,13 +503,7 @@
   }
 
   this.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
-    if (!allAreFinite(arguments)) {
-      return;
-    }
-    if (this._currentPath.isEmpty()) {
-      this.moveTo(cp1x, cp1y);
-    }
-    this._currentPath.cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
+    bezierCurveTo(this._currentPath, cp1x, cp1y, cp2x, cp2y, x, y);
   }
 
   this.clearRect = function(x, y, width, height) {
@@ -530,25 +513,30 @@
     this._paint.setBlendMode(this._globalCompositeOperation);
   }
 
-  this.clip = function(fillRule) {
-    var clip = this._currentPath.copy();
+  this.clip = function(path, fillRule) {
+    if (typeof path === 'string') {
+      // shift the args if a Path2D is supplied
+      fillRule = path;
+      path = this._currentPath;
+    } else if (path && path._getPath) {
+      path = path._getPath();
+    }
+    if (!path) {
+      path = this._currentPath;
+    }
+
+    var clip = path.copy();
     if (fillRule && fillRule.toLowerCase() === 'evenodd') {
       clip.setFillType(CanvasKit.FillType.EvenOdd);
     } else {
       clip.setFillType(CanvasKit.FillType.Winding);
     }
     this._canvas.clipPath(clip, CanvasKit.ClipOp.Intersect, true);
+    clip.delete();
   }
 
   this.closePath = function() {
-    if (this._currentPath.isEmpty()) {
-      return;
-    }
-    // Check to see if we are not just a single point
-    var bounds = this._currentPath.getBounds();
-    if ((bounds.fBottom - bounds.fTop) || (bounds.fRight - bounds.fLeft)) {
-      this._currentPath.close();
-    }
+    closePath(this._currentPath);
   }
 
   this.createImageData = function() {
@@ -630,69 +618,10 @@
     iPaint.dispose();
   }
 
-  this._ellipseHelper = function(x, y, radiusX, radiusY, startAngle, endAngle) {
-    var sweepDegrees = radiansToDegrees(endAngle - startAngle);
-    var startDegrees = radiansToDegrees(startAngle);
-
-    var oval = CanvasKit.LTRBRect(x - radiusX, y - radiusY, x + radiusX, y + radiusY);
-
-    // draw in 2 180 degree segments because trying to draw all 360 degrees at once
-    // draws nothing.
-    if (almostEqual(Math.abs(sweepDegrees), 360)) {
-      var halfSweep = sweepDegrees/2;
-      this._currentPath.arcTo(oval, startDegrees, halfSweep, false);
-      this._currentPath.arcTo(oval, startDegrees + halfSweep, halfSweep, false);
-      return;
-    }
-    this._currentPath.arcTo(oval, startDegrees, sweepDegrees, false);
-  }
-
   this.ellipse = function(x, y, radiusX, radiusY, rotation,
                           startAngle, endAngle, ccw) {
-    if (!allAreFinite([x, y, radiusX, radiusY, rotation, startAngle, endAngle])) {
-      return;
-    }
-    if (radiusX < 0 || radiusY < 0) {
-      throw 'radii cannot be negative';
-    }
-
-    // based off of CanonicalizeAngle in Chrome
-    var tao = 2 * Math.PI;
-    var newStartAngle = startAngle % tao;
-    if (newStartAngle < 0) {
-      newStartAngle += tao;
-    }
-    var delta = newStartAngle - startAngle;
-    startAngle = newStartAngle;
-    endAngle += delta;
-
-    // Based off of AdjustEndAngle in Chrome.
-    if (!ccw && (endAngle - startAngle) >= tao) {
-      // Draw complete ellipse
-      endAngle = startAngle + tao;
-    } else if (ccw && (startAngle - endAngle) >= tao) {
-      // Draw complete ellipse
-      endAngle = startAngle - tao;
-    } else if (!ccw && startAngle > endAngle) {
-      endAngle = startAngle + (tao - (startAngle - endAngle) % tao);
-    } else if (ccw && startAngle < endAngle) {
-      endAngle = startAngle - (tao - (endAngle - startAngle) % tao);
-    }
-
-
-    // Based off of Chrome's implementation in
-    // https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/graphics/path.cc
-    // of note, can't use addArc or addOval because they close the arc, which
-    // the spec says not to do (unless the user explicitly calls closePath).
-    // This throws off points being in/out of the arc.
-    if (!rotation) {
-      this._ellipseHelper(x, y, radiusX, radiusY, startAngle, endAngle);
-      return;
-    }
-    var rotated = CanvasKit.SkMatrix.rotated(rotation, x, y);
-    this._currentPath.transform(CanvasKit.SkMatrix.invert(rotated));
-    this._ellipseHelper(x, y, radiusX, radiusY, startAngle, endAngle);
-    this._currentPath.transform(rotated);
+    ellipse(this._currentPath, x, y, radiusX, radiusY, rotation,
+            startAngle, endAngle, ccw);
   }
 
   // A helper to copy the current paint, ready for filling
@@ -719,7 +648,14 @@
     return paint;
   }
 
-  this.fill = function(fillRule) {
+  this.fill = function(path, fillRule) {
+    if (typeof path === 'string') {
+      // shift the args if a Path2D is supplied
+      fillRule = path;
+      path = this._currentPath;
+    } else if (path && path._getPath) {
+      path = path._getPath();
+    }
     if (fillRule === 'evenodd') {
       this._currentPath.setFillType(CanvasKit.FillType.EvenOdd);
     } else if (fillRule === 'nonzero' || !fillRule) {
@@ -727,17 +663,21 @@
     } else {
       throw 'invalid fill rule';
     }
+    if (!path) {
+      path = this._currentPath;
+    }
+
     var fillPaint = this._fillPaint();
 
     var shadowPaint = this._shadowPaint(fillPaint);
     if (shadowPaint) {
       this._canvas.save();
       this._canvas.concat(this._shadowOffsetMatrix());
-      this._canvas.drawPath(this._currentPath, shadowPaint);
+      this._canvas.drawPath(path, shadowPaint);
       this._canvas.restore();
       shadowPaint.dispose();
     }
-    this._canvas.drawPath(this._currentPath, fillPaint);
+    this._canvas.drawPath(path, fillPaint);
     fillPaint.dispose();
   }
 
@@ -785,6 +725,17 @@
   }
 
   this.isPointInPath = function(x, y, fillmode) {
+    var args = arguments;
+    if (args.length === 3) {
+      var path = this._currentPath;
+    } else if (args.length === 4) {
+      var path = args[0];
+      x = args[1];
+      y = args[2];
+      fillmode = args[3];
+    } else {
+      throw 'invalid arg count, need 3 or 4, got ' + args.length;
+    }
     if (!isFinite(x) || !isFinite(y)) {
       return false;
     }
@@ -796,20 +747,30 @@
     var pts = this._mapToLocalCoordinates([x, y]);
     x = pts[0];
     y = pts[1];
-    this._currentPath.setFillType(fillmode === 'nonzero' ?
+    path.setFillType(fillmode === 'nonzero' ?
                                   CanvasKit.FillType.Winding :
                                   CanvasKit.FillType.EvenOdd);
-    return this._currentPath.contains(x, y);
+    return path.contains(x, y);
   }
 
   this.isPointInStroke = function(x, y) {
+    var args = arguments;
+    if (args.length === 2) {
+      var path = this._currentPath;
+    } else if (args.length === 3) {
+      var path = args[0];
+      x = args[1];
+      y = args[2];
+    } else {
+      throw 'invalid arg count, need 2 or 3, got ' + args.length;
+    }
     if (!isFinite(x) || !isFinite(y)) {
       return false;
     }
     var pts = this._mapToLocalCoordinates([x, y]);
     x = pts[0];
     y = pts[1];
-    var temp = this._currentPath.copy();
+    var temp = path.copy();
     // fillmode is always nonzero
     temp.setFillType(CanvasKit.FillType.Winding);
     temp.stroke({'width': this.lineWidth, 'miter_limit': this.miterLimit,
@@ -822,14 +783,7 @@
   }
 
   this.lineTo = function(x, y) {
-    if (!allAreFinite(arguments)) {
-      return;
-    }
-    // A lineTo without a previous point has a moveTo inserted before it
-    if (this._currentPath.isEmpty()) {
-      this._currentPath.moveTo(x, y);
-    }
-    this._currentPath.lineTo(x, y);
+    lineTo(this._currentPath, x, y);
   }
 
   this.measureText = function(text) {
@@ -840,10 +794,7 @@
   }
 
   this.moveTo = function(x, y) {
-    if (!allAreFinite(arguments)) {
-      return;
-    }
-    this._currentPath.moveTo(x, y);
+    moveTo(this._currentPath, x, y);
   }
 
   this.putImageData = function(imageData, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
@@ -895,21 +846,11 @@
   }
 
   this.quadraticCurveTo = function(cpx, cpy, x, y) {
-    if (!allAreFinite(arguments)) {
-      return;
-    }
-    if (this._currentPath.isEmpty()) {
-      this._currentPath.moveTo(cpx, cpy);
-    }
-    this._currentPath.quadTo(cpx, cpy, x, y);
+    quadraticCurveTo(this._currentPath, cpx, cpy, x, y);
   }
 
   this.rect = function(x, y, width, height) {
-    if (!allAreFinite(arguments)) {
-      return;
-    }
-    // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rect
-    this._currentPath.addRect(x, y, x+width, y+height);
+    rect(this._currentPath, x, y, width, height);
   }
 
   this.resetTransform = function() {
@@ -1113,19 +1054,20 @@
     return paint;
   }
 
-  this.stroke = function() {
+  this.stroke = function(path) {
+    path = path ? path._getPath() : this._currentPath;
     var strokePaint = this._strokePaint();
 
     var shadowPaint = this._shadowPaint(strokePaint);
     if (shadowPaint) {
       this._canvas.save();
       this._canvas.concat(this._shadowOffsetMatrix());
-      this._canvas.drawPath(this._currentPath, shadowPaint);
+      this._canvas.drawPath(path, shadowPaint);
       this._canvas.restore();
       shadowPaint.dispose();
     }
 
-    this._canvas.drawPath(this._currentPath, strokePaint);
+    this._canvas.drawPath(path, strokePaint);
     strokePaint.dispose();
   }
 
diff --git a/experimental/canvaskit/htmlcanvas/htmlcanvas.js b/experimental/canvaskit/htmlcanvas/htmlcanvas.js
index 6f118e5..de9794f 100644
--- a/experimental/canvaskit/htmlcanvas/htmlcanvas.js
+++ b/experimental/canvaskit/htmlcanvas/htmlcanvas.js
@@ -32,6 +32,12 @@
     addToFontCache(newFont, descriptors);
   }
 
+  this.makePath2D = function(path) {
+    var p2d = new Path2D(path);
+    this._toCleanup.push(p2d._getPath());
+    return p2d;
+  }
+
   // A normal <canvas> requires that clients call getContext
   this.getContext = function(type) {
     if (type === '2d') {
diff --git a/experimental/canvaskit/htmlcanvas/path2d.js b/experimental/canvaskit/htmlcanvas/path2d.js
new file mode 100644
index 0000000..dd9c682
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/path2d.js
@@ -0,0 +1,206 @@
+// CanvasPath methods, which all take an SkPath object as the first param
+
+function arc(skpath, 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.
+  ellipse(skpath, x, y, radius, radius, 0, startAngle, endAngle, ccw);
+}
+
+function arcTo(skpath, x1, y1, x2, y2, radius) {
+  if (!allAreFinite([x1, y1, x2, y2, radius])) {
+    return;
+  }
+  if (radius < 0) {
+    throw 'radii cannot be negative';
+  }
+  if (skpath.isEmpty()) {
+    skpath.moveTo(x1, y1);
+  }
+  skpath.arcTo(x1, y1, x2, y2, radius);
+}
+
+function bezierCurveTo(skpath, cp1x, cp1y, cp2x, cp2y, x, y) {
+  if (!allAreFinite([cp1x, cp1y, cp2x, cp2y, x, y])) {
+    return;
+  }
+  if (skpath.isEmpty()) {
+    skpath.moveTo(cp1x, cp1y);
+  }
+  skpath.cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
+}
+
+function closePath(skpath) {
+  if (skpath.isEmpty()) {
+    return;
+  }
+  // Check to see if we are not just a single point
+  var bounds = skpath.getBounds();
+  if ((bounds.fBottom - bounds.fTop) || (bounds.fRight - bounds.fLeft)) {
+    skpath.close();
+  }
+}
+
+function _ellipseHelper(skpath, x, y, radiusX, radiusY, startAngle, endAngle) {
+  var sweepDegrees = radiansToDegrees(endAngle - startAngle);
+  var startDegrees = radiansToDegrees(startAngle);
+
+  var oval = CanvasKit.LTRBRect(x - radiusX, y - radiusY, x + radiusX, y + radiusY);
+
+  // draw in 2 180 degree segments because trying to draw all 360 degrees at once
+  // draws nothing.
+  if (almostEqual(Math.abs(sweepDegrees), 360)) {
+    var halfSweep = sweepDegrees/2;
+    skpath.arcTo(oval, startDegrees, halfSweep, false);
+    skpath.arcTo(oval, startDegrees + halfSweep, halfSweep, false);
+    return;
+  }
+  skpath.arcTo(oval, startDegrees, sweepDegrees, false);
+}
+
+function ellipse(skpath, x, y, radiusX, radiusY, rotation,
+                 startAngle, endAngle, ccw) {
+  if (!allAreFinite([x, y, radiusX, radiusY, rotation, startAngle, endAngle])) {
+    return;
+  }
+  if (radiusX < 0 || radiusY < 0) {
+    throw 'radii cannot be negative';
+  }
+
+  // based off of CanonicalizeAngle in Chrome
+  var tao = 2 * Math.PI;
+  var newStartAngle = startAngle % tao;
+  if (newStartAngle < 0) {
+    newStartAngle += tao;
+  }
+  var delta = newStartAngle - startAngle;
+  startAngle = newStartAngle;
+  endAngle += delta;
+
+  // Based off of AdjustEndAngle in Chrome.
+  if (!ccw && (endAngle - startAngle) >= tao) {
+    // Draw complete ellipse
+    endAngle = startAngle + tao;
+  } else if (ccw && (startAngle - endAngle) >= tao) {
+    // Draw complete ellipse
+    endAngle = startAngle - tao;
+  } else if (!ccw && startAngle > endAngle) {
+    endAngle = startAngle + (tao - (startAngle - endAngle) % tao);
+  } else if (ccw && startAngle < endAngle) {
+    endAngle = startAngle - (tao - (endAngle - startAngle) % tao);
+  }
+
+  // Based off of Chrome's implementation in
+  // https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/graphics/path.cc
+  // of note, can't use addArc or addOval because they close the arc, which
+  // the spec says not to do (unless the user explicitly calls closePath).
+  // This throws off points being in/out of the arc.
+  if (!rotation) {
+    _ellipseHelper(skpath, x, y, radiusX, radiusY, startAngle, endAngle);
+    return;
+  }
+  var rotated = CanvasKit.SkMatrix.rotated(rotation, x, y);
+  var rotatedInvert = CanvasKit.SkMatrix.rotated(-rotation, x, y);
+  skpath.transform(rotatedInvert);
+  _ellipseHelper(skpath, x, y, radiusX, radiusY, startAngle, endAngle);
+  skpath.transform(rotated);
+}
+
+function lineTo(skpath, x, y) {
+  if (!allAreFinite([x, y])) {
+    return;
+  }
+  // A lineTo without a previous point has a moveTo inserted before it
+  if (skpath.isEmpty()) {
+    skpath.moveTo(x, y);
+  }
+  skpath.lineTo(x, y);
+}
+
+function moveTo(skpath, x, y) {
+  if (!allAreFinite([x, y])) {
+    return;
+  }
+  skpath.moveTo(x, y);
+}
+
+function quadraticCurveTo(skpath, cpx, cpy, x, y) {
+  if (!allAreFinite([cpx, cpy, x, y])) {
+    return;
+  }
+  if (skpath.isEmpty()) {
+    skpath.moveTo(cpx, cpy);
+  }
+  skpath.quadTo(cpx, cpy, x, y);
+}
+
+function rect(skpath, x, y, width, height) {
+  if (!allAreFinite([x, y, width, height])) {
+    return;
+  }
+  // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rect
+  skpath.addRect(x, y, x+width, y+height);
+}
+
+function Path2D(path) {
+  this._path = null;
+  if (typeof path === 'string') {
+      this._path = CanvasKit.MakePathFromSVGString(path);
+  } else if (path && path._getPath) {
+      this._path = path._getPath().copy();
+  } else {
+    this._path = new CanvasKit.SkPath();
+  }
+
+  this._getPath = function() {
+      return this._path;
+  }
+
+  this.addPath = function(path2d, transform) {
+    if (!transform) {
+      transform = {
+        'a': 1, 'c': 0, 'e': 0,
+        'b': 0, 'd': 1, 'f': 0,
+      };
+    }
+    this._path.addPath(path2d._getPath(), [transform.a, transform.c, transform.e,
+                                           transform.b, transform.d, transform.f]);
+  }
+
+  this.arc = function(x, y, radius, startAngle, endAngle, ccw) {
+    arc(this._path, x, y, radius, startAngle, endAngle, ccw);
+  }
+
+  this.arcTo = function(x1, y1, x2, y2, radius) {
+    arcTo(this._path, x1, y1, x2, y2, radius);
+  }
+
+  this.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
+    bezierCurveTo(this._path, cp1x, cp1y, cp2x, cp2y, x, y);
+  }
+
+  this.closePath = function() {
+    closePath(this._path);
+  }
+
+  this.ellipse = function(x, y, radiusX, radiusY, rotation,
+                          startAngle, endAngle, ccw) {
+    ellipse(this._path, x, y, radiusX, radiusY, rotation,
+            startAngle, endAngle, ccw);
+  }
+
+  this.lineTo = function(x, y) {
+    lineTo(this._path, x, y);
+  }
+
+  this.moveTo = function(x, y) {
+    moveTo(this._path, x, y);
+  }
+
+  this.quadraticCurveTo = function(cpx, cpy, x, y) {
+    quadraticCurveTo(this._path, cpx, cpy, x, y);
+  }
+
+  this.rect = function(x, y, width, height) {
+    rect(this._path, x, y, width, height);
+  }
+}
diff --git a/experimental/canvaskit/tests/canvas2d.spec.js b/experimental/canvaskit/tests/canvas2d.spec.js
index 002ccad..4ce051f 100644
--- a/experimental/canvaskit/tests/canvas2d.spec.js
+++ b/experimental/canvaskit/tests/canvas2d.spec.js
@@ -291,7 +291,7 @@
         }).catch(reportError(done));
     }
 
-    describe('Path drawing API', function() {
+    describe('CanvasContext2D API', function() {
         it('supports all the line types', function(done) {
             LoadCanvasKit.then(catchException(done, () => {
                 multipleCanvasTest('all_line_drawing_operations', done, (canvas) => {
@@ -837,7 +837,57 @@
                 done();
             }));
         });
-    }); // end describe('Path drawing API')
+    }); // end describe('CanvasContext2D API')
+
+    describe('Path2D API', function() {
+        it('supports all the line types', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('path2d_line_drawing_operations', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    var clock;
+                    var path;
+                    if (canvas.makePath2D) {
+                        clock = canvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
+                        path = canvas.makePath2D();
+                    } else {
+                        clock = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z')
+                        path = new Path2D();
+                    }
+                    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.bezierCurveTo(90, 10, 160, 150, 190, 10);
+
+                    path.moveTo(36, 148);
+                    path.quadraticCurveTo(66, 188, 120, 136);
+                    path.lineTo(36, 148);
+
+                    path.rect(5, 170, 20, 25);
+
+                    path.moveTo(150, 180);
+                    path.arcTo(150, 100, 50, 200, 20);
+                    path.lineTo(160, 160);
+
+                    path.moveTo(20, 120);
+                    path.arc(20, 120, 18, 0, 1.75 * Math.PI);
+                    path.lineTo(20, 120);
+
+                    path.moveTo(150, 5);
+                    path.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
+
+                    ctx.lineWidth = 2;
+                    ctx.scale(3.0, 3.0);
+                    ctx.stroke(path);
+                    ctx.stroke(clock);
+                });
+            }));
+        });
+    }); // end describe('Path2D API')
 
 
 });