svg-path-commander 2.1.0 → 2.1.1

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 (51) hide show
  1. package/README.md +61 -5
  2. package/dist/svg-path-commander.cjs +1 -1
  3. package/dist/svg-path-commander.cjs.map +1 -1
  4. package/dist/svg-path-commander.d.ts +119 -33
  5. package/dist/svg-path-commander.js +1 -1
  6. package/dist/svg-path-commander.js.map +1 -1
  7. package/dist/svg-path-commander.mjs +798 -828
  8. package/dist/svg-path-commander.mjs.map +1 -1
  9. package/package.json +3 -3
  10. package/src/convert/pathToAbsolute.ts +16 -70
  11. package/src/convert/pathToCurve.ts +36 -28
  12. package/src/convert/pathToRelative.ts +33 -62
  13. package/src/index.ts +13 -19
  14. package/src/interface.ts +1 -1
  15. package/src/math/arcTools.ts +248 -71
  16. package/src/math/bezier.ts +19 -27
  17. package/src/math/cubicTools.ts +67 -26
  18. package/src/math/distanceSquareRoot.ts +3 -1
  19. package/src/math/lineTools.ts +41 -26
  20. package/src/math/midPoint.ts +3 -1
  21. package/src/math/polygonArea.ts +3 -1
  22. package/src/math/polygonLength.ts +2 -1
  23. package/src/math/quadTools.ts +45 -26
  24. package/src/parser/parsePathString.ts +4 -4
  25. package/src/process/absolutizeSegment.ts +58 -0
  26. package/src/process/iterate.ts +33 -0
  27. package/src/process/normalizePath.ts +34 -28
  28. package/src/process/normalizeSegment.ts +8 -9
  29. package/src/process/projection2d.ts +2 -1
  30. package/src/process/relativizeSegment.ts +61 -0
  31. package/src/process/reversePath.ts +1 -1
  32. package/src/process/roundPath.ts +8 -10
  33. package/src/process/segmentToCubic.ts +1 -1
  34. package/src/process/shortenSegment.ts +3 -3
  35. package/src/process/splitCubic.ts +8 -7
  36. package/src/process/splitPath.ts +38 -4
  37. package/src/process/transformPath.ts +80 -73
  38. package/src/types.ts +35 -1
  39. package/src/util/getPathArea.ts +3 -3
  40. package/src/util/getPathBBox.ts +86 -19
  41. package/src/util/getPointAtLength.ts +98 -4
  42. package/src/util/getPropertiesAtLength.ts +2 -2
  43. package/src/util/getTotalLength.ts +71 -4
  44. package/test/class.test.ts +15 -14
  45. package/test/fixtures/shapes.js +27 -27
  46. package/test/fixtures/simpleShapes.js +18 -18
  47. package/test/static.test.ts +37 -17
  48. package/cypress.config.ts +0 -29
  49. package/src/process/fixArc.ts +0 -23
  50. package/src/process/replaceArc.ts +0 -52
  51. package/src/util/pathFactory.ts +0 -130
@@ -1,4 +1,4 @@
1
- import { default as getLineSegmentProperties } from './lineTools';
1
+ import { getPointAtLineLength } from './lineTools';
2
2
  import type { Point } from '../types';
3
3
 
4
4
  /**
@@ -19,48 +19,26 @@ const ellipticalArcLength = (rx: number, ry: number, theta: number) => {
19
19
  };
20
20
 
21
21
  /**
22
- * Returns the most extreme points in an Arc segment.
23
- * @param x Center X coordinate of the ellipse arc
24
- * @param y Center Y coordinate of the ellipse arc
25
- * @param rx Radius on the X axis of the ellipse
26
- * @param ry Radius on the Y axis of the ellipse
27
- * @param rotation The ellipse rotation angle in radians
28
- * @param startAngle The ellipse start angle in radians
29
- * @param endAngle The ellipse end angle in radians
30
- * @see https://stackoverflow.com/questions/87734/how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse
22
+ * Compute point on ellipse from angle around ellipse (theta);
23
+ * @param theta the arc sweep angle
24
+ * @param cx the center X
25
+ * @param cy the center Y
26
+ * @param rx the radius X
27
+ * @param ry the radius Y
28
+ * @param alpha the angle
29
+ * @returns a point around ellipse
31
30
  */
32
- const minmax = (
33
- x: number,
34
- y: number,
35
- rx: number,
36
- ry: number,
37
- rotation: number,
38
- startAngle: number,
39
- endAngle: number,
40
- ) => {
41
- const { cos, sin, min, max } = Math;
42
- const cosRotation = cos(rotation);
43
- const sinRotation = sin(rotation);
44
-
45
- // Rotate parametric equations
46
- const xRotated = (t: number) => {
47
- return x + rx * cos(t) * cosRotation - ry * sin(t) * sinRotation;
48
- };
49
- const yRotated = (t: number) => {
50
- return y + ry * sin(t) * cosRotation + rx * cos(t) * sinRotation;
51
- };
52
-
53
- // Evaluate at start and end angles
54
- const startX = xRotated(startAngle);
55
- const startY = yRotated(startAngle);
56
- const endX = xRotated(endAngle);
57
- const endY = yRotated(endAngle);
31
+ const arc = (theta: number, cx: number, cy: number, rx: number, ry: number, alpha: number) => {
32
+ // theta is angle in radians around arc
33
+ // alpha is angle of rotation of ellipse in radians
34
+ const cos = Math.cos(alpha);
35
+ const sin = Math.sin(alpha);
36
+ const x = rx * Math.cos(theta);
37
+ const y = ry * Math.sin(theta);
58
38
 
59
- // Find minimum and maximum x and y values
60
- // Return AABB
61
39
  return {
62
- min: { x: min(startX, endX), y: min(startY, endY) },
63
- max: { x: max(startX, endX), y: max(startY, endY) },
40
+ x: cx + cos * x - sin * y,
41
+ y: cy + sin * x + cos * y,
64
42
  };
65
43
  };
66
44
 
@@ -82,20 +60,21 @@ const angleBetween = (v0: Point, v1: Point) => {
82
60
  };
83
61
 
84
62
  /**
85
- * Returns properties for an Arc segment.
63
+ * Returns the following properties for an Arc segment: center, start angle
64
+ * and radiuses on X and Y coordinates.
86
65
  *
87
66
  * @param x1 the starting point X
88
67
  * @param y1 the starting point Y
89
- * @param c1x the first control point X
90
- * @param c1y the first control point Y
91
- * @param c2x the second control point X
92
- * @param c2y the second control point Y
68
+ * @param RX the radius on X axis
69
+ * @param RY the radius on Y axis
70
+ * @param angle the ellipse rotation in degrees
71
+ * @param LAF the large arc flag
72
+ * @param SF the sweep flag
93
73
  * @param x2 the ending point X
94
74
  * @param y2 the ending point Y
95
- * @param distance a [0-1] ratio
96
- * @returns properties specific to Arc segmentas well as the segment length, point at length and the bounding box
75
+ * @returns properties specific to Arc segments
97
76
  */
98
- const getSegmentProperties = (
77
+ const getArcProps = (
99
78
  x1: number,
100
79
  y1: number,
101
80
  RX: number,
@@ -105,7 +84,6 @@ const getSegmentProperties = (
105
84
  SF: number,
106
85
  x: number,
107
86
  y: number,
108
- distance?: number,
109
87
  ) => {
110
88
  const { abs, sin, cos, sqrt, PI } = Math;
111
89
  let rx = abs(RX);
@@ -115,14 +93,22 @@ const getSegmentProperties = (
115
93
 
116
94
  if (x1 === x && y1 === y) {
117
95
  return {
118
- point: { x, y },
119
- length: 0,
120
- bbox: { min: { x, y }, max: { x, y } },
96
+ rx,
97
+ ry,
98
+ startAngle: 0,
99
+ endAngle: 0,
100
+ center: { x, y },
121
101
  };
122
102
  }
123
103
 
124
104
  if (rx === 0 || ry === 0) {
125
- return getLineSegmentProperties(x1, y1, x, y, distance);
105
+ return {
106
+ rx,
107
+ ry,
108
+ startAngle: 0,
109
+ endAngle: 0,
110
+ center: { x, y },
111
+ };
126
112
  }
127
113
 
128
114
  const dx = (x1 - x) / 2;
@@ -144,6 +130,7 @@ const getSegmentProperties = (
144
130
  const cSquareRootDenom = rx ** 2 * transformedPoint.y ** 2 + ry ** 2 * transformedPoint.x ** 2;
145
131
 
146
132
  let cRadicand = cSquareNumerator / cSquareRootDenom;
133
+ /* istanbul ignore next @preserve */
147
134
  cRadicand = cRadicand < 0 ? 0 : cRadicand;
148
135
  const cCoef = (LAF !== SF ? 1 : -1) * sqrt(cRadicand);
149
136
  const transformedCenter = {
@@ -176,15 +163,7 @@ const getSegmentProperties = (
176
163
  }
177
164
  sweepAngle %= 2 * PI;
178
165
 
179
- const alpha = startAngle + sweepAngle * (distance || 0);
180
166
  const endAngle = startAngle + sweepAngle;
181
- const ellipseComponentX = rx * cos(alpha);
182
- const ellipseComponentY = ry * sin(alpha);
183
-
184
- const point = {
185
- x: cos(xRotRad) * ellipseComponentX - sin(xRotRad) * ellipseComponentY + center.x,
186
- y: sin(xRotRad) * ellipseComponentX + cos(xRotRad) * ellipseComponentY + center.y,
187
- };
188
167
 
189
168
  // to be used later
190
169
  // point.ellipticalArcStartAngle = startAngle;
@@ -198,20 +177,218 @@ const getSegmentProperties = (
198
177
  // point.box = minmax(center.x, center.y, rx, ry, xRotRad, startAngle, startAngle + sweepAngle);
199
178
 
200
179
  return {
201
- point,
202
180
  center,
203
- angle: alpha,
204
181
  startAngle,
205
182
  endAngle,
206
- radiusX: rx,
207
- radiusY: ry,
208
- get length() {
209
- return ellipticalArcLength(rx, ry, sweepAngle);
183
+ rx,
184
+ ry,
185
+ };
186
+ };
187
+
188
+ /**
189
+ * Returns the length of an Arc segment.
190
+ *
191
+ * @param x1 the starting point X
192
+ * @param y1 the starting point Y
193
+ * @param c1x the first control point X
194
+ * @param c1y the first control point Y
195
+ * @param c2x the second control point X
196
+ * @param c2y the second control point Y
197
+ * @param x2 the ending point X
198
+ * @param y2 the ending point Y
199
+ * @returns the length of the Arc segment
200
+ */
201
+ export const getArcLength = (
202
+ x1: number,
203
+ y1: number,
204
+ RX: number,
205
+ RY: number,
206
+ angle: number,
207
+ LAF: number,
208
+ SF: number,
209
+ x: number,
210
+ y: number,
211
+ ) => {
212
+ const { rx, ry, startAngle, endAngle } = getArcProps(x1, y1, RX, RY, angle, LAF, SF, x, y);
213
+ return ellipticalArcLength(rx, ry, endAngle - startAngle);
214
+ };
215
+
216
+ /**
217
+ * Returns a point along an Arc segment at a given distance.
218
+ *
219
+ * @param x1 the starting point X
220
+ * @param y1 the starting point Y
221
+ * @param RX the radius on X axis
222
+ * @param RY the radius on Y axis
223
+ * @param angle the ellipse rotation in degrees
224
+ * @param LAF the large arc flag
225
+ * @param SF the sweep flag
226
+ * @param x2 the ending point X
227
+ * @param y2 the ending point Y
228
+ * @param distance a [0-1] ratio
229
+ * @returns a point along the Arc segment
230
+ */
231
+ export const getPointAtArcLength = (
232
+ x1: number,
233
+ y1: number,
234
+ RX: number,
235
+ RY: number,
236
+ angle: number,
237
+ LAF: number,
238
+ SF: number,
239
+ x: number,
240
+ y: number,
241
+ distance?: number,
242
+ ) => {
243
+ let point = { x: x1, y: y1 };
244
+ const { center, rx, ry, startAngle, endAngle } = getArcProps(x1, y1, RX, RY, angle, LAF, SF, x, y);
245
+ const length = ellipticalArcLength(rx, ry, endAngle - startAngle);
246
+
247
+ /* istanbul ignore else @preserve */
248
+ if (typeof distance === 'number') {
249
+ if (distance <= 0) {
250
+ point = { x: x1, y: y1 };
251
+ } else if (distance >= length) {
252
+ point = { x, y };
253
+ } else {
254
+ /* istanbul ignore next @preserve */
255
+ if (x1 === x && y1 === y) {
256
+ return { x, y };
257
+ }
258
+ /* istanbul ignore next @preserve */
259
+ if (rx === 0 || ry === 0) {
260
+ return getPointAtLineLength(x1, y1, x, y, distance);
261
+ }
262
+ const { PI, cos, sin } = Math;
263
+ const sweepAngle = endAngle - startAngle;
264
+ const xRot = ((angle % 360) + 360) % 360;
265
+ const xRotRad = xRot * (PI / 180);
266
+ const alpha = startAngle + sweepAngle * (distance / length);
267
+ const ellipseComponentX = rx * cos(alpha);
268
+ const ellipseComponentY = ry * sin(alpha);
269
+
270
+ point = {
271
+ x: cos(xRotRad) * ellipseComponentX - sin(xRotRad) * ellipseComponentY + center.x,
272
+ y: sin(xRotRad) * ellipseComponentX + cos(xRotRad) * ellipseComponentY + center.y,
273
+ };
274
+ }
275
+ }
276
+
277
+ return point;
278
+ };
279
+
280
+ /**
281
+ * Returns the bounding box for an Arc segment.
282
+ * @see https://github.com/herrstrietzel/svg-pathdata-getbbox
283
+ *
284
+ * @param x1 the starting point X
285
+ * @param y1 the starting point Y
286
+ * @param RX the radius on X axis
287
+ * @param RY the radius on Y axis
288
+ * @param angle the ellipse rotation in degrees
289
+ * @param LAF the large arc flag
290
+ * @param SF the sweep flag
291
+ * @param x2 the ending point X
292
+ * @param y2 the ending point Y
293
+ * @returns the extrema of the Arc segment
294
+ *
295
+ */
296
+ export const getArcBBox = (
297
+ x1: number,
298
+ y1: number,
299
+ RX: number,
300
+ RY: number,
301
+ angle: number,
302
+ LAF: number,
303
+ SF: number,
304
+ x: number,
305
+ y: number,
306
+ ) => {
307
+ const { center, rx, ry, startAngle, endAngle } = getArcProps(x1, y1, RX, RY, angle, LAF, SF, x, y);
308
+ const deltaAngle = endAngle - startAngle;
309
+
310
+ // final on path point
311
+ const p = { x, y };
312
+
313
+ // circle/elipse center coordinates
314
+ const [cx, cy] = [center.x, center.y];
315
+
316
+ // collect extreme points – add end point
317
+ const extremes = [p];
318
+
319
+ // rotation to radians
320
+ const alpha = (angle * Math.PI) / 180;
321
+ const tan = Math.tan(alpha);
322
+
323
+ /**
324
+ * find min/max from zeroes of directional derivative along x and y
325
+ * along x axis
326
+ */
327
+ const theta = Math.atan2(-ry * tan, rx);
328
+ const angle1 = theta;
329
+ const angle2 = theta + Math.PI;
330
+ const angle3 = Math.atan2(ry, rx * tan);
331
+ const angle4 = angle3 + Math.PI;
332
+
333
+ // inner bounding box
334
+ const xArr = [x1, x];
335
+ const yArr = [y1, y];
336
+ const xMin = Math.min(...xArr);
337
+ const xMax = Math.max(...xArr);
338
+ const yMin = Math.min(...yArr);
339
+ const yMax = Math.max(...yArr);
340
+
341
+ // on path point close after start
342
+ const angleAfterStart = endAngle - deltaAngle * 0.001;
343
+ const pP2 = arc(angleAfterStart, cx, cy, rx, ry, alpha);
344
+
345
+ // on path point close before end
346
+ const angleBeforeEnd = endAngle - deltaAngle * 0.999;
347
+ const pP3 = arc(angleBeforeEnd, cx, cy, rx, ry, alpha);
348
+
349
+ /**
350
+ * expected extremes
351
+ * if leaving inner bounding box
352
+ * (between segment start and end point)
353
+ * otherwise exclude elliptic extreme points
354
+ */
355
+
356
+ // right
357
+ // istanbul ignore if @preserve
358
+ if (pP2.x > xMax || pP3.x > xMax) {
359
+ // get point for this theta
360
+ extremes.push(arc(angle1, cx, cy, rx, ry, alpha));
361
+ }
362
+
363
+ // left
364
+ // istanbul ignore if @preserve
365
+ if (pP2.x < xMin || pP3.x < xMin) {
366
+ // get anti-symmetric point
367
+ extremes.push(arc(angle2, cx, cy, rx, ry, alpha));
368
+ }
369
+
370
+ // top
371
+ // istanbul ignore if @preserve
372
+ if (pP2.y < yMin || pP3.y < yMin) {
373
+ // get anti-symmetric point
374
+ extremes.push(arc(angle4, cx, cy, rx, ry, alpha));
375
+ }
376
+
377
+ // bottom
378
+ // istanbul ignore if @preserve
379
+ if (pP2.y > yMax || pP3.y > yMax) {
380
+ // get point for this theta
381
+ extremes.push(arc(angle3, cx, cy, rx, ry, alpha));
382
+ }
383
+
384
+ return {
385
+ min: {
386
+ x: Math.min(...extremes.map(n => n.x)),
387
+ y: Math.min(...extremes.map(n => n.y)),
210
388
  },
211
- get bbox() {
212
- return minmax(center.x, center.y, rx, ry, xRotRad, startAngle, startAngle + sweepAngle);
389
+ max: {
390
+ x: Math.max(...extremes.map(n => n.x)),
391
+ y: Math.max(...extremes.map(n => n.y)),
213
392
  },
214
393
  };
215
394
  };
216
-
217
- export default getSegmentProperties;
@@ -1,4 +1,14 @@
1
- import { type Point } from '../types';
1
+ import type {
2
+ PointTuple,
3
+ DerivedPoint,
4
+ QuadPoints,
5
+ CubicPoints,
6
+ DerivedQuadPoints,
7
+ DerivedCubicPoints,
8
+ QuadCoordinates,
9
+ CubicCoordinates,
10
+ DeriveCallback,
11
+ } from '../types';
2
12
 
3
13
  /**
4
14
  * Tools from bezier.js by Mike 'Pomax' Kamermans
@@ -37,25 +47,6 @@ const Cvalues = [
37
47
  0.0123412297999871995468056670700372915759, 0.0123412297999871995468056670700372915759,
38
48
  ];
39
49
 
40
- type DerivedPoint = Point & { t: number };
41
- type QuadPoints = [Point, Point, Point, Point, Point, Point];
42
- type CubicPoints = [Point, Point, Point, Point, Point, Point, Point, Point];
43
- type DerivedQuadPoints = [DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint, DerivedPoint];
44
- type DerivedCubicPoints = [
45
- DerivedPoint,
46
- DerivedPoint,
47
- DerivedPoint,
48
- DerivedPoint,
49
- DerivedPoint,
50
- DerivedPoint,
51
- DerivedPoint,
52
- DerivedPoint,
53
- ];
54
- export type QuadCoordinates = [number, number, number, number, number, number];
55
- export type CubicCoordinates = [number, number, number, number, number, number, number, number];
56
-
57
- type DeriveCallback = (t: number) => Point;
58
-
59
50
  /**
60
51
  *
61
52
  * @param points
@@ -195,15 +186,15 @@ export const minmaxQ = (A: [number, number, number]) => {
195
186
  const min = Math.min(A[0], A[2]);
196
187
  const max = Math.max(A[0], A[2]);
197
188
 
198
- /* istanbul ignore else @preserve */
189
+ /* istanbul ignore next @preserve */
199
190
  if (A[1] >= A[0] ? A[2] >= A[1] : A[2] <= A[1]) {
200
191
  // if no extremum in ]0,1[
201
- return [min, max] as [number, number];
192
+ return [min, max] as PointTuple;
202
193
  }
203
194
 
204
195
  // check if the extremum E is min or max
205
196
  const E = (A[0] * A[2] - A[1] * A[1]) / (A[0] - 2 * A[1] + A[2]);
206
- return (E < min ? [E, max] : [min, E]) as [number, number];
197
+ return (E < min ? [E, max] : [min, E]) as PointTuple;
207
198
  };
208
199
 
209
200
  /**
@@ -215,11 +206,11 @@ export const minmaxC = (A: [number, number, number, number]) => {
215
206
  const K = A[0] - 3 * A[1] + 3 * A[2] - A[3];
216
207
 
217
208
  // if the polynomial is (almost) quadratic and not cubic
218
- /* istanbul ignore else @preserve */
209
+ /* istanbul ignore next @preserve */
219
210
  if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
220
211
  if (A[0] === A[3] && A[0] === A[1]) {
221
212
  // no curve, point targeting same location
222
- return [A[0], A[3]] as [number, number];
213
+ return [A[0], A[3]] as PointTuple;
223
214
  }
224
215
 
225
216
  return minmaxQ([A[0], -0.5 * A[0] + 1.5 * A[1], A[0] - 3 * A[1] + 3 * A[2]]);
@@ -230,7 +221,7 @@ export const minmaxC = (A: [number, number, number, number]) => {
230
221
 
231
222
  // if the polynomial is monotone in [0,1]
232
223
  if (T <= 0) {
233
- return [Math.min(A[0], A[3]), Math.max(A[0], A[3])] as [number, number];
224
+ return [Math.min(A[0], A[3]), Math.max(A[0], A[3])] as PointTuple;
234
225
  }
235
226
  const S = Math.sqrt(T);
236
227
 
@@ -241,6 +232,7 @@ export const minmaxC = (A: [number, number, number, number]) => {
241
232
  const L = A[0] - 2 * A[1] + A[2];
242
233
  // check local extrema
243
234
  for (let R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
235
+ // istanbul ignore next @preserve
244
236
  if (R > 0 && R < 1) {
245
237
  // if the extrema is for R in [0,1]
246
238
  const Q =
@@ -257,5 +249,5 @@ export const minmaxC = (A: [number, number, number, number]) => {
257
249
  }
258
250
  }
259
251
 
260
- return [min, max] as [number, number];
252
+ return [min, max] as PointTuple;
261
253
  };
@@ -1,4 +1,5 @@
1
- import { length, minmaxC, type CubicCoordinates } from './bezier';
1
+ import { length, minmaxC } from './bezier';
2
+ import { type CubicCoordinates } from '../types';
2
3
 
3
4
  /**
4
5
  * Returns a {x,y} point at a given length of a CubicBezier segment.
@@ -14,7 +15,7 @@ import { length, minmaxC, type CubicCoordinates } from './bezier';
14
15
  * @param t a [0-1] ratio
15
16
  * @returns the point at cubic-bezier segment length
16
17
  */
17
- const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: CubicCoordinates, t: number) => {
18
+ export const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: CubicCoordinates, t: number) => {
18
19
  const t1 = 1 - t;
19
20
  return {
20
21
  x: t1 ** 3 * x1 + 3 * t1 ** 2 * t * c1x + 3 * t1 * t ** 2 * c2x + t ** 3 * x2,
@@ -23,7 +24,7 @@ const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: Cubi
23
24
  };
24
25
 
25
26
  /**
26
- * Returns the properties of a CubicBezier segment.
27
+ * Returns the length of a CubicBezier segment.
27
28
  *
28
29
  * @param x1 the starting point X
29
30
  * @param y1 the starting point Y
@@ -33,10 +34,36 @@ const getPointAtCubicSegmentLength = ([x1, y1, c1x, c1y, c2x, c2y, x2, y2]: Cubi
33
34
  * @param c2y the second control point Y
34
35
  * @param x2 the ending point X
35
36
  * @param y2 the ending point Y
36
- * @param distance the point distance
37
- * @returns the segment length, point at length and the bounding box
37
+ * @returns the CubicBezier segment length
38
38
  */
39
- const getSegmentProperties = (
39
+ export const getCubicLength = (
40
+ x1: number,
41
+ y1: number,
42
+ c1x: number,
43
+ c1y: number,
44
+ c2x: number,
45
+ c2y: number,
46
+ x2: number,
47
+ y2: number,
48
+ ) => {
49
+ return length([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
50
+ };
51
+
52
+ /**
53
+ * Returns the point along a CubicBezier segment at a given distance.
54
+ *
55
+ * @param x1 the starting point X
56
+ * @param y1 the starting point Y
57
+ * @param c1x the first control point X
58
+ * @param c1y the first control point Y
59
+ * @param c2x the second control point X
60
+ * @param c2y the second control point Y
61
+ * @param x2 the ending point X
62
+ * @param y2 the ending point Y
63
+ * @param distance the distance to look at
64
+ * @returns the point at CubicBezier length
65
+ */
66
+ export const getPointAtCubicLength = (
40
67
  x1: number,
41
68
  y1: number,
42
69
  c1x: number,
@@ -48,34 +75,48 @@ const getSegmentProperties = (
48
75
  distance?: number,
49
76
  ) => {
50
77
  const distanceIsNumber = typeof distance === 'number';
51
- let POINT = { x: x1, y: y1 };
52
- const getLength = () => length([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
53
-
78
+ let point = { x: x1, y: y1 };
79
+ /* istanbul ignore else @preserve */
54
80
  if (distanceIsNumber) {
55
- const currentLength = getLength();
81
+ const currentLength = length([x1, y1, c1x, c1y, c2x, c2y, x2, y2]);
56
82
  if (distance <= 0) {
57
83
  // first point already defined
58
84
  } else if (distance >= currentLength) {
59
- POINT = { x: x2, y: y2 };
85
+ point = { x: x2, y: y2 };
60
86
  } else {
61
- POINT = getPointAtCubicSegmentLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2], distance / currentLength);
87
+ point = getPointAtCubicSegmentLength([x1, y1, c1x, c1y, c2x, c2y, x2, y2], distance / currentLength);
62
88
  }
63
89
  }
90
+ return point;
91
+ };
64
92
 
93
+ /**
94
+ * Returns the boundig box of a CubicBezier segment.
95
+ *
96
+ * @param x1 the starting point X
97
+ * @param y1 the starting point Y
98
+ * @param c1x the first control point X
99
+ * @param c1y the first control point Y
100
+ * @param c2x the second control point X
101
+ * @param c2y the second control point Y
102
+ * @param x2 the ending point X
103
+ * @param y2 the ending point Y
104
+ * @returns the point at CubicBezier length
105
+ */
106
+ export const getCubicBBox = (
107
+ x1: number,
108
+ y1: number,
109
+ c1x: number,
110
+ c1y: number,
111
+ c2x: number,
112
+ c2y: number,
113
+ x2: number,
114
+ y2: number,
115
+ ) => {
116
+ const cxMinMax = minmaxC([x1, c1x, c2x, x2]);
117
+ const cyMinMax = minmaxC([y1, c1y, c2y, y2]);
65
118
  return {
66
- point: POINT,
67
- get length() {
68
- return getLength();
69
- },
70
- get bbox() {
71
- const cxMinMax = minmaxC([x1, c1x, c2x, x2]);
72
- const cyMinMax = minmaxC([y1, c1y, c2y, y2]);
73
- return {
74
- min: { x: cxMinMax[0], y: cyMinMax[0] },
75
- max: { x: cxMinMax[1], y: cyMinMax[1] },
76
- };
77
- },
119
+ min: { x: cxMinMax[0], y: cyMinMax[0] },
120
+ max: { x: cxMinMax[1], y: cyMinMax[1] },
78
121
  };
79
122
  };
80
-
81
- export default getSegmentProperties;
@@ -1,3 +1,5 @@
1
+ import { type PointTuple } from '../types';
2
+
1
3
  /**
2
4
  * Returns the square root of the distance
3
5
  * between two given points.
@@ -6,7 +8,7 @@
6
8
  * @param b the second point coordinates
7
9
  * @returns the distance value
8
10
  */
9
- const distanceSquareRoot = (a: [number, number], b: [number, number]): number => {
11
+ const distanceSquareRoot = (a: PointTuple, b: PointTuple): number => {
10
12
  return Math.sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]));
11
13
  };
12
14