1// CanvasPath methods, which all take an SkPath object as the first param 2 3function arc(skpath, x, y, radius, startAngle, endAngle, ccw) { 4 // As per https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arc 5 // arc is essentially a simpler version of ellipse. 6 ellipse(skpath, x, y, radius, radius, 0, startAngle, endAngle, ccw); 7} 8 9function arcTo(skpath, x1, y1, x2, y2, radius) { 10 if (!allAreFinite([x1, y1, x2, y2, radius])) { 11 return; 12 } 13 if (radius < 0) { 14 throw 'radii cannot be negative'; 15 } 16 if (skpath.isEmpty()) { 17 skpath.moveTo(x1, y1); 18 } 19 skpath.arcTo(x1, y1, x2, y2, radius); 20} 21 22function bezierCurveTo(skpath, cp1x, cp1y, cp2x, cp2y, x, y) { 23 if (!allAreFinite([cp1x, cp1y, cp2x, cp2y, x, y])) { 24 return; 25 } 26 if (skpath.isEmpty()) { 27 skpath.moveTo(cp1x, cp1y); 28 } 29 skpath.cubicTo(cp1x, cp1y, cp2x, cp2y, x, y); 30} 31 32function closePath(skpath) { 33 if (skpath.isEmpty()) { 34 return; 35 } 36 // Check to see if we are not just a single point 37 var bounds = skpath.getBounds(); 38 if ((bounds.fBottom - bounds.fTop) || (bounds.fRight - bounds.fLeft)) { 39 skpath.close(); 40 } 41} 42 43function _ellipseHelper(skpath, x, y, radiusX, radiusY, startAngle, endAngle) { 44 var sweepDegrees = radiansToDegrees(endAngle - startAngle); 45 var startDegrees = radiansToDegrees(startAngle); 46 47 var oval = CanvasKit.LTRBRect(x - radiusX, y - radiusY, x + radiusX, y + radiusY); 48 49 // draw in 2 180 degree segments because trying to draw all 360 degrees at once 50 // draws nothing. 51 if (almostEqual(Math.abs(sweepDegrees), 360)) { 52 var halfSweep = sweepDegrees/2; 53 skpath.arcTo(oval, startDegrees, halfSweep, false); 54 skpath.arcTo(oval, startDegrees + halfSweep, halfSweep, false); 55 return; 56 } 57 skpath.arcTo(oval, startDegrees, sweepDegrees, false); 58} 59 60function ellipse(skpath, x, y, radiusX, radiusY, rotation, 61 startAngle, endAngle, ccw) { 62 if (!allAreFinite([x, y, radiusX, radiusY, rotation, startAngle, endAngle])) { 63 return; 64 } 65 if (radiusX < 0 || radiusY < 0) { 66 throw 'radii cannot be negative'; 67 } 68 69 // based off of CanonicalizeAngle in Chrome 70 var tao = 2 * Math.PI; 71 var newStartAngle = startAngle % tao; 72 if (newStartAngle < 0) { 73 newStartAngle += tao; 74 } 75 var delta = newStartAngle - startAngle; 76 startAngle = newStartAngle; 77 endAngle += delta; 78 79 // Based off of AdjustEndAngle in Chrome. 80 if (!ccw && (endAngle - startAngle) >= tao) { 81 // Draw complete ellipse 82 endAngle = startAngle + tao; 83 } else if (ccw && (startAngle - endAngle) >= tao) { 84 // Draw complete ellipse 85 endAngle = startAngle - tao; 86 } else if (!ccw && startAngle > endAngle) { 87 endAngle = startAngle + (tao - (startAngle - endAngle) % tao); 88 } else if (ccw && startAngle < endAngle) { 89 endAngle = startAngle - (tao - (endAngle - startAngle) % tao); 90 } 91 92 // Based off of Chrome's implementation in 93 // https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/graphics/path.cc 94 // of note, can't use addArc or addOval because they close the arc, which 95 // the spec says not to do (unless the user explicitly calls closePath). 96 // This throws off points being in/out of the arc. 97 if (!rotation) { 98 _ellipseHelper(skpath, x, y, radiusX, radiusY, startAngle, endAngle); 99 return; 100 } 101 var rotated = CanvasKit.SkMatrix.rotated(rotation, x, y); 102 var rotatedInvert = CanvasKit.SkMatrix.rotated(-rotation, x, y); 103 skpath.transform(rotatedInvert); 104 _ellipseHelper(skpath, x, y, radiusX, radiusY, startAngle, endAngle); 105 skpath.transform(rotated); 106} 107 108function lineTo(skpath, x, y) { 109 if (!allAreFinite([x, y])) { 110 return; 111 } 112 // A lineTo without a previous point has a moveTo inserted before it 113 if (skpath.isEmpty()) { 114 skpath.moveTo(x, y); 115 } 116 skpath.lineTo(x, y); 117} 118 119function moveTo(skpath, x, y) { 120 if (!allAreFinite([x, y])) { 121 return; 122 } 123 skpath.moveTo(x, y); 124} 125 126function quadraticCurveTo(skpath, cpx, cpy, x, y) { 127 if (!allAreFinite([cpx, cpy, x, y])) { 128 return; 129 } 130 if (skpath.isEmpty()) { 131 skpath.moveTo(cpx, cpy); 132 } 133 skpath.quadTo(cpx, cpy, x, y); 134} 135 136function rect(skpath, x, y, width, height) { 137 if (!allAreFinite([x, y, width, height])) { 138 return; 139 } 140 // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rect 141 skpath.addRect(x, y, x+width, y+height); 142} 143 144function Path2D(path) { 145 this._path = null; 146 if (typeof path === 'string') { 147 this._path = CanvasKit.MakePathFromSVGString(path); 148 } else if (path && path._getPath) { 149 this._path = path._getPath().copy(); 150 } else { 151 this._path = new CanvasKit.SkPath(); 152 } 153 154 this._getPath = function() { 155 return this._path; 156 } 157 158 this.addPath = function(path2d, transform) { 159 if (!transform) { 160 transform = { 161 'a': 1, 'c': 0, 'e': 0, 162 'b': 0, 'd': 1, 'f': 0, 163 }; 164 } 165 this._path.addPath(path2d._getPath(), [transform.a, transform.c, transform.e, 166 transform.b, transform.d, transform.f]); 167 } 168 169 this.arc = function(x, y, radius, startAngle, endAngle, ccw) { 170 arc(this._path, x, y, radius, startAngle, endAngle, ccw); 171 } 172 173 this.arcTo = function(x1, y1, x2, y2, radius) { 174 arcTo(this._path, x1, y1, x2, y2, radius); 175 } 176 177 this.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) { 178 bezierCurveTo(this._path, cp1x, cp1y, cp2x, cp2y, x, y); 179 } 180 181 this.closePath = function() { 182 closePath(this._path); 183 } 184 185 this.ellipse = function(x, y, radiusX, radiusY, rotation, 186 startAngle, endAngle, ccw) { 187 ellipse(this._path, x, y, radiusX, radiusY, rotation, 188 startAngle, endAngle, ccw); 189 } 190 191 this.lineTo = function(x, y) { 192 lineTo(this._path, x, y); 193 } 194 195 this.moveTo = function(x, y) { 196 moveTo(this._path, x, y); 197 } 198 199 this.quadraticCurveTo = function(cpx, cpy, x, y) { 200 quadraticCurveTo(this._path, cpx, cpy, x, y); 201 } 202 203 this.rect = function(x, y, width, height) { 204 rect(this._path, x, y, width, height); 205 } 206} 207