Kevin Lubick | a40f832 | 2018-12-17 16:01:36 -0500 | [diff] [blame] | 1 | // CanvasPath methods, which all take an SkPath object as the first param |
| 2 | |
| 3 | function 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 | |
| 9 | function 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 | |
| 22 | function 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 | |
| 32 | function 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 | |
| 43 | function _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 | |
| 60 | function 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 | |
| 108 | function 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 | |
| 119 | function moveTo(skpath, x, y) { |
| 120 | if (!allAreFinite([x, y])) { |
| 121 | return; |
| 122 | } |
| 123 | skpath.moveTo(x, y); |
| 124 | } |
| 125 | |
| 126 | function 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 | |
| 136 | function 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 | |
| 144 | function 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 | } |