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