toosoon-utils 4.1.8 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +97 -885
  2. package/lib/constants.d.ts +0 -1
  3. package/lib/constants.js +3 -1
  4. package/lib/extras/color-scale/ColorScale.d.ts +2 -2
  5. package/lib/extras/color-scale/ColorScale.js +2 -2
  6. package/lib/extras/curves/CatmullRomCurve.d.ts +20 -5
  7. package/lib/extras/curves/CatmullRomCurve.js +23 -5
  8. package/lib/extras/curves/CatmullRomCurve3.d.ts +101 -0
  9. package/lib/extras/curves/CatmullRomCurve3.js +122 -0
  10. package/lib/extras/curves/CubicBezierCurve.d.ts +20 -5
  11. package/lib/extras/curves/CubicBezierCurve.js +23 -5
  12. package/lib/extras/curves/CubicBezierCurve3.d.ts +101 -0
  13. package/lib/extras/curves/CubicBezierCurve3.js +122 -0
  14. package/lib/extras/curves/Curve.d.ts +23 -24
  15. package/lib/extras/curves/Curve.js +19 -26
  16. package/lib/extras/curves/EllipseCurve.d.ts +21 -5
  17. package/lib/extras/curves/EllipseCurve.js +55 -6
  18. package/lib/extras/curves/LineCurve.d.ts +34 -10
  19. package/lib/extras/curves/LineCurve.js +35 -13
  20. package/lib/extras/curves/LineCurve3.d.ts +87 -0
  21. package/lib/extras/curves/LineCurve3.js +108 -0
  22. package/lib/extras/curves/PolylineCurve.d.ts +9 -8
  23. package/lib/extras/curves/PolylineCurve.js +6 -6
  24. package/lib/extras/curves/PolylineCurve3.d.ts +28 -0
  25. package/lib/extras/curves/PolylineCurve3.js +39 -0
  26. package/lib/extras/curves/QuadraticBezierCurve.d.ts +19 -5
  27. package/lib/extras/curves/QuadraticBezierCurve.js +22 -5
  28. package/lib/extras/curves/QuadraticBezierCurve3.d.ts +84 -0
  29. package/lib/extras/curves/QuadraticBezierCurve3.js +102 -0
  30. package/lib/extras/curves/SplineCurve.d.ts +12 -8
  31. package/lib/extras/curves/SplineCurve.js +9 -6
  32. package/lib/extras/curves/SplineCurve3.d.ts +28 -0
  33. package/lib/extras/curves/SplineCurve3.js +41 -0
  34. package/lib/extras/curves/index.d.ts +6 -0
  35. package/lib/extras/curves/index.js +6 -0
  36. package/lib/extras/geometry/Matrix2.d.ts +1 -0
  37. package/lib/extras/geometry/Matrix2.js +230 -0
  38. package/lib/extras/geometry/Matrix4.d.ts +1 -0
  39. package/lib/extras/geometry/Matrix4.js +632 -0
  40. package/lib/extras/geometry/Vector.d.ts +42 -0
  41. package/lib/extras/geometry/Vector.js +1 -0
  42. package/lib/extras/geometry/Vector2.d.ts +480 -0
  43. package/lib/extras/geometry/Vector2.js +709 -0
  44. package/lib/extras/geometry/Vector3.d.ts +486 -0
  45. package/lib/extras/geometry/Vector3.js +765 -0
  46. package/lib/extras/geometry/index.d.ts +3 -0
  47. package/lib/extras/geometry/index.js +2 -0
  48. package/lib/extras/paths/Path.d.ts +24 -18
  49. package/lib/extras/paths/Path.js +48 -35
  50. package/lib/extras/paths/PathContext.d.ts +97 -67
  51. package/lib/extras/paths/PathContext.js +326 -183
  52. package/lib/extras/paths/PathSVG.d.ts +43 -31
  53. package/lib/extras/paths/PathSVG.js +68 -50
  54. package/lib/geometry.d.ts +0 -135
  55. package/lib/geometry.js +1 -219
  56. package/lib/maths.d.ts +54 -22
  57. package/lib/maths.js +77 -27
  58. package/lib/random.d.ts +12 -16
  59. package/lib/random.js +19 -27
  60. package/lib/tsconfig.tsbuildinfo +1 -1
  61. package/lib/types.d.ts +43 -1
  62. package/package.json +2 -1
@@ -1,13 +1,7 @@
1
1
  import { EPSILON, PI } from '../../constants';
2
- import { arc, distance, ellipse, isCoincident, isCollinear, line } from '../../geometry';
3
- import LineCurve from '../curves/LineCurve';
4
- import PolylineCurve from '../curves/PolylineCurve';
5
- import QuadraticBezierCurve from '../curves/QuadraticBezierCurve';
6
- import CubicBezierCurve from '../curves/CubicBezierCurve';
7
- import CatmullRomCurve from '../curves/CatmullRomCurve';
8
- import SplineCurve from '../curves/SplineCurve';
9
- import EllipseCurve from '../curves/EllipseCurve';
10
- import ArcCurve from '../curves/ArcCurve';
2
+ import { angle, distance, toDegrees } from '../../geometry';
3
+ import { LineCurve, PolylineCurve, QuadraticBezierCurve, CubicBezierCurve, CatmullRomCurve, SplineCurve, EllipseCurve, ArcCurve } from '../curves';
4
+ import { Vector2 } from '../geometry';
11
5
  import Path from './Path';
12
6
  /**
13
7
  * Utility class for manipulating connected curves providing methods similar to the 2D Canvas API
@@ -15,47 +9,53 @@ import Path from './Path';
15
9
  * @exports
16
10
  * @class PathContext
17
11
  * @extends Path
12
+ * @implements CanvasRenderingContext2D
18
13
  */
19
14
  export default class PathContext extends Path {
20
15
  /**
21
- * Path current offset
16
+ * Path current position
22
17
  */
23
- currentPosition = [NaN, NaN];
18
+ currentPosition = new Vector2(NaN, NaN);
24
19
  /**
25
- * Create a path from the given list of points
20
+ * Path current transformation matrix
21
+ */
22
+ currentTransform = new DOMMatrix();
23
+ _transformStack = [];
24
+ /**
25
+ * Create a path from a given list of points
26
26
  *
27
- * @param {Point[]} points Array of points defining the path
28
- * @param {Point[]} type Type of curve used for creating the path
27
+ * @param {Point2[]} points Array of points defining the path
29
28
  * @return {this}
30
29
  */
31
- setFromPoints(points, type) {
30
+ setFromPoints(points) {
32
31
  this.moveTo(...points[0]);
33
- if (type === 'polyline')
34
- return this.polylineTo(points);
35
- if (type === 'spline')
36
- return this.splineTo(points);
37
32
  for (let i = 1, l = points.length; i < l; i++) {
38
33
  this.lineTo(...points[i]);
39
34
  }
40
35
  return this;
41
36
  }
42
37
  /**
43
- * Begin the path
44
- * Reset `currentPosition`
38
+ * Begin this path
39
+ *
40
+ * @return {this}
45
41
  */
46
42
  beginPath() {
47
43
  this._setCurrentPosition(NaN, NaN);
44
+ return this;
48
45
  }
49
46
  /**
50
- * Draw a line from the ending position to the beginning position of the path
51
- * Add an instance of {@link LineCurve} to the path
47
+ * Draw a line from the ending position to the beginning position of this path
48
+ * Add an instance of {@link LineCurve} to this path
49
+ *
50
+ * @return {this}
52
51
  */
53
52
  closePath() {
54
- const startPoint = this.curves[0]?.getPoint(0);
55
- const endPoint = this.curves[this.curves.length - 1]?.getPoint(1);
56
- if (!isCoincident(...startPoint, ...endPoint))
57
- this.add(new LineCurve(...endPoint, ...startPoint));
58
- this._setCurrentPosition(...endPoint);
53
+ const startPoint = this.subpaths[0]?.getPoint(0);
54
+ const endPoint = this.subpaths[this.subpaths.length - 1]?.getPoint(1);
55
+ if (!startPoint.equals(endPoint)) {
56
+ const curve = new LineCurve(endPoint.x, endPoint.y, startPoint.x, startPoint.y);
57
+ this.add(curve);
58
+ }
59
59
  return this;
60
60
  }
61
61
  /**
@@ -66,91 +66,46 @@ export default class PathContext extends Path {
66
66
  * @return {this}
67
67
  */
68
68
  moveTo(x, y) {
69
- this._setCurrentPosition(x, y);
69
+ const [tX, tY] = this._transformPoint([x, y]);
70
+ this._setCurrentPosition(tX, tY);
70
71
  return this;
71
72
  }
72
73
  /**
73
74
  * Draw a line from the current position to the position specified by `x` and `y`
74
- * Add an instance of {@link LineCurve} to the path
75
+ * Add an instance of {@link LineCurve} to this path
75
76
  *
76
77
  * @param {number} x X-axis coordinate of the point
77
78
  * @param {number} y Y-axis coordinate of the point
78
79
  * @return {this}
79
80
  */
80
81
  lineTo(x, y) {
81
- if (this.currentPosition.every(isNaN))
82
- return this.moveTo(x, y);
83
- const curve = new LineCurve(...this.currentPosition, x, y);
82
+ const [tX, tY] = this._transformPoint([x, y]);
83
+ if (!this._hasCurrentPosition())
84
+ return this._setCurrentPosition(tX, tY);
85
+ const curve = new LineCurve(this.currentPosition.x, this.currentPosition.y, tX, tY);
84
86
  this.add(curve);
85
- this._setCurrentPosition(x, y);
87
+ this._setCurrentPosition(tX, tY);
86
88
  return this;
87
89
  }
88
90
  /**
89
- * Draw a Polyline curve from the current position through the given points
90
- * Add an instance of {@link PolylineCurve} to the path
91
+ * Draw a Polyline curve from the current position through given points
92
+ * Add an instance of {@link PolylineCurve} to this path
91
93
  *
92
- * @param {Point[]} points Array of points defining the curve
94
+ * @param {Point2[]} points Array of points defining the curve
93
95
  * @returns {this}
94
96
  */
95
97
  polylineTo(points) {
96
- if (this.currentPosition.every(isNaN))
97
- this.moveTo(...points[0]);
98
- const curve = new PolylineCurve([this.currentPosition].concat(points));
98
+ const tPoints = this._transformPoints(points);
99
+ if (!this._hasCurrentPosition())
100
+ this._setCurrentPosition(...tPoints[0]);
101
+ const curve = new PolylineCurve([this.currentPosition.toArray()].concat(tPoints));
99
102
  this.add(curve);
100
- this._setCurrentPosition(...points[points.length - 1]);
101
- return this;
102
- }
103
- /**
104
- * Draw an Arc curve from the current position, tangential to the 2 segments created by both control points
105
- * Add an instance of {@link ArcCurve} to the path
106
- *
107
- * @param {number} x1 X-axis coordinate of the first control point
108
- * @param {number} y1 Y-axis coordinate of the first control point
109
- * @param {number} x2 X-axis coordinate of the second control point
110
- * @param {number} y2 Y-axis coordinate of the second control point
111
- * @param {number} radius Arc radius (Must be non-negative)
112
- * @returns {this}
113
- */
114
- arcTo(x1, y1, x2, y2, radius) {
115
- if (this.currentPosition.every(isNaN))
116
- this.moveTo(x1, y1);
117
- const x0 = this.currentPosition[0];
118
- const y0 = this.currentPosition[1];
119
- if (radius < 0) {
120
- throw new Error(`IndexSizeError: Failed to execute 'arcTo' on 'PathContext': The radius provided (${radius}) is negative.`);
121
- }
122
- if (isCoincident(x0, y0, x1, y1)) {
123
- return this;
124
- }
125
- if (isCollinear(x0, y0, x1, y1, x2, y2)) {
126
- return this.lineTo(x2, y2);
127
- }
128
- const l01 = distance(x0, y0, x1, y1);
129
- const l21 = distance(x2, y2, x1, y1);
130
- const [t1x, t1y] = line(radius / l01, x1, y1, x0, y0);
131
- const [t2x, t2y] = line(radius / l21, x1, y1, x2, y2);
132
- const v1x = (t1x - x1) / radius;
133
- const v1y = (t1y - y1) / radius;
134
- const v2x = (t2x - x1) / radius;
135
- const v2y = (t2y - y1) / radius;
136
- const normalX = v2y - v1y;
137
- const normalY = v1x - v2x;
138
- const cx = x1 + normalX * radius;
139
- const cy = y1 + normalY * radius;
140
- const startAngle = Math.atan2(t1y - cy, t1x - cx);
141
- const endAngle = Math.atan2(t2y - cy, t2x - cx);
142
- let clockwise = endAngle - startAngle > 0;
143
- if (clockwise && endAngle - startAngle > PI)
144
- clockwise = false;
145
- else if (!clockwise && startAngle - endAngle > PI)
146
- clockwise = true;
147
- this.lineTo(t1x, t1y);
148
- this.arc(cx, cy, radius, startAngle, endAngle, !clockwise);
103
+ this._setCurrentPosition(...tPoints[tPoints.length - 1]);
149
104
  return this;
150
105
  }
151
106
  /**
152
107
  * Draw a Quadratic Bézier curve from the current position to the end point specified by `x` and `y`, using the control point specified by `cpx` and `cpy`
153
- * Add an instance of {@link QuadraticBezierCurve} to the path
108
+ * Add an instance of {@link QuadraticBezierCurve} to this path
154
109
  *
155
110
  * @param {number} cpx X-axis coordinate of the control point
156
111
  * @param {number} cpy Y-axis coordinate of the control point
@@ -159,16 +114,18 @@ export default class PathContext extends Path {
159
114
  * @return {this}
160
115
  */
161
116
  quadraticCurveTo(cpx, cpy, x2, y2) {
162
- if (this.currentPosition.every(isNaN))
163
- this.moveTo(cpx, cpy);
164
- const curve = new QuadraticBezierCurve(...this.currentPosition, cpx, cpy, x2, y2);
117
+ const [tCpx, tCpy] = this._transformPoint([cpx, cpy]);
118
+ const [tX2, tY2] = this._transformPoint([x2, y2]);
119
+ if (!this._hasCurrentPosition())
120
+ this._setCurrentPosition(tCpx, tCpy);
121
+ const curve = new QuadraticBezierCurve(this.currentPosition.x, this.currentPosition.y, tCpx, tCpy, tX2, tY2);
165
122
  this.add(curve);
166
- this._setCurrentPosition(x2, y2);
123
+ this._setCurrentPosition(tX2, tY2);
167
124
  return this;
168
125
  }
169
126
  /**
170
127
  * Draw a Cubic Bézier curve from the current position to the end point specified by `x` and `y`, using the control point specified by (`cp1x`, `cp1y`) and (`cp2x`, `cp2y`)
171
- * Add an instance of {@link CubicBezierCurve} to the path
128
+ * Add an instance of {@link CubicBezierCurve} to this path
172
129
  *
173
130
  * @param {number} cp1x X-axis coordinate of the first control point
174
131
  * @param {number} cp1y Y-axis coordinate of the first control point
@@ -179,16 +136,19 @@ export default class PathContext extends Path {
179
136
  * @return {this}
180
137
  */
181
138
  bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2) {
182
- if (this.currentPosition.every(isNaN))
183
- this.moveTo(cp1x, cp1y);
184
- const curve = new CubicBezierCurve(...this.currentPosition, cp1x, cp1y, cp2x, cp2y, x2, y2);
139
+ const [tCp1x, tCp1y] = this._transformPoint([cp1x, cp1y]);
140
+ const [tCp2x, tCp2y] = this._transformPoint([cp2x, cp2y]);
141
+ const [tX2, tY2] = this._transformPoint([x2, y2]);
142
+ if (!this._hasCurrentPosition())
143
+ this._setCurrentPosition(tCp1x, tCp1y);
144
+ const curve = new CubicBezierCurve(this.currentPosition.x, this.currentPosition.y, tCp1x, tCp1y, tCp2x, tCp2y, tX2, tY2);
185
145
  this.add(curve);
186
- this._setCurrentPosition(x2, y2);
146
+ this._setCurrentPosition(tX2, tY2);
187
147
  return this;
188
148
  }
189
149
  /**
190
150
  * Draw a Catmull-Rom curve from the current position to the end point specified by `x` and `y`, using the control points specified by (`cp1x`, `cp1y`) and (`cp2x`, `cp2y`)
191
- * Add an instance of {@link CatmullRomCurve} to the path
151
+ * Add an instance of {@link CatmullRomCurve} to this path
192
152
  *
193
153
  * @param {number} cp1x X-axis coordinate of the first control point
194
154
  * @param {number} cp1y Y-axis coordinate of the first control point
@@ -199,85 +159,143 @@ export default class PathContext extends Path {
199
159
  * @return {this}
200
160
  */
201
161
  catmullRomCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2) {
202
- if (this.currentPosition.every(isNaN))
203
- this.moveTo(cp1x, cp1y);
204
- const curve = new CatmullRomCurve(...this.currentPosition, cp1x, cp1y, cp2x, cp2y, x2, y2);
162
+ const [tCp1x, tCp1y] = this._transformPoint([cp1x, cp1y]);
163
+ const [tCp2x, tCp2y] = this._transformPoint([cp2x, cp2y]);
164
+ const [tX2, tY2] = this._transformPoint([x2, y2]);
165
+ if (!this._hasCurrentPosition())
166
+ this._setCurrentPosition(tCp1x, tCp1y);
167
+ const curve = new CatmullRomCurve(this.currentPosition.x, this.currentPosition.y, tCp1x, tCp1y, tCp2x, tCp2y, tX2, tY2);
205
168
  this.add(curve);
206
- this._setCurrentPosition(x2, y2);
169
+ this._setCurrentPosition(tX2, tY2);
207
170
  return this;
208
171
  }
209
172
  /**
210
- * Draw a Spline curve from the current position through the given points
211
- * Add an instance of {@link SplineCurve} to the path
173
+ * Draw a Spline curve from the current position through given points
174
+ * Add an instance of {@link SplineCurve} to this path
212
175
  *
213
- * @param {Point[]} points Array of points defining the curve
176
+ * @param {Point2[]} points Array of points defining the curve
214
177
  * @return {this}
215
178
  */
216
179
  splineTo(points) {
217
- if (this.currentPosition.every(isNaN))
218
- this.moveTo(...points[0]);
219
- const curve = new SplineCurve([this.currentPosition].concat(points));
180
+ const tPoints = this._transformPoints(points);
181
+ if (!this._hasCurrentPosition())
182
+ this._setCurrentPosition(...tPoints[0]);
183
+ const curve = new SplineCurve([this.currentPosition.toArray()].concat(tPoints));
220
184
  this.add(curve);
221
- this._setCurrentPosition(...points[points.length - 1]);
185
+ this._setCurrentPosition(...tPoints[tPoints.length - 1]);
222
186
  return this;
223
187
  }
224
188
  /**
225
189
  * Draw an Ellispe curve which is centered at (`cx`, `cy`) position
226
- * Add an instance of {@link EllipseCurve} to the path
190
+ * Add an instance of {@link EllipseCurve} to this path
227
191
  *
228
192
  * @param {number} cx X-axis coordinate of the center of the circle
229
193
  * @param {number} cy Y-axis coordinate of the center of the circle
230
194
  * @param {number} rx X-radius of the ellipse
231
195
  * @param {number} ry Y-radius of the ellipse
232
- * @param {number} [rotation] Rotation angle of the ellipse (in radians), counterclockwise from the positive X-axis
233
- * @param {number} [startAngle] Start angle of the arc (in radians)
234
- * @param {number} [endAngle] End angle of the arc (in radians)
196
+ * @param {number} rotation Rotation angle of the ellipse (in radians), counterclockwise from the positive X-axis
197
+ * @param {number} startAngle Start angle of the arc (in radians)
198
+ * @param {number} endAngle End angle of the arc (in radians)
235
199
  * @param {boolean} [counterclockwise] Flag indicating the direction of the arc
236
200
  * @return {this}
237
201
  */
238
202
  ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise) {
239
- const start = ellipse(0, cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
240
- const end = ellipse(1, cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
241
- if (this.currentPosition.every(isNaN))
242
- this.moveTo(...start);
243
- else if (!isCoincident(...this.currentPosition, ...start))
244
- this.lineTo(...start);
245
- if (rx <= EPSILON && ry <= EPSILON)
203
+ const [tCx, tCy, tRx, tRy, tRotation] = this._transformEllipse(cx, cy, rx, ry, rotation);
204
+ const start = EllipseCurve.interpolate(0, tCx, tCy, tRx, tRy, tRotation, startAngle, endAngle, counterclockwise);
205
+ const end = EllipseCurve.interpolate(1, tCx, tCy, tRx, tRy, tRotation, startAngle, endAngle, counterclockwise);
206
+ if (!this._hasCurrentPosition())
207
+ this._setCurrentPosition(...start);
208
+ else if (!this.currentPosition.equals(start)) {
209
+ const curve = new LineCurve(this.currentPosition.x, this.currentPosition.y, ...start);
210
+ this.add(curve);
211
+ }
212
+ if (tRx <= EPSILON && tRy <= EPSILON)
246
213
  return this;
247
- const curve = new EllipseCurve(cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
214
+ const curve = new EllipseCurve(tCx, tCy, tRx, tRy, tRotation, startAngle, endAngle, counterclockwise);
248
215
  this.add(curve);
249
216
  this._setCurrentPosition(...end);
250
217
  return this;
251
218
  }
252
219
  /**
253
220
  * Draw an Arc curve which is centered at (`cx`, `cy`) position
254
- * Add an instance of {@link ArcCurve} to the path
221
+ * Add an instance of {@link ArcCurve} to this path
255
222
  *
256
223
  * @param {number} cx X-axis coordinate of the center of the circle
257
224
  * @param {number} cy Y-axis coordinate of the center of the circle
258
225
  * @param {number} radius Radius of the circle
259
- * @param {number} [startAngle] Start angle of the arc (in radians)
260
- * @param {number} [endAngle] End angle of the arc (in radians)
226
+ * @param {number} startAngle Start angle of the arc (in radians)
227
+ * @param {number} endAngle End angle of the arc (in radians)
261
228
  * @param {boolean} [counterclockwise] Flag indicating the direction of the arc
262
229
  * @return {this}
263
230
  */
264
231
  arc(cx, cy, radius, startAngle, endAngle, counterclockwise) {
265
- const start = arc(0, cx, cy, radius, startAngle, endAngle, counterclockwise);
266
- const end = arc(1, cx, cy, radius, startAngle, endAngle, counterclockwise);
267
- if (this.currentPosition.every(isNaN))
268
- this.moveTo(...start);
269
- else if (!isCoincident(...this.currentPosition, ...start))
270
- this.lineTo(...start);
271
- if (radius <= EPSILON)
232
+ if (!this._isUniform) {
233
+ return this.ellipse(cx, cy, radius, radius, 0, startAngle, endAngle, counterclockwise);
234
+ }
235
+ const [tCx, tCy, tRadius] = this._transformEllipse(cx, cy, radius, radius, 0);
236
+ const start = EllipseCurve.interpolate(0, tCx, tCy, tRadius, tRadius, 0, startAngle, endAngle, counterclockwise);
237
+ const end = EllipseCurve.interpolate(1, tCx, tCy, tRadius, tRadius, 0, startAngle, endAngle, counterclockwise);
238
+ if (!this._hasCurrentPosition())
239
+ this._setCurrentPosition(...start);
240
+ else if (!this.currentPosition.equals(start)) {
241
+ const curve = new LineCurve(this.currentPosition.x, this.currentPosition.y, ...start);
242
+ this.add(curve);
243
+ }
244
+ if (tRadius <= EPSILON)
272
245
  return this;
273
- const curve = new ArcCurve(cx, cy, radius, startAngle, endAngle, counterclockwise);
246
+ const curve = new ArcCurve(tCx, tCy, tRadius, startAngle, endAngle, counterclockwise);
274
247
  this.add(curve);
275
248
  this._setCurrentPosition(...end);
276
249
  return this;
277
250
  }
251
+ /**
252
+ * Draw an Arc curve from the current position, tangential to the 2 segments created by both control points
253
+ * Add an instance of {@link ArcCurve} to this path
254
+ *
255
+ * @param {number} x1 X-axis coordinate of the first control point
256
+ * @param {number} y1 Y-axis coordinate of the first control point
257
+ * @param {number} x2 X-axis coordinate of the second control point
258
+ * @param {number} y2 Y-axis coordinate of the second control point
259
+ * @param {number} radius Arc radius (Must be non-negative)
260
+ * @returns {this}
261
+ */
262
+ arcTo(x1, y1, x2, y2, radius) {
263
+ if (radius < 0) {
264
+ throw new Error(`IndexSizeError: Failed to execute 'arcTo' on 'PathContext': The radius provided (${radius}) is negative.`);
265
+ }
266
+ const [x0, y0] = this._inversePoint(this.currentPosition.toArray());
267
+ if (Vector2.equals([x0, y0], [x1, y1])) {
268
+ return this;
269
+ }
270
+ if (Vector2.collinear([x0, y0], [x1, y1], [x2, y2]) || radius === 0) {
271
+ return this.lineTo(x1, y1);
272
+ }
273
+ const l01 = distance(x0, y0, x1, y1);
274
+ const l21 = distance(x2, y2, x1, y1);
275
+ const [t1x, t1y] = LineCurve.interpolate(radius / l01, x1, y1, x0, y0);
276
+ const [t2x, t2y] = LineCurve.interpolate(radius / l21, x1, y1, x2, y2);
277
+ const v1x = (t1x - x1) / radius;
278
+ const v1y = (t1y - y1) / radius;
279
+ const v2x = (t2x - x1) / radius;
280
+ const v2y = (t2y - y1) / radius;
281
+ const normalX = v2y - v1y;
282
+ const normalY = v1x - v2x;
283
+ const cx = x1 + normalX * radius;
284
+ const cy = y1 + normalY * radius;
285
+ const startAngle = angle(cx, cy, t1x, t1y);
286
+ const endAngle = angle(cx, cy, t2x, t2y);
287
+ let clockwise = endAngle - startAngle > 0;
288
+ if (clockwise && endAngle - startAngle > PI)
289
+ clockwise = false;
290
+ else if (!clockwise && startAngle - endAngle > PI)
291
+ clockwise = true;
292
+ this.lineTo(t1x, t1y);
293
+ this.arc(cx, cy, radius, startAngle, endAngle, !clockwise);
294
+ return this;
295
+ }
278
296
  /**
279
297
  * Draw a rectangular path from the start position specified by `x` and `y` to the end position using `width` and `height`
280
- * Add an instance of {@link PolylineCurve} to the path
298
+ * Add an instance of {@link PolylineCurve} to this path
281
299
  *
282
300
  * @param {number} x X-axis coordinate of the rectangle starting point
283
301
  * @param {number} y Y-axis coordinate of the rectangle starting point
@@ -286,22 +304,19 @@ export default class PathContext extends Path {
286
304
  * @return {this}
287
305
  */
288
306
  rect(x, y, width, height) {
289
- if (this.currentPosition.every(isNaN))
290
- this.moveTo(x, y);
291
- else if (!isCoincident(...this.currentPosition, x, y))
292
- this.lineTo(x, y);
293
- const curve = new PolylineCurve([
307
+ this.moveTo(x, y);
308
+ this.polylineTo([
309
+ [x, y],
294
310
  [x + width, y],
295
311
  [x + width, y + height],
296
312
  [x, y + height],
297
313
  [x, y]
298
314
  ]);
299
- this.add(curve);
300
- this._setCurrentPosition(x, y);
301
315
  return this;
302
316
  }
303
317
  /**
304
318
  * Draw a rounded rectangular path from the start position specified by `x` and `y` to the end position using `width` and `height`
319
+ * Add an instance of {@link Path} to this path
305
320
  *
306
321
  * @param {number} x X-axis coordinate of the rectangle starting point
307
322
  * @param {number} y Y-axis coordinate of the rectangle starting point
@@ -311,6 +326,7 @@ export default class PathContext extends Path {
311
326
  * @return {this}
312
327
  */
313
328
  roundRect(x, y, width, height, radius) {
329
+ this.moveTo(x, y);
314
330
  let topLeftRadius = typeof radius === 'number' ? radius : radius[0];
315
331
  let topRightRadius = typeof radius === 'number' ? radius : radius[1] ?? radius[0];
316
332
  let bottomRightRadius = typeof radius === 'number' ? radius : radius[2] ?? topLeftRadius;
@@ -320,52 +336,183 @@ export default class PathContext extends Path {
320
336
  topRightRadius = Math.min(topRightRadius, maxRadius);
321
337
  bottomRightRadius = Math.min(bottomRightRadius, maxRadius);
322
338
  bottomLeftRadius = Math.min(bottomLeftRadius, maxRadius);
323
- if (this.currentPosition.every(isNaN))
324
- this.moveTo(x + topLeftRadius, y);
325
- else if (!isCoincident(...this.currentPosition, x, y))
326
- this.lineTo(x + topLeftRadius, y);
339
+ const curve = new PathContext({ autoClose: true });
340
+ curve.setTransform(this.getTransform());
341
+ this.add(curve);
327
342
  // Top-Right corner
328
343
  if (topRightRadius > 0) {
329
- this.lineTo(x + width - topRightRadius, y);
330
- this.arcTo(x + width, y, x + width, y + topRightRadius, topRightRadius);
344
+ curve.lineTo(x + width - topRightRadius, y);
345
+ curve.arcTo(x + width, y, x + width, y + topRightRadius, topRightRadius);
331
346
  }
332
347
  else {
333
- this.lineTo(x + width, y);
348
+ curve.lineTo(x + width, y);
334
349
  }
335
350
  // Bottom-Right corner
336
351
  if (bottomRightRadius > 0) {
337
- this.lineTo(x + width, y + height - bottomRightRadius);
338
- this.arcTo(x + width, y + height, x + width - bottomRightRadius, y + height, bottomRightRadius);
352
+ curve.lineTo(x + width, y + height - bottomRightRadius);
353
+ curve.arcTo(x + width, y + height, x + width - bottomRightRadius, y + height, bottomRightRadius);
339
354
  }
340
355
  else {
341
- this.lineTo(x + width, y + height);
356
+ curve.lineTo(x + width, y + height);
342
357
  }
343
358
  // Bottom-Left corner
344
359
  if (bottomLeftRadius > 0) {
345
- this.lineTo(x + bottomLeftRadius, y + height);
346
- this.arcTo(x, y + height, x, y + height - bottomLeftRadius, bottomLeftRadius);
360
+ curve.lineTo(x + bottomLeftRadius, y + height);
361
+ curve.arcTo(x, y + height, x, y + height - bottomLeftRadius, bottomLeftRadius);
347
362
  }
348
363
  else {
349
- this.lineTo(x, y + height);
364
+ curve.lineTo(x, y + height);
350
365
  }
351
366
  // Top-Left corner
352
367
  if (topLeftRadius > 0) {
353
- this.lineTo(x, y + topLeftRadius);
354
- this.arcTo(x, y, x + topLeftRadius, y, topLeftRadius);
368
+ curve.lineTo(x, y + topLeftRadius);
369
+ curve.arcTo(x, y, x + topLeftRadius, y, topLeftRadius);
355
370
  }
356
371
  else {
357
- this.lineTo(x, y);
372
+ curve.lineTo(x, y);
358
373
  }
359
374
  return this;
360
375
  }
361
- // public transform() {}
362
- // public translate(x: number, y: number) {}
363
- // public rotate(angle: number) {}
364
- // public restore() {}
376
+ _hasCurrentPosition() {
377
+ return !isNaN(this.currentPosition.x) && !isNaN(this.currentPosition.y);
378
+ }
365
379
  _setCurrentPosition(x, y) {
366
- this.currentPosition[0] = x;
367
- this.currentPosition[1] = y;
380
+ this.currentPosition.set(x, y);
381
+ return this;
382
+ }
383
+ setTransform(a, b, c, d, e, f) {
384
+ if (a instanceof DOMMatrix) {
385
+ const matrix = a;
386
+ this.currentTransform = new DOMMatrix([matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f]);
387
+ }
388
+ else {
389
+ this.currentTransform = new DOMMatrix([a, b, c, d, e, f]);
390
+ }
391
+ }
392
+ getTransform() {
393
+ const { a, b, c, d, e, f } = this.currentTransform;
394
+ return new DOMMatrix([a, b, c, d, e, f]);
395
+ }
396
+ resetTransform() {
397
+ this.setTransform(1, 0, 0, 1, 0, 0);
398
+ }
399
+ transform(a, b, c, d, e, f) {
400
+ const matrix = new DOMMatrix([a, b, c, d, e, f]);
401
+ this.currentTransform = this.currentTransform.multiply(matrix);
402
+ }
403
+ translate(x, y) {
404
+ this.currentTransform = this.currentTransform.translate(x, y);
405
+ }
406
+ rotate(angle) {
407
+ this.currentTransform = this.currentTransform.rotate(toDegrees(angle));
408
+ }
409
+ scale(x, y) {
410
+ this.currentTransform = this.currentTransform.scale(x, y);
411
+ }
412
+ save() {
413
+ this._transformStack.push(this.getTransform());
414
+ }
415
+ restore() {
416
+ if (this._transformStack.length > 0) {
417
+ this.currentTransform = this._transformStack.pop();
418
+ }
419
+ }
420
+ reset() {
421
+ this.resetTransform();
422
+ this._transformStack = [];
423
+ }
424
+ // ******************************************
425
+ // Path context transformations
426
+ // ******************************************
427
+ _transformPoint(point, matrix = this.currentTransform) {
428
+ if (this._isIdentity)
429
+ return point;
430
+ const { x, y } = matrix.transformPoint({ x: point[0], y: point[1] });
431
+ return [x, y];
368
432
  }
433
+ _transformPoints(points, matrix) {
434
+ if (this._isIdentity)
435
+ return points;
436
+ return points.map((point) => this._transformPoint(point, matrix));
437
+ }
438
+ _transformVector(vector, matrix) {
439
+ if (this._isIdentity)
440
+ return vector;
441
+ const [x0, y0] = this._transformPoint([0, 0], matrix);
442
+ const [vx, vy] = this._transformPoint(vector, matrix);
443
+ return [vx - x0, vy - y0];
444
+ }
445
+ _transformEllipse(cx, cy, rx, ry, rotation, matrix) {
446
+ if (this._isIdentity)
447
+ return [cx, cy, rx, ry, rotation];
448
+ [cx, cy] = this._transformPoint([cx, cy], matrix);
449
+ const [rx1, ry1] = this._transformVector([rx, 0], matrix);
450
+ const [rx2, ry2] = this._transformVector([0, ry], matrix);
451
+ rx = Math.hypot(rx1, ry1);
452
+ ry = Math.hypot(rx2, ry2);
453
+ rotation += angle(0, 0, rx1, ry1);
454
+ return [cx, cy, rx, ry, rotation];
455
+ }
456
+ _inversePoint(point) {
457
+ return this._transformPoint(point, this.currentTransform.inverse());
458
+ }
459
+ get _translateX() {
460
+ return this.currentTransform.e;
461
+ }
462
+ get _translateY() {
463
+ return this.currentTransform.f;
464
+ }
465
+ get _scaleX() {
466
+ const { a, b } = this.currentTransform;
467
+ return Math.hypot(a, b);
468
+ }
469
+ get _scaleY() {
470
+ const { c, d } = this.currentTransform;
471
+ return Math.hypot(c, d);
472
+ }
473
+ get _rotation() {
474
+ const { a, b } = this.currentTransform;
475
+ return Math.atan2(b, a);
476
+ }
477
+ get _skewX() {
478
+ const { c, d } = this.currentTransform;
479
+ const cos = Math.cos(this._rotation);
480
+ const sin = Math.sin(this._rotation);
481
+ const m11 = c * cos + d * sin;
482
+ return Math.atan(m11 / this._scaleX);
483
+ }
484
+ get _skewY() {
485
+ const { a, b } = this.currentTransform;
486
+ const cos = Math.cos(this._rotation);
487
+ const sin = Math.sin(this._rotation);
488
+ const m21 = a * sin - b * cos;
489
+ return Math.atan(m21 / this._scaleY);
490
+ }
491
+ get _isTranslated() {
492
+ return Math.abs(this._translateX) > EPSILON || Math.abs(this._translateY) > EPSILON;
493
+ }
494
+ get _isScaled() {
495
+ return Math.abs(this._scaleX - 1) > EPSILON || Math.abs(this._scaleY - 1) > EPSILON;
496
+ }
497
+ get _isRotated() {
498
+ const { b, c } = this.currentTransform;
499
+ return Math.abs(b) > EPSILON || Math.abs(c) > EPSILON;
500
+ }
501
+ get _isSkewed() {
502
+ const { a, b, c, d } = this.currentTransform;
503
+ const angleX = Math.atan2(b, a);
504
+ const angleY = Math.atan2(-c, d);
505
+ return Math.abs(angleX - angleY) > EPSILON;
506
+ }
507
+ get _isUniform() {
508
+ return Math.abs(this._scaleX - this._scaleY) <= EPSILON;
509
+ }
510
+ get _isIdentity() {
511
+ return this.currentTransform.isIdentity;
512
+ }
513
+ // ******************************************
514
+ // Canvas 2D interface
515
+ // ******************************************
369
516
  canvas;
370
517
  fillStyle;
371
518
  strokeStyle;
@@ -373,8 +520,11 @@ export default class PathContext extends Path {
373
520
  lineCap;
374
521
  lineJoin;
375
522
  lineDashOffset;
523
+ setLineDash;
524
+ getLineDash;
376
525
  fillText;
377
526
  strokeText;
527
+ miterLimit;
378
528
  fill;
379
529
  stroke;
380
530
  clearRect;
@@ -382,6 +532,9 @@ export default class PathContext extends Path {
382
532
  strokeRect;
383
533
  drawImage;
384
534
  clip;
535
+ filter;
536
+ globalAlpha;
537
+ globalCompositeOperation;
385
538
  createLinearGradient;
386
539
  createRadialGradient;
387
540
  createConicGradient;
@@ -402,29 +555,19 @@ export default class PathContext extends Path {
402
555
  wordSpacing;
403
556
  direction;
404
557
  measureText;
405
- filter;
406
- globalAlpha;
407
- globalCompositeOperation;
408
- miterLimit;
409
- save;
410
- restore;
411
- reset;
412
- transform;
413
- translate;
414
- rotate;
415
- scale;
416
- getTransform;
417
- setTransform;
418
- resetTransform;
419
- getLineDash;
420
- setLineDash;
421
- shadowBlur;
422
558
  shadowColor;
559
+ shadowBlur;
423
560
  shadowOffsetX;
424
561
  shadowOffsetY;
425
562
  drawFocusIfNeeded;
426
563
  isPointInPath;
427
564
  isPointInStroke;
428
- getContextAttributes;
429
- isContextLost;
565
+ getContextAttributes() {
566
+ return {
567
+ alpha: false
568
+ };
569
+ }
570
+ isContextLost() {
571
+ return false;
572
+ }
430
573
  }