[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/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();
   }