toosoon-utils 4.1.4 → 4.1.6

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.
@@ -0,0 +1,442 @@
1
+ import { EPSILON, PI } from '../../constants';
2
+ import { arc, ellipse, isCoincident, isCollinear } 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';
11
+ import Path from './Path';
12
+ /**
13
+ * Utility class for manipulating connected curves providing methods similar to the 2D Canvas API
14
+ *
15
+ * @exports
16
+ * @class PathContext
17
+ * @extends Path
18
+ */
19
+ export default class PathContext extends Path {
20
+ /**
21
+ * Path current offset
22
+ */
23
+ currentPosition = [NaN, NaN];
24
+ /**
25
+ * Create a path from the given list of points
26
+ *
27
+ * @param {Point[]} points Array of points defining the path
28
+ * @param {Point[]} type Type of curve used for creating the path
29
+ * @return {this}
30
+ */
31
+ setFromPoints(points, type) {
32
+ this.moveTo(...points[0]);
33
+ if (type === 'polyline')
34
+ return this.polylineTo(points);
35
+ if (type === 'spline')
36
+ return this.splineTo(points);
37
+ for (let i = 1, l = points.length; i < l; i++) {
38
+ this.lineTo(...points[i]);
39
+ }
40
+ return this;
41
+ }
42
+ /**
43
+ * Begin the path
44
+ * Reset `currentPosition`
45
+ */
46
+ beginPath() {
47
+ this._setCurrentPosition(NaN, NaN);
48
+ }
49
+ /**
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
52
+ */
53
+ 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);
59
+ return this;
60
+ }
61
+ /**
62
+ * Move {@link Path#currentPosition} to the coordinates specified by `x` and `y`
63
+ *
64
+ * @param {number} x X-axis coordinate of the point
65
+ * @param {number} y Y-axis coordinate of the point
66
+ * @return {this}
67
+ */
68
+ moveTo(x, y) {
69
+ this._setCurrentPosition(x, y);
70
+ return this;
71
+ }
72
+ /**
73
+ * 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
+ *
76
+ * @param {number} x X-axis coordinate of the point
77
+ * @param {number} y Y-axis coordinate of the point
78
+ * @return {this}
79
+ */
80
+ lineTo(x, y) {
81
+ if (this.currentPosition.every(isNaN))
82
+ return this.moveTo(x, y);
83
+ const curve = new LineCurve(...this.currentPosition, x, y);
84
+ this.add(curve);
85
+ this._setCurrentPosition(x, y);
86
+ return this;
87
+ }
88
+ /**
89
+ * Draw a Polyline curve from the current position through the given points
90
+ * Add an instance of {@link PolylineCurve} to the path
91
+ *
92
+ * @param {Point[]} points Array of points defining the curve
93
+ * @returns {this}
94
+ */
95
+ polylineTo(points) {
96
+ if (this.currentPosition.every(isNaN))
97
+ this.moveTo(...points[0]);
98
+ const curve = new PolylineCurve([this.currentPosition].concat(points));
99
+ 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
+ if (radius < 0) {
118
+ console.warn(`Path.arcTo()`, `Arc radius must be non-negative.`, radius);
119
+ return this.lineTo(x2, y2);
120
+ }
121
+ const x0 = this.currentPosition[0];
122
+ const y0 = this.currentPosition[1];
123
+ // (x1, y1) is coincident with (x0, y0)
124
+ if (isCoincident(x0, y0, x1, y1)) {
125
+ return this;
126
+ }
127
+ // (x0, y0), (x1 ,y1) and (x2, y2) are collinear
128
+ if (isCollinear(x0, y0, x1, y1, x2, y2) || radius <= EPSILON) {
129
+ return this.lineTo(x2, y2);
130
+ }
131
+ const dx01 = x0 - x1;
132
+ const dy01 = y0 - y1;
133
+ const dx20 = x2 - x0;
134
+ const dy20 = y2 - y0;
135
+ const dx21 = x2 - x1;
136
+ const dy21 = y2 - y1;
137
+ const l20 = Math.hypot(dx20, dy20);
138
+ const l21 = Math.hypot(dx21, dy21);
139
+ const l01 = Math.hypot(dx01, dy01);
140
+ const l = radius * Math.tan((PI - Math.acos((l21 + l01 - l20) / (2 * l21 * l01))) / 2);
141
+ const t01 = l / l01;
142
+ const t21 = l / l21;
143
+ if (Math.abs(t01 - 1) > EPSILON) {
144
+ this.lineTo(x1 + t01 * dx01, y1 + t01 * dy01);
145
+ }
146
+ const startX = x1 + t01 * dx01;
147
+ const startY = y1 + t01 * dy01;
148
+ const endX = x1 + t21 * dx21;
149
+ const endY = y1 + t21 * dy21;
150
+ const normalX = dy01 * -1;
151
+ const normalY = dx01;
152
+ const centerX = (startX + endX) / 2;
153
+ const centerY = (startY + endY) / 2;
154
+ const d = Math.sqrt(radius ** 2 - l ** 2);
155
+ const cx = centerX + (normalX / Math.hypot(normalX, normalY)) * d;
156
+ const cy = centerY + (normalY / Math.hypot(normalX, normalY)) * d;
157
+ const startAngle = Math.atan2(startY - cy, startX - cx);
158
+ const endAngle = Math.atan2(endY - cy, endX - cx);
159
+ const counterclockwise = dy01 * dx21 < dx01 * dy21;
160
+ this.arc(x1 + t21 * dx21, y1 + t21 * dy21, radius, startAngle, endAngle, counterclockwise);
161
+ return this;
162
+ }
163
+ /**
164
+ * 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`
165
+ * Add an instance of {@link QuadraticBezierCurve} to the path
166
+ *
167
+ * @param {number} cpx X-axis coordinate of the control point
168
+ * @param {number} cpy Y-axis coordinate of the control point
169
+ * @param {number} x2 X-axis coordinate of the end point
170
+ * @param {number} y2 Y-axis coordinate of the end point
171
+ * @return {this}
172
+ */
173
+ quadraticCurveTo(cpx, cpy, x2, y2) {
174
+ if (this.currentPosition.every(isNaN))
175
+ this.moveTo(cpx, cpy);
176
+ const curve = new QuadraticBezierCurve(...this.currentPosition, cpx, cpy, x2, y2);
177
+ this.add(curve);
178
+ this._setCurrentPosition(x2, y2);
179
+ return this;
180
+ }
181
+ /**
182
+ * 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`)
183
+ * Add an instance of {@link CubicBezierCurve} to the path
184
+ *
185
+ * @param {number} cp1x X-axis coordinate of the first control point
186
+ * @param {number} cp1y Y-axis coordinate of the first control point
187
+ * @param {number} cp2x X-axis coordinate of the second control point
188
+ * @param {number} cp2y Y-axis coordinate of the second control point
189
+ * @param {number} x2 X-axis coordinate of the end point
190
+ * @param {number} y2 Y-axis coordinate of the end point
191
+ * @return {this}
192
+ */
193
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2) {
194
+ if (this.currentPosition.every(isNaN))
195
+ this.moveTo(cp1x, cp1y);
196
+ const curve = new CubicBezierCurve(...this.currentPosition, cp1x, cp1y, cp2x, cp2y, x2, y2);
197
+ this.add(curve);
198
+ this._setCurrentPosition(x2, y2);
199
+ return this;
200
+ }
201
+ /**
202
+ * 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`)
203
+ * Add an instance of {@link CatmullRomCurve} to the path
204
+ *
205
+ * @param {number} cp1x X-axis coordinate of the first control point
206
+ * @param {number} cp1y Y-axis coordinate of the first control point
207
+ * @param {number} cp2x X-axis coordinate of the second control point
208
+ * @param {number} cp2y Y-axis coordinate of the second control point
209
+ * @param {number} x2 X-axis coordinate of the end point
210
+ * @param {number} y2 Y-axis coordinate of the end point
211
+ * @return {this}
212
+ */
213
+ catmullRomCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2) {
214
+ if (this.currentPosition.every(isNaN))
215
+ this.moveTo(cp1x, cp1y);
216
+ const curve = new CatmullRomCurve(...this.currentPosition, cp1x, cp1y, cp2x, cp2y, x2, y2);
217
+ this.add(curve);
218
+ this._setCurrentPosition(x2, y2);
219
+ return this;
220
+ }
221
+ /**
222
+ * Draw a Spline curve from the current position through the given points
223
+ * Add an instance of {@link SplineCurve} to the path
224
+ *
225
+ * @param {Point[]} points Array of points defining the curve
226
+ * @return {this}
227
+ */
228
+ splineTo(points) {
229
+ if (this.currentPosition.every(isNaN))
230
+ this.moveTo(...points[0]);
231
+ const curve = new SplineCurve([this.currentPosition].concat(points));
232
+ this.add(curve);
233
+ this._setCurrentPosition(...points[points.length - 1]);
234
+ return this;
235
+ }
236
+ /**
237
+ * Draw an Ellispe curve which is centered at (`cx`, `cy`) position
238
+ * Add an instance of {@link EllipseCurve} to the path
239
+ *
240
+ * @param {number} cx X-axis coordinate of the center of the circle
241
+ * @param {number} cy Y-axis coordinate of the center of the circle
242
+ * @param {number} rx X-radius of the ellipse
243
+ * @param {number} ry Y-radius of the ellipse
244
+ * @param {number} [rotation] Rotation angle of the ellipse (in radians), counterclockwise from the positive X-axis
245
+ * @param {number} [startAngle] Start angle of the arc (in radians)
246
+ * @param {number} [endAngle] End angle of the arc (in radians)
247
+ * @param {boolean} [counterclockwise] Flag indicating the direction of the arc
248
+ * @return {this}
249
+ */
250
+ ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise) {
251
+ const start = ellipse(0, cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
252
+ const end = ellipse(1, cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
253
+ if (this.currentPosition.every(isNaN))
254
+ this.moveTo(...start);
255
+ else if (!isCoincident(...this.currentPosition, ...start))
256
+ this.lineTo(...start);
257
+ if (rx <= EPSILON && ry <= EPSILON)
258
+ return this;
259
+ const curve = new EllipseCurve(cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
260
+ this.add(curve);
261
+ this._setCurrentPosition(...end);
262
+ return this;
263
+ }
264
+ /**
265
+ * Draw an Arc curve which is centered at (`cx`, `cy`) position
266
+ * Add an instance of {@link ArcCurve} to the path
267
+ *
268
+ * @param {number} cx X-axis coordinate of the center of the circle
269
+ * @param {number} cy Y-axis coordinate of the center of the circle
270
+ * @param {number} radius Radius of the circle
271
+ * @param {number} [startAngle] Start angle of the arc (in radians)
272
+ * @param {number} [endAngle] End angle of the arc (in radians)
273
+ * @param {boolean} [counterclockwise] Flag indicating the direction of the arc
274
+ * @return {this}
275
+ */
276
+ arc(cx, cy, radius, startAngle, endAngle, counterclockwise) {
277
+ const start = arc(0, cx, cy, radius, startAngle, endAngle, counterclockwise);
278
+ const end = arc(1, cx, cy, radius, startAngle, endAngle, counterclockwise);
279
+ if (this.currentPosition.every(isNaN))
280
+ this.moveTo(...start);
281
+ else if (!isCoincident(...this.currentPosition, ...start))
282
+ this.lineTo(...start);
283
+ if (radius <= EPSILON)
284
+ return this;
285
+ const curve = new ArcCurve(cx, cy, radius, startAngle, endAngle, counterclockwise);
286
+ this.add(curve);
287
+ this._setCurrentPosition(...end);
288
+ return this;
289
+ }
290
+ /**
291
+ * Draw a rectangular path from the start position specified by `x` and `y` to the end position using `width` and `height`
292
+ * Add an instance of {@link PolylineCurve} to the path
293
+ *
294
+ * @param {number} x X-axis coordinate of the rectangle starting point
295
+ * @param {number} y Y-axis coordinate of the rectangle starting point
296
+ * @param {number} width Rectangle width (Positive values are to the right and negative to the left)
297
+ * @param {number} height Rectangle height (Positive values are down, and negative are up)
298
+ * @return {this}
299
+ */
300
+ rect(x, y, width, height) {
301
+ if (this.currentPosition.every(isNaN))
302
+ this.moveTo(x, y);
303
+ else if (!isCoincident(...this.currentPosition, x, y))
304
+ this.lineTo(x, y);
305
+ const curve = new PolylineCurve([
306
+ [x + width, y],
307
+ [x + width, y + height],
308
+ [x, y + height],
309
+ [x, y]
310
+ ]);
311
+ this.add(curve);
312
+ this._setCurrentPosition(x, y);
313
+ return this;
314
+ }
315
+ /**
316
+ * Draw a rounded rectangular path from the start position specified by `x` and `y` to the end position using `width` and `height`
317
+ *
318
+ * @param {number} x X-axis coordinate of the rectangle starting point
319
+ * @param {number} y Y-axis coordinate of the rectangle starting point
320
+ * @param {number} width Rectangle width (Positive values are to the right and negative to the left)
321
+ * @param {number} height Rectangle height (Positive values are down, and negative are up)
322
+ * @param {number|number[]} radius Radius of the circular arc to be used for the corners of the rectangle
323
+ * @return {this}
324
+ */
325
+ roundRect(x, y, width, height, radius) {
326
+ let topLeftRadius = typeof radius === 'number' ? radius : radius[0];
327
+ let topRightRadius = typeof radius === 'number' ? radius : radius[1] ?? radius[0];
328
+ let bottomRightRadius = typeof radius === 'number' ? radius : radius[2] ?? topLeftRadius;
329
+ let bottomLeftRadius = typeof radius === 'number' ? radius : radius[3] ?? topRightRadius;
330
+ const maxRadius = Math.min(width / 2, height / 2);
331
+ topLeftRadius = Math.min(topLeftRadius, maxRadius);
332
+ topRightRadius = Math.min(topRightRadius, maxRadius);
333
+ bottomRightRadius = Math.min(bottomRightRadius, maxRadius);
334
+ bottomLeftRadius = Math.min(bottomLeftRadius, maxRadius);
335
+ if (this.currentPosition.every(isNaN))
336
+ this.moveTo(x + topLeftRadius, y);
337
+ else if (!isCoincident(...this.currentPosition, x, y))
338
+ this.lineTo(x + topLeftRadius, y);
339
+ // Top-Right corner
340
+ if (topRightRadius > 0) {
341
+ this.lineTo(x + width - topRightRadius, y);
342
+ this.arcTo(x + width, y, x + width, y + topRightRadius, topRightRadius);
343
+ }
344
+ else {
345
+ this.lineTo(x + width, y);
346
+ }
347
+ // Bottom-Right corner
348
+ if (bottomRightRadius > 0) {
349
+ this.lineTo(x + width, y + height - bottomRightRadius);
350
+ this.arcTo(x + width, y + height, x + width - bottomRightRadius, y + height, bottomRightRadius);
351
+ }
352
+ else {
353
+ this.lineTo(x + width, y + height);
354
+ }
355
+ // Bottom-Left corner
356
+ if (bottomLeftRadius > 0) {
357
+ this.lineTo(x + bottomLeftRadius, y + height);
358
+ this.arcTo(x, y + height, x, y + height - bottomLeftRadius, bottomLeftRadius);
359
+ }
360
+ else {
361
+ this.lineTo(x, y + height);
362
+ }
363
+ // Top-Left corner
364
+ if (topLeftRadius > 0) {
365
+ this.lineTo(x, y + topLeftRadius);
366
+ this.arcTo(x, y, x + topLeftRadius, y, topLeftRadius);
367
+ }
368
+ else {
369
+ this.lineTo(x, y);
370
+ }
371
+ return this;
372
+ }
373
+ // public transform() {}
374
+ // public translate(x: number, y: number) {}
375
+ // public rotate(angle: number) {}
376
+ // public restore() {}
377
+ _setCurrentPosition(x, y) {
378
+ this.currentPosition[0] = x;
379
+ this.currentPosition[1] = y;
380
+ }
381
+ canvas;
382
+ fillStyle;
383
+ strokeStyle;
384
+ lineWidth;
385
+ lineCap;
386
+ lineJoin;
387
+ lineDashOffset;
388
+ fillText;
389
+ strokeText;
390
+ fill;
391
+ stroke;
392
+ clearRect;
393
+ fillRect;
394
+ strokeRect;
395
+ drawImage;
396
+ clip;
397
+ createLinearGradient;
398
+ createRadialGradient;
399
+ createConicGradient;
400
+ createPattern;
401
+ createImageData;
402
+ getImageData;
403
+ putImageData;
404
+ imageSmoothingEnabled;
405
+ imageSmoothingQuality;
406
+ font;
407
+ fontKerning;
408
+ fontStretch;
409
+ fontVariantCaps;
410
+ letterSpacing;
411
+ textAlign;
412
+ textBaseline;
413
+ textRendering;
414
+ wordSpacing;
415
+ direction;
416
+ measureText;
417
+ filter;
418
+ globalAlpha;
419
+ globalCompositeOperation;
420
+ miterLimit;
421
+ save;
422
+ restore;
423
+ reset;
424
+ transform;
425
+ translate;
426
+ rotate;
427
+ scale;
428
+ getTransform;
429
+ setTransform;
430
+ resetTransform;
431
+ getLineDash;
432
+ setLineDash;
433
+ shadowBlur;
434
+ shadowColor;
435
+ shadowOffsetX;
436
+ shadowOffsetY;
437
+ drawFocusIfNeeded;
438
+ isPointInPath;
439
+ isPointInStroke;
440
+ getContextAttributes;
441
+ isContextLost;
442
+ }
@@ -0,0 +1,100 @@
1
+ import Curve from '../curves/Curve';
2
+ import LineCurve from '../curves/LineCurve';
3
+ import PolylineCurve from '../curves/PolylineCurve';
4
+ import QuadraticBezierCurve from '../curves/QuadraticBezierCurve';
5
+ import CubicBezierCurve from '../curves/CubicBezierCurve';
6
+ import CatmullRomCurve from '../curves/CatmullRomCurve';
7
+ import SplineCurve from '../curves/SplineCurve';
8
+ import EllipseCurve from '../curves/EllipseCurve';
9
+ import ArcCurve from '../curves/ArcCurve';
10
+ import PathContext from './PathContext';
11
+ export type SerializationParams = {
12
+ curveResolution?: number;
13
+ };
14
+ /**
15
+ * Utility class for manipulating connected curves and generating SVG path
16
+ *
17
+ * It works by serializing 2D Canvas API to SVG path data
18
+ *
19
+ * @exports
20
+ * @class PathSVG
21
+ * @extends PathContext
22
+ */
23
+ export default class PathSVG extends PathContext {
24
+ /**
25
+ * Serialize a {@link Curve}
26
+ *
27
+ * @param {Curve} curve
28
+ * @param {object} [params.curveResolution=5] Resolution used for Catmull-Rom curves and Spline curves approximations
29
+ * @returns string
30
+ */
31
+ static serialize(curve: Curve, { curveResolution }?: SerializationParams): string;
32
+ /**
33
+ * Serialize a {@link LineCurve}
34
+ *
35
+ * @param {LineCurve} curve
36
+ * @returns string
37
+ */
38
+ static serializeLineCurve(curve: LineCurve): string;
39
+ /**
40
+ * Serialize a {@link PolylineCurve}
41
+ *
42
+ * @param {PolylineCurve} curve
43
+ * @returns string
44
+ */
45
+ static serializePolylineCurve(curve: PolylineCurve): string;
46
+ /**
47
+ * Serialize a {@link QuadraticBezierCurve}
48
+ *
49
+ * @param {QuadraticBezierCurve} curve
50
+ * @returns string
51
+ */
52
+ static serializeQuadraticBezierCurve(curve: QuadraticBezierCurve): string;
53
+ /**
54
+ * Serialize a {@link CubicBezierCurve}
55
+ *
56
+ * @param {CubicBezierCurve} curve
57
+ * @returns string
58
+ */
59
+ static serializeCubicBezierCurve(curve: CubicBezierCurve): string;
60
+ /**
61
+ * Serialize a {@link CatmullRomCurve}
62
+ *
63
+ * @param {CatmullRomCurve} curve
64
+ * @param {object} params
65
+ * @param {number} [params.curveResolution=5]
66
+ * @returns string
67
+ */
68
+ static serializeCatmullRomCurve(curve: CatmullRomCurve, { curveResolution }: Pick<SerializationParams, 'curveResolution'>): string;
69
+ /**
70
+ * Serialize a {@link SplineCurve}
71
+ *
72
+ * @param {SplineCurve} curve
73
+ * @param {object} params
74
+ * @param {number} [params.curveResolution=5]
75
+ * @returns string
76
+ */
77
+ static serializeSplineCurve(curve: SplineCurve, { curveResolution }: Pick<SerializationParams, 'curveResolution'>): string;
78
+ /**
79
+ * Serialize an {@link EllipseCurve}
80
+ *
81
+ * @param {EllipseCurve} curve
82
+ * @returns string
83
+ */
84
+ static serializeEllipseCurve(curve: EllipseCurve): string;
85
+ /**
86
+ * Serialize an {@link ArcCurve}
87
+ *
88
+ * @param {ArcCurve} curve
89
+ * @returns string
90
+ */
91
+ static serializeArcCurve(curve: ArcCurve): string;
92
+ /**
93
+ * Return SVG path data string
94
+ *
95
+ * @param {object} [params]
96
+ * @param {object} [params.curveResolution=5] Resolution used for Catmull-Rom curves and Spline curves approximations
97
+ * @returns {string}
98
+ */
99
+ toString(params?: SerializationParams): string;
100
+ }