toosoon-utils 4.1.3 → 4.1.5
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.
- package/README.md +177 -124
- package/lib/extras/curves/ArcCurve.d.ts +1 -1
- package/lib/extras/curves/ArcCurve.js +1 -1
- package/lib/extras/curves/CatmullRomCurve.d.ts +62 -0
- package/lib/extras/curves/CatmullRomCurve.js +75 -0
- package/lib/extras/curves/Curve.d.ts +1 -0
- package/lib/extras/curves/Curve.js +3 -2
- package/lib/extras/curves/EllipseCurve.d.ts +5 -5
- package/lib/extras/curves/EllipseCurve.js +6 -5
- package/lib/extras/curves/PolylineCurve.d.ts +1 -1
- package/lib/extras/curves/PolylineCurve.js +1 -1
- package/lib/extras/curves/SplineCurve.d.ts +1 -1
- package/lib/extras/curves/SplineCurve.js +1 -1
- package/lib/extras/curves/index.d.ts +1 -4
- package/lib/extras/curves/index.js +1 -4
- package/lib/extras/paths/Path.d.ts +65 -0
- package/lib/extras/paths/Path.js +137 -0
- package/lib/extras/paths/PathContext.d.ts +166 -0
- package/lib/extras/paths/PathContext.js +381 -0
- package/lib/extras/paths/PathSVG.d.ts +100 -0
- package/lib/extras/paths/PathSVG.js +182 -0
- package/lib/extras/paths/index.d.ts +3 -0
- package/lib/extras/paths/index.js +3 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,381 @@
|
|
|
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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { EPSILON, PI, TWO_PI, TAU_EPSILON } from '../../constants';
|
|
2
|
+
import { isCoincident, toDegrees } 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 PathContext from './PathContext';
|
|
12
|
+
/**
|
|
13
|
+
* Utility class for manipulating connected curves and generating SVG path
|
|
14
|
+
*
|
|
15
|
+
* It works by serializing 2D Canvas API to SVG path data
|
|
16
|
+
*
|
|
17
|
+
* @exports
|
|
18
|
+
* @class PathSVG
|
|
19
|
+
* @extends PathContext
|
|
20
|
+
*/
|
|
21
|
+
export default class PathSVG extends PathContext {
|
|
22
|
+
/**
|
|
23
|
+
* Serialize a {@link Curve}
|
|
24
|
+
*
|
|
25
|
+
* @param {Curve} curve
|
|
26
|
+
* @param {object} [params.curveResolution=5] Resolution used for Catmull-Rom curves and Spline curves approximations
|
|
27
|
+
* @returns string
|
|
28
|
+
*/
|
|
29
|
+
static serialize(curve, { curveResolution } = {}) {
|
|
30
|
+
if (curve instanceof LineCurve) {
|
|
31
|
+
return PathSVG.serializeLineCurve(curve);
|
|
32
|
+
}
|
|
33
|
+
if (curve instanceof PolylineCurve) {
|
|
34
|
+
return PathSVG.serializePolylineCurve(curve);
|
|
35
|
+
}
|
|
36
|
+
if (curve instanceof QuadraticBezierCurve) {
|
|
37
|
+
return PathSVG.serializeQuadraticBezierCurve(curve);
|
|
38
|
+
}
|
|
39
|
+
if (curve instanceof CubicBezierCurve) {
|
|
40
|
+
return PathSVG.serializeCubicBezierCurve(curve);
|
|
41
|
+
}
|
|
42
|
+
if (curve instanceof CatmullRomCurve) {
|
|
43
|
+
return PathSVG.serializeCatmullRomCurve(curve, { curveResolution });
|
|
44
|
+
}
|
|
45
|
+
if (curve instanceof SplineCurve) {
|
|
46
|
+
return PathSVG.serializeSplineCurve(curve, { curveResolution });
|
|
47
|
+
}
|
|
48
|
+
if (curve instanceof EllipseCurve) {
|
|
49
|
+
return PathSVG.serializeEllipseCurve(curve);
|
|
50
|
+
}
|
|
51
|
+
if (curve instanceof ArcCurve) {
|
|
52
|
+
return PathSVG.serializeArcCurve(curve);
|
|
53
|
+
}
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Serialize a {@link LineCurve}
|
|
58
|
+
*
|
|
59
|
+
* @param {LineCurve} curve
|
|
60
|
+
* @returns string
|
|
61
|
+
*/
|
|
62
|
+
static serializeLineCurve(curve) {
|
|
63
|
+
const { x2, y2 } = curve;
|
|
64
|
+
return `L${x2},${y2}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Serialize a {@link PolylineCurve}
|
|
68
|
+
*
|
|
69
|
+
* @param {PolylineCurve} curve
|
|
70
|
+
* @returns string
|
|
71
|
+
*/
|
|
72
|
+
static serializePolylineCurve(curve) {
|
|
73
|
+
const { points } = curve;
|
|
74
|
+
return points.map(([x, y]) => `L${x},${y}`).join(' ');
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Serialize a {@link QuadraticBezierCurve}
|
|
78
|
+
*
|
|
79
|
+
* @param {QuadraticBezierCurve} curve
|
|
80
|
+
* @returns string
|
|
81
|
+
*/
|
|
82
|
+
static serializeQuadraticBezierCurve(curve) {
|
|
83
|
+
const { cpx, cpy, x2, y2 } = curve;
|
|
84
|
+
return `Q${cpx},${cpy},${x2},${y2}`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Serialize a {@link CubicBezierCurve}
|
|
88
|
+
*
|
|
89
|
+
* @param {CubicBezierCurve} curve
|
|
90
|
+
* @returns string
|
|
91
|
+
*/
|
|
92
|
+
static serializeCubicBezierCurve(curve) {
|
|
93
|
+
const { cp1x, cp1y, cp2x, cp2y, x2, y2 } = curve;
|
|
94
|
+
return `C${cp1x},${cp1y},${cp2x},${cp2y},${x2},${y2}`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Serialize a {@link CatmullRomCurve}
|
|
98
|
+
*
|
|
99
|
+
* @param {CatmullRomCurve} curve
|
|
100
|
+
* @param {object} params
|
|
101
|
+
* @param {number} [params.curveResolution=5]
|
|
102
|
+
* @returns string
|
|
103
|
+
*/
|
|
104
|
+
static serializeCatmullRomCurve(curve, { curveResolution = 5 }) {
|
|
105
|
+
const divisions = (curve.getLength() * curveResolution) / 100;
|
|
106
|
+
return curve
|
|
107
|
+
.getSpacedPoints(divisions)
|
|
108
|
+
.map(([x, y]) => `L${x},${y}`)
|
|
109
|
+
.join(' ');
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Serialize a {@link SplineCurve}
|
|
113
|
+
*
|
|
114
|
+
* @param {SplineCurve} curve
|
|
115
|
+
* @param {object} params
|
|
116
|
+
* @param {number} [params.curveResolution=5]
|
|
117
|
+
* @returns string
|
|
118
|
+
*/
|
|
119
|
+
static serializeSplineCurve(curve, { curveResolution = 5 }) {
|
|
120
|
+
const divisions = (curve.getLength() * curveResolution) / 100;
|
|
121
|
+
return curve
|
|
122
|
+
.getSpacedPoints(divisions)
|
|
123
|
+
.map(([x, y]) => `L${x},${y}`)
|
|
124
|
+
.join(' ');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Serialize an {@link EllipseCurve}
|
|
128
|
+
*
|
|
129
|
+
* @param {EllipseCurve} curve
|
|
130
|
+
* @returns string
|
|
131
|
+
*/
|
|
132
|
+
static serializeEllipseCurve(curve) {
|
|
133
|
+
const { cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise } = curve;
|
|
134
|
+
let deltaAngle = counterclockwise ? startAngle - endAngle : endAngle - startAngle;
|
|
135
|
+
if (deltaAngle < 0)
|
|
136
|
+
deltaAngle = (deltaAngle % TWO_PI) + TWO_PI;
|
|
137
|
+
const xAxisRotation = toDegrees(rotation);
|
|
138
|
+
const largeArcFlag = deltaAngle >= PI ? 1 : 0;
|
|
139
|
+
const sweepFlag = counterclockwise ? 0 : 1;
|
|
140
|
+
if (deltaAngle > TAU_EPSILON) {
|
|
141
|
+
const dx = Math.cos(startAngle) * rx;
|
|
142
|
+
const dy = Math.sin(startAngle) * ry;
|
|
143
|
+
return (`A${rx},${ry},${xAxisRotation},1,${sweepFlag},${cx - dx},${cy - dy}` +
|
|
144
|
+
`A${rx},${ry},${xAxisRotation},1,${sweepFlag},${cx + dx},${cy + dy}`);
|
|
145
|
+
}
|
|
146
|
+
else if (deltaAngle > EPSILON) {
|
|
147
|
+
const dx = Math.cos(endAngle) * rx;
|
|
148
|
+
const dy = Math.sin(endAngle) * ry;
|
|
149
|
+
return `A${rx},${ry},${xAxisRotation},${largeArcFlag},${sweepFlag},${cx + dx},${cy + dy}`;
|
|
150
|
+
}
|
|
151
|
+
return '';
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Serialize an {@link ArcCurve}
|
|
155
|
+
*
|
|
156
|
+
* @param {ArcCurve} curve
|
|
157
|
+
* @returns string
|
|
158
|
+
*/
|
|
159
|
+
static serializeArcCurve(curve) {
|
|
160
|
+
return PathSVG.serializeEllipseCurve(curve);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Return SVG path data string
|
|
164
|
+
*
|
|
165
|
+
* @param {object} [params]
|
|
166
|
+
* @param {object} [params.curveResolution=5] Resolution used for Catmull-Rom curves and Spline curves approximations
|
|
167
|
+
* @returns {string}
|
|
168
|
+
*/
|
|
169
|
+
toString(params = {}) {
|
|
170
|
+
return this.curves
|
|
171
|
+
.map((curve, index) => {
|
|
172
|
+
let commands = ``;
|
|
173
|
+
const lastPoint = index === 0 ? this.curves[0]?.getPoint(0) : this.curves[index - 1]?.getPoint(1);
|
|
174
|
+
if (!isCoincident(...lastPoint, ...curve.getPoint(0))) {
|
|
175
|
+
commands += `M${lastPoint[0]},${lastPoint[1]}`;
|
|
176
|
+
}
|
|
177
|
+
commands += PathSVG.serialize(curve, params);
|
|
178
|
+
return commands;
|
|
179
|
+
})
|
|
180
|
+
.join(' ');
|
|
181
|
+
}
|
|
182
|
+
}
|