@emasoft/svg-matrix 1.1.0 → 1.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 (55) hide show
  1. package/bin/svg-matrix.js +7 -6
  2. package/bin/svgm.js +109 -40
  3. package/dist/svg-matrix.min.js +7 -7
  4. package/dist/svg-toolbox.min.js +148 -228
  5. package/dist/svgm.min.js +152 -232
  6. package/dist/version.json +5 -5
  7. package/package.json +1 -1
  8. package/scripts/postinstall.js +72 -41
  9. package/scripts/test-postinstall.js +18 -16
  10. package/scripts/version-sync.js +78 -60
  11. package/src/animation-optimization.js +190 -98
  12. package/src/animation-references.js +11 -3
  13. package/src/arc-length.js +23 -20
  14. package/src/bezier-analysis.js +9 -13
  15. package/src/bezier-intersections.js +18 -4
  16. package/src/browser-verify.js +35 -8
  17. package/src/clip-path-resolver.js +285 -114
  18. package/src/convert-path-data.js +20 -8
  19. package/src/css-specificity.js +33 -9
  20. package/src/douglas-peucker.js +272 -141
  21. package/src/geometry-to-path.js +79 -22
  22. package/src/gjk-collision.js +287 -126
  23. package/src/index.js +56 -21
  24. package/src/inkscape-support.js +122 -101
  25. package/src/logger.js +43 -27
  26. package/src/marker-resolver.js +201 -121
  27. package/src/mask-resolver.js +231 -98
  28. package/src/matrix.js +9 -5
  29. package/src/mesh-gradient.js +22 -14
  30. package/src/off-canvas-detection.js +53 -17
  31. package/src/path-optimization.js +356 -171
  32. package/src/path-simplification.js +671 -256
  33. package/src/pattern-resolver.js +1 -3
  34. package/src/polygon-clip.js +396 -78
  35. package/src/svg-boolean-ops.js +90 -23
  36. package/src/svg-collections.js +1546 -667
  37. package/src/svg-flatten.js +152 -38
  38. package/src/svg-matrix-lib.js +2 -2
  39. package/src/svg-parser.js +5 -1
  40. package/src/svg-rendering-context.js +3 -1
  41. package/src/svg-toolbox-lib.js +2 -2
  42. package/src/svg-toolbox.js +99 -457
  43. package/src/svg-validation-data.js +513 -345
  44. package/src/svg2-polyfills.js +156 -93
  45. package/src/svgm-lib.js +8 -4
  46. package/src/transform-optimization.js +168 -51
  47. package/src/transforms2d.js +73 -40
  48. package/src/transforms3d.js +34 -27
  49. package/src/use-symbol-resolver.js +175 -76
  50. package/src/vector.js +80 -44
  51. package/src/vendor/inkscape-hatch-polyfill.js +143 -108
  52. package/src/vendor/inkscape-hatch-polyfill.min.js +291 -1
  53. package/src/vendor/inkscape-mesh-polyfill.js +953 -766
  54. package/src/vendor/inkscape-mesh-polyfill.min.js +896 -1
  55. package/src/verification.js +3 -4
@@ -35,19 +35,19 @@
35
35
  * @module path-simplification
36
36
  */
37
37
 
38
- import Decimal from 'decimal.js';
38
+ import Decimal from "decimal.js";
39
39
 
40
40
  // Set high precision for all calculations
41
41
  Decimal.set({ precision: 80 });
42
42
 
43
43
  // Helper to convert to Decimal
44
- const D = x => (x instanceof Decimal ? x : new Decimal(x));
44
+ const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
45
45
 
46
46
  // Near-zero threshold for comparisons (much smaller than SVGO's!)
47
- const EPSILON = new Decimal('1e-40');
47
+ const EPSILON = new Decimal("1e-40");
48
48
 
49
49
  // Default tolerance for simplification (user-configurable)
50
- const DEFAULT_TOLERANCE = new Decimal('1e-10');
50
+ const DEFAULT_TOLERANCE = new Decimal("1e-10");
51
51
 
52
52
  /**
53
53
  * Implementation of atan2 using Decimal.js (which doesn't provide it natively).
@@ -57,12 +57,14 @@ const DEFAULT_TOLERANCE = new Decimal('1e-10');
57
57
  * @returns {Decimal} Angle in radians (-π to π)
58
58
  */
59
59
  function decimalAtan2(y, x) {
60
- if (y === null || y === undefined) throw new Error('decimalAtan2: y parameter is null or undefined');
61
- if (x === null || x === undefined) throw new Error('decimalAtan2: x parameter is null or undefined');
60
+ if (y === null || y === undefined)
61
+ throw new Error("decimalAtan2: y parameter is null or undefined");
62
+ if (x === null || x === undefined)
63
+ throw new Error("decimalAtan2: x parameter is null or undefined");
62
64
  const yD = D(y);
63
65
  const xD = D(x);
64
- if (!yD.isFinite()) throw new Error('decimalAtan2: y must be finite');
65
- if (!xD.isFinite()) throw new Error('decimalAtan2: x must be finite');
66
+ if (!yD.isFinite()) throw new Error("decimalAtan2: y must be finite");
67
+ if (!xD.isFinite()) throw new Error("decimalAtan2: x must be finite");
66
68
  const PI = Decimal.acos(-1);
67
69
 
68
70
  // Check x=0 cases first to avoid division by zero
@@ -102,12 +104,14 @@ function decimalAtan2(y, x) {
102
104
  * @returns {{x: Decimal, y: Decimal}} Point object
103
105
  */
104
106
  export function point(x, y) {
105
- if (x === null || x === undefined) throw new Error('point: x parameter is null or undefined');
106
- if (y === null || y === undefined) throw new Error('point: y parameter is null or undefined');
107
+ if (x === null || x === undefined)
108
+ throw new Error("point: x parameter is null or undefined");
109
+ if (y === null || y === undefined)
110
+ throw new Error("point: y parameter is null or undefined");
107
111
  const xD = D(x);
108
112
  const yD = D(y);
109
- if (!xD.isFinite()) throw new Error('point: x must be finite');
110
- if (!yD.isFinite()) throw new Error('point: y must be finite');
113
+ if (!xD.isFinite()) throw new Error("point: x must be finite");
114
+ if (!yD.isFinite()) throw new Error("point: y must be finite");
111
115
  return { x: xD, y: yD };
112
116
  }
113
117
 
@@ -119,9 +123,12 @@ export function point(x, y) {
119
123
  * @returns {Decimal} Squared distance
120
124
  */
121
125
  export function distanceSquared(p1, p2) {
122
- if (!p1 || !p2) throw new Error('distanceSquared: points cannot be null or undefined');
123
- if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal)) throw new Error('distanceSquared: p1 must have Decimal x and y properties');
124
- if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal)) throw new Error('distanceSquared: p2 must have Decimal x and y properties');
126
+ if (!p1 || !p2)
127
+ throw new Error("distanceSquared: points cannot be null or undefined");
128
+ if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal))
129
+ throw new Error("distanceSquared: p1 must have Decimal x and y properties");
130
+ if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal))
131
+ throw new Error("distanceSquared: p2 must have Decimal x and y properties");
125
132
  const dx = p2.x.minus(p1.x);
126
133
  const dy = p2.y.minus(p1.y);
127
134
  return dx.mul(dx).plus(dy.mul(dy));
@@ -149,13 +156,26 @@ export function distance(p1, p2) {
149
156
  * @returns {Decimal} Perpendicular distance
150
157
  */
151
158
  export function pointToLineDistance(pt, lineStart, lineEnd) {
152
- if (!pt || !lineStart || !lineEnd) throw new Error('pointToLineDistance: points cannot be null or undefined');
153
- if (!(pt.x instanceof Decimal) || !(pt.y instanceof Decimal)) throw new Error('pointToLineDistance: pt must have Decimal x and y properties');
154
- if (!(lineStart.x instanceof Decimal) || !(lineStart.y instanceof Decimal)) throw new Error('pointToLineDistance: lineStart must have Decimal x and y properties');
155
- if (!(lineEnd.x instanceof Decimal) || !(lineEnd.y instanceof Decimal)) throw new Error('pointToLineDistance: lineEnd must have Decimal x and y properties');
156
- const x0 = pt.x, y0 = pt.y;
157
- const x1 = lineStart.x, y1 = lineStart.y;
158
- const x2 = lineEnd.x, y2 = lineEnd.y;
159
+ if (!pt || !lineStart || !lineEnd)
160
+ throw new Error("pointToLineDistance: points cannot be null or undefined");
161
+ if (!(pt.x instanceof Decimal) || !(pt.y instanceof Decimal))
162
+ throw new Error(
163
+ "pointToLineDistance: pt must have Decimal x and y properties",
164
+ );
165
+ if (!(lineStart.x instanceof Decimal) || !(lineStart.y instanceof Decimal))
166
+ throw new Error(
167
+ "pointToLineDistance: lineStart must have Decimal x and y properties",
168
+ );
169
+ if (!(lineEnd.x instanceof Decimal) || !(lineEnd.y instanceof Decimal))
170
+ throw new Error(
171
+ "pointToLineDistance: lineEnd must have Decimal x and y properties",
172
+ );
173
+ const x0 = pt.x,
174
+ y0 = pt.y;
175
+ const x1 = lineStart.x,
176
+ y1 = lineStart.y;
177
+ const x2 = lineEnd.x,
178
+ y2 = lineEnd.y;
159
179
 
160
180
  const dx = x2.minus(x1);
161
181
  const dy = y2.minus(y1);
@@ -168,7 +188,12 @@ export function pointToLineDistance(pt, lineStart, lineEnd) {
168
188
  }
169
189
 
170
190
  // Numerator: |(y2-y1)*x0 - (x2-x1)*y0 + x2*y1 - y2*x1|
171
- const numerator = dy.mul(x0).minus(dx.mul(y0)).plus(x2.mul(y1)).minus(y2.mul(x1)).abs();
191
+ const numerator = dy
192
+ .mul(x0)
193
+ .minus(dx.mul(y0))
194
+ .plus(x2.mul(y1))
195
+ .minus(y2.mul(x1))
196
+ .abs();
172
197
 
173
198
  // Denominator: sqrt((y2-y1)² + (x2-x1)²)
174
199
  const denominator = lineLengthSq.sqrt();
@@ -185,10 +210,14 @@ export function pointToLineDistance(pt, lineStart, lineEnd) {
185
210
  * @returns {Decimal} Cross product (positive = CCW, negative = CW, zero = collinear)
186
211
  */
187
212
  export function crossProduct(p1, p2, p3) {
188
- if (!p1 || !p2 || !p3) throw new Error('crossProduct: points cannot be null or undefined');
189
- if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal)) throw new Error('crossProduct: p1 must have Decimal x and y properties');
190
- if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal)) throw new Error('crossProduct: p2 must have Decimal x and y properties');
191
- if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal)) throw new Error('crossProduct: p3 must have Decimal x and y properties');
213
+ if (!p1 || !p2 || !p3)
214
+ throw new Error("crossProduct: points cannot be null or undefined");
215
+ if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal))
216
+ throw new Error("crossProduct: p1 must have Decimal x and y properties");
217
+ if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal))
218
+ throw new Error("crossProduct: p2 must have Decimal x and y properties");
219
+ if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal))
220
+ throw new Error("crossProduct: p3 must have Decimal x and y properties");
192
221
  const v1x = p2.x.minus(p1.x);
193
222
  const v1y = p2.y.minus(p1.y);
194
223
  const v2x = p3.x.minus(p1.x);
@@ -212,26 +241,41 @@ export function crossProduct(p1, p2, p3) {
212
241
  * @returns {{x: Decimal, y: Decimal}} Point on curve
213
242
  */
214
243
  export function evaluateCubicBezier(p0, p1, p2, p3, t) {
215
- if (!p0 || !p1 || !p2 || !p3) throw new Error('evaluateCubicBezier: points cannot be null or undefined');
216
- if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal)) throw new Error('evaluateCubicBezier: p0 must have Decimal x and y properties');
217
- if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal)) throw new Error('evaluateCubicBezier: p1 must have Decimal x and y properties');
218
- if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal)) throw new Error('evaluateCubicBezier: p2 must have Decimal x and y properties');
219
- if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal)) throw new Error('evaluateCubicBezier: p3 must have Decimal x and y properties');
220
- if (t === null || t === undefined) throw new Error('evaluateCubicBezier: t parameter is null or undefined');
244
+ if (!p0 || !p1 || !p2 || !p3)
245
+ throw new Error("evaluateCubicBezier: points cannot be null or undefined");
246
+ if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal))
247
+ throw new Error(
248
+ "evaluateCubicBezier: p0 must have Decimal x and y properties",
249
+ );
250
+ if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal))
251
+ throw new Error(
252
+ "evaluateCubicBezier: p1 must have Decimal x and y properties",
253
+ );
254
+ if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal))
255
+ throw new Error(
256
+ "evaluateCubicBezier: p2 must have Decimal x and y properties",
257
+ );
258
+ if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal))
259
+ throw new Error(
260
+ "evaluateCubicBezier: p3 must have Decimal x and y properties",
261
+ );
262
+ if (t === null || t === undefined)
263
+ throw new Error("evaluateCubicBezier: t parameter is null or undefined");
221
264
  const tD = D(t);
222
- if (!tD.isFinite()) throw new Error('evaluateCubicBezier: t must be finite');
223
- if (tD.lessThan(0) || tD.greaterThan(1)) throw new Error('evaluateCubicBezier: t must be in range [0, 1]');
265
+ if (!tD.isFinite()) throw new Error("evaluateCubicBezier: t must be finite");
266
+ if (tD.lessThan(0) || tD.greaterThan(1))
267
+ throw new Error("evaluateCubicBezier: t must be in range [0, 1]");
224
268
  const oneMinusT = D(1).minus(tD);
225
269
 
226
270
  // Bernstein basis polynomials
227
- const b0 = oneMinusT.pow(3); // (1-t)³
228
- const b1 = D(3).mul(oneMinusT.pow(2)).mul(tD); // 3(1-t)²t
229
- const b2 = D(3).mul(oneMinusT).mul(tD.pow(2)); // 3(1-t)t²
230
- const b3 = tD.pow(3); // t³
271
+ const b0 = oneMinusT.pow(3); // (1-t)³
272
+ const b1 = D(3).mul(oneMinusT.pow(2)).mul(tD); // 3(1-t)²t
273
+ const b2 = D(3).mul(oneMinusT).mul(tD.pow(2)); // 3(1-t)t²
274
+ const b3 = tD.pow(3); // t³
231
275
 
232
276
  return {
233
277
  x: b0.mul(p0.x).plus(b1.mul(p1.x)).plus(b2.mul(p2.x)).plus(b3.mul(p3.x)),
234
- y: b0.mul(p0.y).plus(b1.mul(p1.y)).plus(b2.mul(p2.y)).plus(b3.mul(p3.y))
278
+ y: b0.mul(p0.y).plus(b1.mul(p1.y)).plus(b2.mul(p2.y)).plus(b3.mul(p3.y)),
235
279
  };
236
280
  }
237
281
 
@@ -246,24 +290,41 @@ export function evaluateCubicBezier(p0, p1, p2, p3, t) {
246
290
  * @returns {{x: Decimal, y: Decimal}} Point on curve
247
291
  */
248
292
  export function evaluateQuadraticBezier(p0, p1, p2, t) {
249
- if (!p0 || !p1 || !p2) throw new Error('evaluateQuadraticBezier: points cannot be null or undefined');
250
- if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal)) throw new Error('evaluateQuadraticBezier: p0 must have Decimal x and y properties');
251
- if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal)) throw new Error('evaluateQuadraticBezier: p1 must have Decimal x and y properties');
252
- if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal)) throw new Error('evaluateQuadraticBezier: p2 must have Decimal x and y properties');
253
- if (t === null || t === undefined) throw new Error('evaluateQuadraticBezier: t parameter is null or undefined');
293
+ if (!p0 || !p1 || !p2)
294
+ throw new Error(
295
+ "evaluateQuadraticBezier: points cannot be null or undefined",
296
+ );
297
+ if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal))
298
+ throw new Error(
299
+ "evaluateQuadraticBezier: p0 must have Decimal x and y properties",
300
+ );
301
+ if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal))
302
+ throw new Error(
303
+ "evaluateQuadraticBezier: p1 must have Decimal x and y properties",
304
+ );
305
+ if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal))
306
+ throw new Error(
307
+ "evaluateQuadraticBezier: p2 must have Decimal x and y properties",
308
+ );
309
+ if (t === null || t === undefined)
310
+ throw new Error(
311
+ "evaluateQuadraticBezier: t parameter is null or undefined",
312
+ );
254
313
  const tD = D(t);
255
- if (!tD.isFinite()) throw new Error('evaluateQuadraticBezier: t must be finite');
256
- if (tD.lessThan(0) || tD.greaterThan(1)) throw new Error('evaluateQuadraticBezier: t must be in range [0, 1]');
314
+ if (!tD.isFinite())
315
+ throw new Error("evaluateQuadraticBezier: t must be finite");
316
+ if (tD.lessThan(0) || tD.greaterThan(1))
317
+ throw new Error("evaluateQuadraticBezier: t must be in range [0, 1]");
257
318
  const oneMinusT = D(1).minus(tD);
258
319
 
259
320
  // Bernstein basis polynomials
260
- const b0 = oneMinusT.pow(2); // (1-t)²
261
- const b1 = D(2).mul(oneMinusT).mul(tD); // 2(1-t)t
262
- const b2 = tD.pow(2); // t²
321
+ const b0 = oneMinusT.pow(2); // (1-t)²
322
+ const b1 = D(2).mul(oneMinusT).mul(tD); // 2(1-t)t
323
+ const b2 = tD.pow(2); // t²
263
324
 
264
325
  return {
265
326
  x: b0.mul(p0.x).plus(b1.mul(p1.x)).plus(b2.mul(p2.x)),
266
- y: b0.mul(p0.y).plus(b1.mul(p1.y)).plus(b2.mul(p2.y))
327
+ y: b0.mul(p0.y).plus(b1.mul(p1.y)).plus(b2.mul(p2.y)),
267
328
  };
268
329
  }
269
330
 
@@ -277,17 +338,22 @@ export function evaluateQuadraticBezier(p0, p1, p2, t) {
277
338
  * @returns {{x: Decimal, y: Decimal}} Point on line
278
339
  */
279
340
  export function evaluateLine(p0, p1, t) {
280
- if (!p0 || !p1) throw new Error('evaluateLine: points cannot be null or undefined');
281
- if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal)) throw new Error('evaluateLine: p0 must have Decimal x and y properties');
282
- if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal)) throw new Error('evaluateLine: p1 must have Decimal x and y properties');
283
- if (t === null || t === undefined) throw new Error('evaluateLine: t parameter is null or undefined');
341
+ if (!p0 || !p1)
342
+ throw new Error("evaluateLine: points cannot be null or undefined");
343
+ if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal))
344
+ throw new Error("evaluateLine: p0 must have Decimal x and y properties");
345
+ if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal))
346
+ throw new Error("evaluateLine: p1 must have Decimal x and y properties");
347
+ if (t === null || t === undefined)
348
+ throw new Error("evaluateLine: t parameter is null or undefined");
284
349
  const tD = D(t);
285
- if (!tD.isFinite()) throw new Error('evaluateLine: t must be finite');
286
- if (tD.lessThan(0) || tD.greaterThan(1)) throw new Error('evaluateLine: t must be in range [0, 1]');
350
+ if (!tD.isFinite()) throw new Error("evaluateLine: t must be finite");
351
+ if (tD.lessThan(0) || tD.greaterThan(1))
352
+ throw new Error("evaluateLine: t must be in range [0, 1]");
287
353
  const oneMinusT = D(1).minus(tD);
288
354
  return {
289
355
  x: oneMinusT.mul(p0.x).plus(tD.mul(p1.x)),
290
- y: oneMinusT.mul(p0.y).plus(tD.mul(p1.y))
356
+ y: oneMinusT.mul(p0.y).plus(tD.mul(p1.y)),
291
357
  };
292
358
  }
293
359
 
@@ -311,15 +377,42 @@ export function evaluateLine(p0, p1, t) {
311
377
  * @param {Decimal} [tolerance=DEFAULT_TOLERANCE] - Maximum allowed deviation
312
378
  * @returns {{isStraight: boolean, maxDeviation: Decimal, verified: boolean}}
313
379
  */
314
- export function isCubicBezierStraight(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE) {
315
- if (!p0 || !p1 || !p2 || !p3) throw new Error('isCubicBezierStraight: points cannot be null or undefined');
316
- if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal)) throw new Error('isCubicBezierStraight: p0 must have Decimal x and y properties');
317
- if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal)) throw new Error('isCubicBezierStraight: p1 must have Decimal x and y properties');
318
- if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal)) throw new Error('isCubicBezierStraight: p2 must have Decimal x and y properties');
319
- if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal)) throw new Error('isCubicBezierStraight: p3 must have Decimal x and y properties');
320
- if (tolerance === null || tolerance === undefined) throw new Error('isCubicBezierStraight: tolerance parameter is null or undefined');
380
+ export function isCubicBezierStraight(
381
+ p0,
382
+ p1,
383
+ p2,
384
+ p3,
385
+ tolerance = DEFAULT_TOLERANCE,
386
+ ) {
387
+ if (!p0 || !p1 || !p2 || !p3)
388
+ throw new Error(
389
+ "isCubicBezierStraight: points cannot be null or undefined",
390
+ );
391
+ if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal))
392
+ throw new Error(
393
+ "isCubicBezierStraight: p0 must have Decimal x and y properties",
394
+ );
395
+ if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal))
396
+ throw new Error(
397
+ "isCubicBezierStraight: p1 must have Decimal x and y properties",
398
+ );
399
+ if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal))
400
+ throw new Error(
401
+ "isCubicBezierStraight: p2 must have Decimal x and y properties",
402
+ );
403
+ if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal))
404
+ throw new Error(
405
+ "isCubicBezierStraight: p3 must have Decimal x and y properties",
406
+ );
407
+ if (tolerance === null || tolerance === undefined)
408
+ throw new Error(
409
+ "isCubicBezierStraight: tolerance parameter is null or undefined",
410
+ );
321
411
  const tol = D(tolerance);
322
- if (!tol.isFinite() || tol.lessThan(0)) throw new Error('isCubicBezierStraight: tolerance must be a non-negative finite number');
412
+ if (!tol.isFinite() || tol.lessThan(0))
413
+ throw new Error(
414
+ "isCubicBezierStraight: tolerance must be a non-negative finite number",
415
+ );
323
416
 
324
417
  // Check if start and end are the same point (degenerate case)
325
418
  const chordLength = distance(p0, p3);
@@ -331,7 +424,7 @@ export function isCubicBezierStraight(p0, p1, p2, p3, tolerance = DEFAULT_TOLERA
331
424
  return {
332
425
  isStraight: maxDev.lessThan(tol),
333
426
  maxDeviation: maxDev,
334
- verified: true
427
+ verified: true,
335
428
  };
336
429
  }
337
430
 
@@ -345,7 +438,7 @@ export function isCubicBezierStraight(p0, p1, p2, p3, tolerance = DEFAULT_TOLERA
345
438
  return {
346
439
  isStraight: false,
347
440
  maxDeviation: maxControlDeviation,
348
- verified: true
441
+ verified: true,
349
442
  };
350
443
  }
351
444
 
@@ -366,7 +459,7 @@ export function isCubicBezierStraight(p0, p1, p2, p3, tolerance = DEFAULT_TOLERA
366
459
  return {
367
460
  isStraight: maxControlDeviation.lessThan(tol) && verified,
368
461
  maxDeviation: Decimal.max(maxControlDeviation, maxSampleDeviation),
369
- verified: true
462
+ verified: true,
370
463
  };
371
464
  }
372
465
 
@@ -379,14 +472,37 @@ export function isCubicBezierStraight(p0, p1, p2, p3, tolerance = DEFAULT_TOLERA
379
472
  * @param {Decimal} [tolerance=DEFAULT_TOLERANCE] - Maximum allowed deviation
380
473
  * @returns {{isStraight: boolean, maxDeviation: Decimal, verified: boolean}}
381
474
  */
382
- export function isQuadraticBezierStraight(p0, p1, p2, tolerance = DEFAULT_TOLERANCE) {
383
- if (!p0 || !p1 || !p2) throw new Error('isQuadraticBezierStraight: points cannot be null or undefined');
384
- if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal)) throw new Error('isQuadraticBezierStraight: p0 must have Decimal x and y properties');
385
- if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal)) throw new Error('isQuadraticBezierStraight: p1 must have Decimal x and y properties');
386
- if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal)) throw new Error('isQuadraticBezierStraight: p2 must have Decimal x and y properties');
387
- if (tolerance === null || tolerance === undefined) throw new Error('isQuadraticBezierStraight: tolerance parameter is null or undefined');
475
+ export function isQuadraticBezierStraight(
476
+ p0,
477
+ p1,
478
+ p2,
479
+ tolerance = DEFAULT_TOLERANCE,
480
+ ) {
481
+ if (!p0 || !p1 || !p2)
482
+ throw new Error(
483
+ "isQuadraticBezierStraight: points cannot be null or undefined",
484
+ );
485
+ if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal))
486
+ throw new Error(
487
+ "isQuadraticBezierStraight: p0 must have Decimal x and y properties",
488
+ );
489
+ if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal))
490
+ throw new Error(
491
+ "isQuadraticBezierStraight: p1 must have Decimal x and y properties",
492
+ );
493
+ if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal))
494
+ throw new Error(
495
+ "isQuadraticBezierStraight: p2 must have Decimal x and y properties",
496
+ );
497
+ if (tolerance === null || tolerance === undefined)
498
+ throw new Error(
499
+ "isQuadraticBezierStraight: tolerance parameter is null or undefined",
500
+ );
388
501
  const tol = D(tolerance);
389
- if (!tol.isFinite() || tol.lessThan(0)) throw new Error('isQuadraticBezierStraight: tolerance must be a non-negative finite number');
502
+ if (!tol.isFinite() || tol.lessThan(0))
503
+ throw new Error(
504
+ "isQuadraticBezierStraight: tolerance must be a non-negative finite number",
505
+ );
390
506
 
391
507
  // Check if start and end are the same point (degenerate case)
392
508
  const chordLength = distance(p0, p2);
@@ -395,7 +511,7 @@ export function isQuadraticBezierStraight(p0, p1, p2, tolerance = DEFAULT_TOLERA
395
511
  return {
396
512
  isStraight: d1.lessThan(tol),
397
513
  maxDeviation: d1,
398
- verified: true
514
+ verified: true,
399
515
  };
400
516
  }
401
517
 
@@ -407,7 +523,7 @@ export function isQuadraticBezierStraight(p0, p1, p2, tolerance = DEFAULT_TOLERA
407
523
  return {
408
524
  isStraight: false,
409
525
  maxDeviation: controlDeviation,
410
- verified: true
526
+ verified: true,
411
527
  };
412
528
  }
413
529
 
@@ -428,7 +544,7 @@ export function isQuadraticBezierStraight(p0, p1, p2, tolerance = DEFAULT_TOLERA
428
544
  return {
429
545
  isStraight: controlDeviation.lessThan(tol) && verified,
430
546
  maxDeviation: Decimal.max(controlDeviation, maxSampleDeviation),
431
- verified: true
547
+ verified: true,
432
548
  };
433
549
  }
434
550
 
@@ -443,7 +559,13 @@ export function isQuadraticBezierStraight(p0, p1, p2, tolerance = DEFAULT_TOLERA
443
559
  * @param {Decimal} [tolerance=DEFAULT_TOLERANCE] - Maximum allowed deviation
444
560
  * @returns {{start: {x: Decimal, y: Decimal}, end: {x: Decimal, y: Decimal}, maxDeviation: Decimal} | null}
445
561
  */
446
- export function cubicBezierToLine(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE) {
562
+ export function cubicBezierToLine(
563
+ p0,
564
+ p1,
565
+ p2,
566
+ p3,
567
+ tolerance = DEFAULT_TOLERANCE,
568
+ ) {
447
569
  // Validation is done in isCubicBezierStraight
448
570
  const result = isCubicBezierStraight(p0, p1, p2, p3, tolerance);
449
571
  if (!result.isStraight || !result.verified) {
@@ -452,7 +574,7 @@ export function cubicBezierToLine(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE)
452
574
  return {
453
575
  start: { x: p0.x, y: p0.y },
454
576
  end: { x: p3.x, y: p3.y },
455
- maxDeviation: result.maxDeviation
577
+ maxDeviation: result.maxDeviation,
456
578
  };
457
579
  }
458
580
 
@@ -480,28 +602,55 @@ export function cubicBezierToLine(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE)
480
602
  * @param {Decimal} [tolerance=DEFAULT_TOLERANCE] - Maximum allowed deviation
481
603
  * @returns {{canLower: boolean, quadraticControl: {x: Decimal, y: Decimal} | null, maxDeviation: Decimal, verified: boolean}}
482
604
  */
483
- export function canLowerCubicToQuadratic(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE) {
484
- if (!p0 || !p1 || !p2 || !p3) throw new Error('canLowerCubicToQuadratic: points cannot be null or undefined');
485
- if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal)) throw new Error('canLowerCubicToQuadratic: p0 must have Decimal x and y properties');
486
- if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal)) throw new Error('canLowerCubicToQuadratic: p1 must have Decimal x and y properties');
487
- if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal)) throw new Error('canLowerCubicToQuadratic: p2 must have Decimal x and y properties');
488
- if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal)) throw new Error('canLowerCubicToQuadratic: p3 must have Decimal x and y properties');
489
- if (tolerance === null || tolerance === undefined) throw new Error('canLowerCubicToQuadratic: tolerance parameter is null or undefined');
605
+ export function canLowerCubicToQuadratic(
606
+ p0,
607
+ p1,
608
+ p2,
609
+ p3,
610
+ tolerance = DEFAULT_TOLERANCE,
611
+ ) {
612
+ if (!p0 || !p1 || !p2 || !p3)
613
+ throw new Error(
614
+ "canLowerCubicToQuadratic: points cannot be null or undefined",
615
+ );
616
+ if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal))
617
+ throw new Error(
618
+ "canLowerCubicToQuadratic: p0 must have Decimal x and y properties",
619
+ );
620
+ if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal))
621
+ throw new Error(
622
+ "canLowerCubicToQuadratic: p1 must have Decimal x and y properties",
623
+ );
624
+ if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal))
625
+ throw new Error(
626
+ "canLowerCubicToQuadratic: p2 must have Decimal x and y properties",
627
+ );
628
+ if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal))
629
+ throw new Error(
630
+ "canLowerCubicToQuadratic: p3 must have Decimal x and y properties",
631
+ );
632
+ if (tolerance === null || tolerance === undefined)
633
+ throw new Error(
634
+ "canLowerCubicToQuadratic: tolerance parameter is null or undefined",
635
+ );
490
636
  const tol = D(tolerance);
491
- if (!tol.isFinite() || tol.lessThan(0)) throw new Error('canLowerCubicToQuadratic: tolerance must be a non-negative finite number');
637
+ if (!tol.isFinite() || tol.lessThan(0))
638
+ throw new Error(
639
+ "canLowerCubicToQuadratic: tolerance must be a non-negative finite number",
640
+ );
492
641
  const three = D(3);
493
642
  const two = D(2);
494
643
 
495
644
  // Calculate Q1 from P1: Q1 = (3*P1 - P0) / 2
496
645
  const q1FromP1 = {
497
646
  x: three.mul(p1.x).minus(p0.x).div(two),
498
- y: three.mul(p1.y).minus(p0.y).div(two)
647
+ y: three.mul(p1.y).minus(p0.y).div(two),
499
648
  };
500
649
 
501
650
  // Calculate Q1 from P2: Q1 = (3*P2 - P3) / 2
502
651
  const q1FromP2 = {
503
652
  x: three.mul(p2.x).minus(p3.x).div(two),
504
- y: three.mul(p2.y).minus(p3.y).div(two)
653
+ y: three.mul(p2.y).minus(p3.y).div(two),
505
654
  };
506
655
 
507
656
  // Check if these are equal within tolerance
@@ -512,14 +661,14 @@ export function canLowerCubicToQuadratic(p0, p1, p2, p3, tolerance = DEFAULT_TOL
512
661
  canLower: false,
513
662
  quadraticControl: null,
514
663
  maxDeviation: deviation,
515
- verified: true
664
+ verified: true,
516
665
  };
517
666
  }
518
667
 
519
668
  // Use the average as the quadratic control point
520
669
  const q1 = {
521
670
  x: q1FromP1.x.plus(q1FromP2.x).div(two),
522
- y: q1FromP1.y.plus(q1FromP2.y).div(two)
671
+ y: q1FromP1.y.plus(q1FromP2.y).div(two),
523
672
  };
524
673
 
525
674
  // VERIFICATION: Sample both curves and compare
@@ -540,7 +689,7 @@ export function canLowerCubicToQuadratic(p0, p1, p2, p3, tolerance = DEFAULT_TOL
540
689
  canLower: verified,
541
690
  quadraticControl: verified ? q1 : null,
542
691
  maxDeviation: Decimal.max(deviation, maxSampleDeviation),
543
- verified: true
692
+ verified: true,
544
693
  };
545
694
  }
546
695
 
@@ -555,7 +704,13 @@ export function canLowerCubicToQuadratic(p0, p1, p2, p3, tolerance = DEFAULT_TOL
555
704
  * @param {Decimal} [tolerance=DEFAULT_TOLERANCE] - Maximum allowed deviation
556
705
  * @returns {{p0: {x: Decimal, y: Decimal}, p1: {x: Decimal, y: Decimal}, p2: {x: Decimal, y: Decimal}, maxDeviation: Decimal} | null}
557
706
  */
558
- export function cubicToQuadratic(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE) {
707
+ export function cubicToQuadratic(
708
+ p0,
709
+ p1,
710
+ p2,
711
+ p3,
712
+ tolerance = DEFAULT_TOLERANCE,
713
+ ) {
559
714
  // Validation is done in canLowerCubicToQuadratic
560
715
  const result = canLowerCubicToQuadratic(p0, p1, p2, p3, tolerance);
561
716
  if (!result.canLower || !result.quadraticControl) {
@@ -565,7 +720,7 @@ export function cubicToQuadratic(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE)
565
720
  p0: { x: p0.x, y: p0.y },
566
721
  p1: result.quadraticControl,
567
722
  p2: { x: p3.x, y: p3.y },
568
- maxDeviation: result.maxDeviation
723
+ maxDeviation: result.maxDeviation,
569
724
  };
570
725
  }
571
726
 
@@ -583,9 +738,14 @@ export function cubicToQuadratic(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE)
583
738
  * @returns {{center: {x: Decimal, y: Decimal}, radius: Decimal} | null}
584
739
  */
585
740
  export function fitCircleToPoints(points) {
586
- if (!points) throw new Error('fitCircleToPoints: points array cannot be null or undefined');
587
- if (!Array.isArray(points)) throw new Error('fitCircleToPoints: points must be an array');
588
- if (points.length === 0) throw new Error('fitCircleToPoints: points array cannot be empty');
741
+ if (!points)
742
+ throw new Error(
743
+ "fitCircleToPoints: points array cannot be null or undefined",
744
+ );
745
+ if (!Array.isArray(points))
746
+ throw new Error("fitCircleToPoints: points must be an array");
747
+ if (points.length === 0)
748
+ throw new Error("fitCircleToPoints: points array cannot be empty");
589
749
  if (points.length < 3) {
590
750
  return null;
591
751
  }
@@ -593,22 +753,33 @@ export function fitCircleToPoints(points) {
593
753
  // Validate all points have Decimal x and y properties
594
754
  for (let i = 0; i < points.length; i++) {
595
755
  const p = points[i];
596
- if (!p) throw new Error(`fitCircleToPoints: point at index ${i} is null or undefined`);
756
+ if (!p)
757
+ throw new Error(
758
+ `fitCircleToPoints: point at index ${i} is null or undefined`,
759
+ );
597
760
  if (!(p.x instanceof Decimal) || !(p.y instanceof Decimal)) {
598
- throw new Error(`fitCircleToPoints: point at index ${i} must have Decimal x and y properties`);
761
+ throw new Error(
762
+ `fitCircleToPoints: point at index ${i} must have Decimal x and y properties`,
763
+ );
599
764
  }
600
765
  }
601
766
 
602
767
  const n = D(points.length);
603
- let sumX = D(0), sumY = D(0);
604
- let sumX2 = D(0), sumY2 = D(0);
768
+ let sumX = D(0),
769
+ sumY = D(0);
770
+ let sumX2 = D(0),
771
+ sumY2 = D(0);
605
772
  let sumXY = D(0);
606
- let sumX3 = D(0), sumY3 = D(0);
607
- let sumX2Y = D(0), sumXY2 = D(0);
773
+ let sumX3 = D(0),
774
+ sumY3 = D(0);
775
+ let sumX2Y = D(0),
776
+ sumXY2 = D(0);
608
777
 
609
778
  for (const p of points) {
610
- const x = p.x, y = p.y;
611
- const x2 = x.mul(x), y2 = y.mul(y);
779
+ const x = p.x,
780
+ y = p.y;
781
+ const x2 = x.mul(x),
782
+ y2 = y.mul(y);
612
783
 
613
784
  sumX = sumX.plus(x);
614
785
  sumY = sumY.plus(y);
@@ -631,8 +802,20 @@ export function fitCircleToPoints(points) {
631
802
  const A = n.mul(sumX2).minus(sumX.mul(sumX));
632
803
  const B = n.mul(sumXY).minus(sumX.mul(sumY));
633
804
  const C = n.mul(sumY2).minus(sumY.mul(sumY));
634
- const DD = D(0.5).mul(n.mul(sumX3).plus(n.mul(sumXY2)).minus(sumX.mul(sumX2)).minus(sumX.mul(sumY2)));
635
- const E = D(0.5).mul(n.mul(sumX2Y).plus(n.mul(sumY3)).minus(sumY.mul(sumX2)).minus(sumY.mul(sumY2)));
805
+ const DD = D(0.5).mul(
806
+ n
807
+ .mul(sumX3)
808
+ .plus(n.mul(sumXY2))
809
+ .minus(sumX.mul(sumX2))
810
+ .minus(sumX.mul(sumY2)),
811
+ );
812
+ const E = D(0.5).mul(
813
+ n
814
+ .mul(sumX2Y)
815
+ .plus(n.mul(sumY3))
816
+ .minus(sumY.mul(sumX2))
817
+ .minus(sumY.mul(sumY2)),
818
+ );
636
819
 
637
820
  // Solve: A*a + B*b = D, B*a + C*b = E
638
821
  const det = A.mul(C).minus(B.mul(B));
@@ -670,15 +853,42 @@ export function fitCircleToPoints(points) {
670
853
  * @param {Decimal} [tolerance=DEFAULT_TOLERANCE] - Maximum allowed deviation
671
854
  * @returns {{isArc: boolean, circle: {center: {x: Decimal, y: Decimal}, radius: Decimal} | null, maxDeviation: Decimal, verified: boolean}}
672
855
  */
673
- export function fitCircleToCubicBezier(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE) {
674
- if (!p0 || !p1 || !p2 || !p3) throw new Error('fitCircleToCubicBezier: points cannot be null or undefined');
675
- if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal)) throw new Error('fitCircleToCubicBezier: p0 must have Decimal x and y properties');
676
- if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal)) throw new Error('fitCircleToCubicBezier: p1 must have Decimal x and y properties');
677
- if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal)) throw new Error('fitCircleToCubicBezier: p2 must have Decimal x and y properties');
678
- if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal)) throw new Error('fitCircleToCubicBezier: p3 must have Decimal x and y properties');
679
- if (tolerance === null || tolerance === undefined) throw new Error('fitCircleToCubicBezier: tolerance parameter is null or undefined');
856
+ export function fitCircleToCubicBezier(
857
+ p0,
858
+ p1,
859
+ p2,
860
+ p3,
861
+ tolerance = DEFAULT_TOLERANCE,
862
+ ) {
863
+ if (!p0 || !p1 || !p2 || !p3)
864
+ throw new Error(
865
+ "fitCircleToCubicBezier: points cannot be null or undefined",
866
+ );
867
+ if (!(p0.x instanceof Decimal) || !(p0.y instanceof Decimal))
868
+ throw new Error(
869
+ "fitCircleToCubicBezier: p0 must have Decimal x and y properties",
870
+ );
871
+ if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal))
872
+ throw new Error(
873
+ "fitCircleToCubicBezier: p1 must have Decimal x and y properties",
874
+ );
875
+ if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal))
876
+ throw new Error(
877
+ "fitCircleToCubicBezier: p2 must have Decimal x and y properties",
878
+ );
879
+ if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal))
880
+ throw new Error(
881
+ "fitCircleToCubicBezier: p3 must have Decimal x and y properties",
882
+ );
883
+ if (tolerance === null || tolerance === undefined)
884
+ throw new Error(
885
+ "fitCircleToCubicBezier: tolerance parameter is null or undefined",
886
+ );
680
887
  const tol = D(tolerance);
681
- if (!tol.isFinite() || tol.lessThan(0)) throw new Error('fitCircleToCubicBezier: tolerance must be a non-negative finite number');
888
+ if (!tol.isFinite() || tol.lessThan(0))
889
+ throw new Error(
890
+ "fitCircleToCubicBezier: tolerance must be a non-negative finite number",
891
+ );
682
892
 
683
893
  // Sample points along the curve for fitting
684
894
  const sampleCount = 9; // Including endpoints
@@ -697,7 +907,7 @@ export function fitCircleToCubicBezier(p0, p1, p2, p3, tolerance = DEFAULT_TOLER
697
907
  isArc: false,
698
908
  circle: null,
699
909
  maxDeviation: D(Infinity),
700
- verified: true
910
+ verified: true,
701
911
  };
702
912
  }
703
913
 
@@ -719,7 +929,7 @@ export function fitCircleToCubicBezier(p0, p1, p2, p3, tolerance = DEFAULT_TOLER
719
929
  isArc,
720
930
  circle: isArc ? circle : null,
721
931
  maxDeviation,
722
- verified: true
932
+ verified: true,
723
933
  };
724
934
  }
725
935
 
@@ -734,7 +944,13 @@ export function fitCircleToCubicBezier(p0, p1, p2, p3, tolerance = DEFAULT_TOLER
734
944
  * @param {Decimal} [tolerance=DEFAULT_TOLERANCE] - Maximum allowed deviation
735
945
  * @returns {{rx: Decimal, ry: Decimal, rotation: Decimal, largeArc: number, sweep: number, endX: Decimal, endY: Decimal, maxDeviation: Decimal} | null}
736
946
  */
737
- export function cubicBezierToArc(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE) {
947
+ export function cubicBezierToArc(
948
+ p0,
949
+ p1,
950
+ p2,
951
+ p3,
952
+ tolerance = DEFAULT_TOLERANCE,
953
+ ) {
738
954
  // Validation is done in fitCircleToCubicBezier
739
955
  const result = fitCircleToCubicBezier(p0, p1, p2, p3, tolerance);
740
956
 
@@ -785,7 +1001,7 @@ export function cubicBezierToArc(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE)
785
1001
  sweep,
786
1002
  endX: p3.x,
787
1003
  endY: p3.y,
788
- maxDeviation: result.maxDeviation
1004
+ maxDeviation: result.maxDeviation,
789
1005
  };
790
1006
  }
791
1007
 
@@ -804,14 +1020,21 @@ export function cubicBezierToArc(p0, p1, p2, p3, tolerance = DEFAULT_TOLERANCE)
804
1020
  * @returns {Decimal | null} Sagitta value, or null if chord > diameter
805
1021
  */
806
1022
  export function calculateSagitta(radius, chordLength) {
807
- if (radius === null || radius === undefined) throw new Error('calculateSagitta: radius parameter is null or undefined');
808
- if (chordLength === null || chordLength === undefined) throw new Error('calculateSagitta: chordLength parameter is null or undefined');
1023
+ if (radius === null || radius === undefined)
1024
+ throw new Error("calculateSagitta: radius parameter is null or undefined");
1025
+ if (chordLength === null || chordLength === undefined)
1026
+ throw new Error(
1027
+ "calculateSagitta: chordLength parameter is null or undefined",
1028
+ );
809
1029
  const r = D(radius);
810
1030
  const c = D(chordLength);
811
- if (!r.isFinite()) throw new Error('calculateSagitta: radius must be finite');
812
- if (!c.isFinite()) throw new Error('calculateSagitta: chordLength must be finite');
813
- if (r.lessThan(0)) throw new Error('calculateSagitta: radius must be non-negative');
814
- if (c.lessThan(0)) throw new Error('calculateSagitta: chordLength must be non-negative');
1031
+ if (!r.isFinite()) throw new Error("calculateSagitta: radius must be finite");
1032
+ if (!c.isFinite())
1033
+ throw new Error("calculateSagitta: chordLength must be finite");
1034
+ if (r.lessThan(0))
1035
+ throw new Error("calculateSagitta: radius must be non-negative");
1036
+ if (c.lessThan(0))
1037
+ throw new Error("calculateSagitta: chordLength must be non-negative");
815
1038
  const halfChord = c.div(2);
816
1039
 
817
1040
  // Check if chord is valid (must be <= 2*r)
@@ -843,24 +1066,51 @@ export function calculateSagitta(radius, chordLength) {
843
1066
  * @param {Decimal} [tolerance=DEFAULT_TOLERANCE] - Maximum allowed deviation
844
1067
  * @returns {{isStraight: boolean, sagitta: Decimal | null, maxDeviation: Decimal, verified: boolean}}
845
1068
  */
846
- export function isArcStraight(rx, ry, rotation, largeArc, sweep, start, end, tolerance = DEFAULT_TOLERANCE) {
847
- if (rx === null || rx === undefined) throw new Error('isArcStraight: rx parameter is null or undefined');
848
- if (ry === null || ry === undefined) throw new Error('isArcStraight: ry parameter is null or undefined');
849
- if (rotation === null || rotation === undefined) throw new Error('isArcStraight: rotation parameter is null or undefined');
850
- if (largeArc === null || largeArc === undefined) throw new Error('isArcStraight: largeArc parameter is null or undefined');
851
- if (sweep === null || sweep === undefined) throw new Error('isArcStraight: sweep parameter is null or undefined');
852
- if (!start || !end) throw new Error('isArcStraight: start and end points cannot be null or undefined');
853
- if (!(start.x instanceof Decimal) || !(start.y instanceof Decimal)) throw new Error('isArcStraight: start must have Decimal x and y properties');
854
- if (!(end.x instanceof Decimal) || !(end.y instanceof Decimal)) throw new Error('isArcStraight: end must have Decimal x and y properties');
855
- if (tolerance === null || tolerance === undefined) throw new Error('isArcStraight: tolerance parameter is null or undefined');
856
- if (largeArc !== 0 && largeArc !== 1) throw new Error('isArcStraight: largeArc must be 0 or 1');
857
- if (sweep !== 0 && sweep !== 1) throw new Error('isArcStraight: sweep must be 0 or 1');
1069
+ export function isArcStraight(
1070
+ rx,
1071
+ ry,
1072
+ rotation,
1073
+ largeArc,
1074
+ sweep,
1075
+ start,
1076
+ end,
1077
+ tolerance = DEFAULT_TOLERANCE,
1078
+ ) {
1079
+ if (rx === null || rx === undefined)
1080
+ throw new Error("isArcStraight: rx parameter is null or undefined");
1081
+ if (ry === null || ry === undefined)
1082
+ throw new Error("isArcStraight: ry parameter is null or undefined");
1083
+ if (rotation === null || rotation === undefined)
1084
+ throw new Error("isArcStraight: rotation parameter is null or undefined");
1085
+ if (largeArc === null || largeArc === undefined)
1086
+ throw new Error("isArcStraight: largeArc parameter is null or undefined");
1087
+ if (sweep === null || sweep === undefined)
1088
+ throw new Error("isArcStraight: sweep parameter is null or undefined");
1089
+ if (!start || !end)
1090
+ throw new Error(
1091
+ "isArcStraight: start and end points cannot be null or undefined",
1092
+ );
1093
+ if (!(start.x instanceof Decimal) || !(start.y instanceof Decimal))
1094
+ throw new Error(
1095
+ "isArcStraight: start must have Decimal x and y properties",
1096
+ );
1097
+ if (!(end.x instanceof Decimal) || !(end.y instanceof Decimal))
1098
+ throw new Error("isArcStraight: end must have Decimal x and y properties");
1099
+ if (tolerance === null || tolerance === undefined)
1100
+ throw new Error("isArcStraight: tolerance parameter is null or undefined");
1101
+ if (largeArc !== 0 && largeArc !== 1)
1102
+ throw new Error("isArcStraight: largeArc must be 0 or 1");
1103
+ if (sweep !== 0 && sweep !== 1)
1104
+ throw new Error("isArcStraight: sweep must be 0 or 1");
858
1105
  const tol = D(tolerance);
859
- if (!tol.isFinite() || tol.lessThan(0)) throw new Error('isArcStraight: tolerance must be a non-negative finite number');
1106
+ if (!tol.isFinite() || tol.lessThan(0))
1107
+ throw new Error(
1108
+ "isArcStraight: tolerance must be a non-negative finite number",
1109
+ );
860
1110
  const rxD = D(rx);
861
1111
  const ryD = D(ry);
862
- if (!rxD.isFinite()) throw new Error('isArcStraight: rx must be finite');
863
- if (!ryD.isFinite()) throw new Error('isArcStraight: ry must be finite');
1112
+ if (!rxD.isFinite()) throw new Error("isArcStraight: rx must be finite");
1113
+ if (!ryD.isFinite()) throw new Error("isArcStraight: ry must be finite");
864
1114
 
865
1115
  // Check for zero or near-zero radii
866
1116
  if (rxD.abs().lessThan(EPSILON) || ryD.abs().lessThan(EPSILON)) {
@@ -868,7 +1118,7 @@ export function isArcStraight(rx, ry, rotation, largeArc, sweep, start, end, tol
868
1118
  isStraight: true,
869
1119
  sagitta: D(0),
870
1120
  maxDeviation: D(0),
871
- verified: true
1121
+ verified: true,
872
1122
  };
873
1123
  }
874
1124
 
@@ -885,7 +1135,7 @@ export function isArcStraight(rx, ry, rotation, largeArc, sweep, start, end, tol
885
1135
  isStraight: false,
886
1136
  sagitta: null,
887
1137
  maxDeviation: rxD, // Max deviation is at least the radius
888
- verified: true
1138
+ verified: true,
889
1139
  };
890
1140
  }
891
1141
 
@@ -896,7 +1146,7 @@ export function isArcStraight(rx, ry, rotation, largeArc, sweep, start, end, tol
896
1146
  isStraight: effectiveSagitta.lessThan(tol),
897
1147
  sagitta: effectiveSagitta,
898
1148
  maxDeviation: effectiveSagitta,
899
- verified: true
1149
+ verified: true,
900
1150
  };
901
1151
  }
902
1152
 
@@ -906,7 +1156,7 @@ export function isArcStraight(rx, ry, rotation, largeArc, sweep, start, end, tol
906
1156
  isStraight: false,
907
1157
  sagitta: null,
908
1158
  maxDeviation: Decimal.max(rxD, ryD),
909
- verified: false
1159
+ verified: false,
910
1160
  };
911
1161
  }
912
1162
 
@@ -924,13 +1174,21 @@ export function isArcStraight(rx, ry, rotation, largeArc, sweep, start, end, tol
924
1174
  * @returns {boolean} True if collinear
925
1175
  */
926
1176
  export function areCollinear(p1, p2, p3, tolerance = DEFAULT_TOLERANCE) {
927
- if (!p1 || !p2 || !p3) throw new Error('areCollinear: points cannot be null or undefined');
928
- if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal)) throw new Error('areCollinear: p1 must have Decimal x and y properties');
929
- if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal)) throw new Error('areCollinear: p2 must have Decimal x and y properties');
930
- if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal)) throw new Error('areCollinear: p3 must have Decimal x and y properties');
931
- if (tolerance === null || tolerance === undefined) throw new Error('areCollinear: tolerance parameter is null or undefined');
1177
+ if (!p1 || !p2 || !p3)
1178
+ throw new Error("areCollinear: points cannot be null or undefined");
1179
+ if (!(p1.x instanceof Decimal) || !(p1.y instanceof Decimal))
1180
+ throw new Error("areCollinear: p1 must have Decimal x and y properties");
1181
+ if (!(p2.x instanceof Decimal) || !(p2.y instanceof Decimal))
1182
+ throw new Error("areCollinear: p2 must have Decimal x and y properties");
1183
+ if (!(p3.x instanceof Decimal) || !(p3.y instanceof Decimal))
1184
+ throw new Error("areCollinear: p3 must have Decimal x and y properties");
1185
+ if (tolerance === null || tolerance === undefined)
1186
+ throw new Error("areCollinear: tolerance parameter is null or undefined");
932
1187
  const tol = D(tolerance);
933
- if (!tol.isFinite() || tol.lessThan(0)) throw new Error('areCollinear: tolerance must be a non-negative finite number');
1188
+ if (!tol.isFinite() || tol.lessThan(0))
1189
+ throw new Error(
1190
+ "areCollinear: tolerance must be a non-negative finite number",
1191
+ );
934
1192
 
935
1193
  // Check using cross product (area of triangle)
936
1194
  const cross = crossProduct(p1, p2, p3).abs();
@@ -954,17 +1212,30 @@ export function areCollinear(p1, p2, p3, tolerance = DEFAULT_TOLERANCE) {
954
1212
  * @returns {{points: Array<{x: Decimal, y: Decimal}>, mergeCount: number, verified: boolean}}
955
1213
  */
956
1214
  export function mergeCollinearSegments(points, tolerance = DEFAULT_TOLERANCE) {
957
- if (!points) throw new Error('mergeCollinearSegments: points array cannot be null or undefined');
958
- if (!Array.isArray(points)) throw new Error('mergeCollinearSegments: points must be an array');
959
- if (points.length === 0) throw new Error('mergeCollinearSegments: points array cannot be empty');
960
- if (tolerance === null || tolerance === undefined) throw new Error('mergeCollinearSegments: tolerance parameter is null or undefined');
1215
+ if (!points)
1216
+ throw new Error(
1217
+ "mergeCollinearSegments: points array cannot be null or undefined",
1218
+ );
1219
+ if (!Array.isArray(points))
1220
+ throw new Error("mergeCollinearSegments: points must be an array");
1221
+ if (points.length === 0)
1222
+ throw new Error("mergeCollinearSegments: points array cannot be empty");
1223
+ if (tolerance === null || tolerance === undefined)
1224
+ throw new Error(
1225
+ "mergeCollinearSegments: tolerance parameter is null or undefined",
1226
+ );
961
1227
 
962
1228
  // Validate all points have Decimal x and y properties
963
1229
  for (let i = 0; i < points.length; i++) {
964
1230
  const p = points[i];
965
- if (!p) throw new Error(`mergeCollinearSegments: point at index ${i} is null or undefined`);
1231
+ if (!p)
1232
+ throw new Error(
1233
+ `mergeCollinearSegments: point at index ${i} is null or undefined`,
1234
+ );
966
1235
  if (!(p.x instanceof Decimal) || !(p.y instanceof Decimal)) {
967
- throw new Error(`mergeCollinearSegments: point at index ${i} must have Decimal x and y properties`);
1236
+ throw new Error(
1237
+ `mergeCollinearSegments: point at index ${i} must have Decimal x and y properties`,
1238
+ );
968
1239
  }
969
1240
  }
970
1241
 
@@ -973,7 +1244,10 @@ export function mergeCollinearSegments(points, tolerance = DEFAULT_TOLERANCE) {
973
1244
  }
974
1245
 
975
1246
  const tol = D(tolerance);
976
- if (!tol.isFinite() || tol.lessThan(0)) throw new Error('mergeCollinearSegments: tolerance must be a non-negative finite number');
1247
+ if (!tol.isFinite() || tol.lessThan(0))
1248
+ throw new Error(
1249
+ "mergeCollinearSegments: tolerance must be a non-negative finite number",
1250
+ );
977
1251
  const result = [points[0]];
978
1252
  let mergeCount = 0;
979
1253
 
@@ -1035,12 +1309,25 @@ export function mergeCollinearSegments(points, tolerance = DEFAULT_TOLERANCE) {
1035
1309
  * @returns {boolean} True if zero-length
1036
1310
  */
1037
1311
  export function isZeroLengthSegment(start, end, tolerance = EPSILON) {
1038
- if (!start || !end) throw new Error('isZeroLengthSegment: points cannot be null or undefined');
1039
- if (!(start.x instanceof Decimal) || !(start.y instanceof Decimal)) throw new Error('isZeroLengthSegment: start must have Decimal x and y properties');
1040
- if (!(end.x instanceof Decimal) || !(end.y instanceof Decimal)) throw new Error('isZeroLengthSegment: end must have Decimal x and y properties');
1041
- if (tolerance === null || tolerance === undefined) throw new Error('isZeroLengthSegment: tolerance parameter is null or undefined');
1312
+ if (!start || !end)
1313
+ throw new Error("isZeroLengthSegment: points cannot be null or undefined");
1314
+ if (!(start.x instanceof Decimal) || !(start.y instanceof Decimal))
1315
+ throw new Error(
1316
+ "isZeroLengthSegment: start must have Decimal x and y properties",
1317
+ );
1318
+ if (!(end.x instanceof Decimal) || !(end.y instanceof Decimal))
1319
+ throw new Error(
1320
+ "isZeroLengthSegment: end must have Decimal x and y properties",
1321
+ );
1322
+ if (tolerance === null || tolerance === undefined)
1323
+ throw new Error(
1324
+ "isZeroLengthSegment: tolerance parameter is null or undefined",
1325
+ );
1042
1326
  const tol = D(tolerance);
1043
- if (!tol.isFinite() || tol.lessThan(0)) throw new Error('isZeroLengthSegment: tolerance must be a non-negative finite number');
1327
+ if (!tol.isFinite() || tol.lessThan(0))
1328
+ throw new Error(
1329
+ "isZeroLengthSegment: tolerance must be a non-negative finite number",
1330
+ );
1044
1331
  return distance(start, end).lessThan(tol);
1045
1332
  }
1046
1333
 
@@ -1052,49 +1339,86 @@ export function isZeroLengthSegment(start, end, tolerance = EPSILON) {
1052
1339
  * @returns {{pathData: Array<{command: string, args: Array<Decimal>}>, removeCount: number, verified: boolean}}
1053
1340
  */
1054
1341
  export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1055
- if (!pathData) throw new Error('removeZeroLengthSegments: pathData cannot be null or undefined');
1056
- if (!Array.isArray(pathData)) throw new Error('removeZeroLengthSegments: pathData must be an array');
1057
- if (tolerance === null || tolerance === undefined) throw new Error('removeZeroLengthSegments: tolerance parameter is null or undefined');
1342
+ if (!pathData)
1343
+ throw new Error(
1344
+ "removeZeroLengthSegments: pathData cannot be null or undefined",
1345
+ );
1346
+ if (!Array.isArray(pathData))
1347
+ throw new Error("removeZeroLengthSegments: pathData must be an array");
1348
+ if (tolerance === null || tolerance === undefined)
1349
+ throw new Error(
1350
+ "removeZeroLengthSegments: tolerance parameter is null or undefined",
1351
+ );
1058
1352
 
1059
1353
  const tol = D(tolerance);
1060
- if (!tol.isFinite() || tol.lessThan(0)) throw new Error('removeZeroLengthSegments: tolerance must be a non-negative finite number');
1354
+ if (!tol.isFinite() || tol.lessThan(0))
1355
+ throw new Error(
1356
+ "removeZeroLengthSegments: tolerance must be a non-negative finite number",
1357
+ );
1061
1358
  const result = [];
1062
1359
  let removeCount = 0;
1063
- let currentX = D(0), currentY = D(0);
1064
- let startX = D(0), startY = D(0);
1360
+ let currentX = D(0),
1361
+ currentY = D(0);
1362
+ let startX = D(0),
1363
+ startY = D(0);
1065
1364
  // Track previous control points for S and T commands (reserved for future S/T command handling)
1066
- let prevCp2X = null, prevCp2Y = null; // For S command (cubic)
1067
- let prevCpX = null, prevCpY = null; // For T command (quadratic)
1365
+ let prevCp2X = null,
1366
+ prevCp2Y = null; // For S command (cubic)
1367
+ let prevCpX = null,
1368
+ prevCpY = null; // For T command (quadratic)
1068
1369
 
1069
1370
  for (let idx = 0; idx < pathData.length; idx++) {
1070
1371
  const item = pathData[idx];
1071
- if (!item) throw new Error(`removeZeroLengthSegments: item at index ${idx} is null or undefined`);
1072
- if (typeof item.command !== 'string') throw new Error(`removeZeroLengthSegments: item at index ${idx} must have a string command property`);
1073
- if (!Array.isArray(item.args)) throw new Error(`removeZeroLengthSegments: item at index ${idx} must have an args array property`);
1372
+ if (!item)
1373
+ throw new Error(
1374
+ `removeZeroLengthSegments: item at index ${idx} is null or undefined`,
1375
+ );
1376
+ if (typeof item.command !== "string")
1377
+ throw new Error(
1378
+ `removeZeroLengthSegments: item at index ${idx} must have a string command property`,
1379
+ );
1380
+ if (!Array.isArray(item.args))
1381
+ throw new Error(
1382
+ `removeZeroLengthSegments: item at index ${idx} must have an args array property`,
1383
+ );
1074
1384
 
1075
1385
  const { command, args } = item;
1076
1386
  let keep = true;
1077
1387
 
1078
1388
  switch (command.toUpperCase()) {
1079
- case 'M':
1389
+ case "M":
1080
1390
  // Update current position (absolute M) or move relative (lowercase m)
1081
- if (args.length < 2) throw new Error(`removeZeroLengthSegments: M command at index ${idx} requires 2 args, got ${args.length}`);
1082
- currentX = command === 'M' ? D(args[0]) : currentX.plus(D(args[0]));
1083
- currentY = command === 'M' ? D(args[1]) : currentY.plus(D(args[1]));
1391
+ if (args.length < 2)
1392
+ throw new Error(
1393
+ `removeZeroLengthSegments: M command at index ${idx} requires 2 args, got ${args.length}`,
1394
+ );
1395
+ currentX = command === "M" ? D(args[0]) : currentX.plus(D(args[0]));
1396
+ currentY = command === "M" ? D(args[1]) : currentY.plus(D(args[1]));
1084
1397
  // CRITICAL: Update subpath start for EVERY M command (BUG 3 FIX)
1085
1398
  startX = currentX;
1086
1399
  startY = currentY;
1087
1400
  // Reset previous control points on new subpath
1088
- prevCp2X = null; prevCp2Y = null;
1089
- prevCpX = null; prevCpY = null;
1401
+ prevCp2X = null;
1402
+ prevCp2Y = null;
1403
+ prevCpX = null;
1404
+ prevCpY = null;
1090
1405
  break;
1091
1406
 
1092
- case 'L': {
1407
+ case "L": {
1093
1408
  // Line to: x y (2 args)
1094
- if (args.length < 2) throw new Error(`removeZeroLengthSegments: L command at index ${idx} requires 2 args, got ${args.length}`);
1095
- const endX = command === 'L' ? D(args[0]) : currentX.plus(D(args[0]));
1096
- const endY = command === 'L' ? D(args[1]) : currentY.plus(D(args[1]));
1097
- if (isZeroLengthSegment({ x: currentX, y: currentY }, { x: endX, y: endY }, tol)) {
1409
+ if (args.length < 2)
1410
+ throw new Error(
1411
+ `removeZeroLengthSegments: L command at index ${idx} requires 2 args, got ${args.length}`,
1412
+ );
1413
+ const endX = command === "L" ? D(args[0]) : currentX.plus(D(args[0]));
1414
+ const endY = command === "L" ? D(args[1]) : currentY.plus(D(args[1]));
1415
+ if (
1416
+ isZeroLengthSegment(
1417
+ { x: currentX, y: currentY },
1418
+ { x: endX, y: endY },
1419
+ tol,
1420
+ )
1421
+ ) {
1098
1422
  keep = false;
1099
1423
  removeCount++;
1100
1424
  }
@@ -1102,17 +1426,22 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1102
1426
  currentX = endX;
1103
1427
  currentY = endY;
1104
1428
  // Reset previous control points (not a curve command)
1105
- prevCp2X = null; prevCp2Y = null;
1106
- prevCpX = null; prevCpY = null;
1429
+ prevCp2X = null;
1430
+ prevCp2Y = null;
1431
+ prevCpX = null;
1432
+ prevCpY = null;
1107
1433
  break;
1108
1434
  }
1109
1435
 
1110
- case 'T': {
1436
+ case "T": {
1111
1437
  // Smooth quadratic Bezier: x y (2 args)
1112
1438
  // Control point is reflected from previous Q/T, or current position if none
1113
- if (args.length < 2) throw new Error(`removeZeroLengthSegments: T command at index ${idx} requires 2 args, got ${args.length}`);
1114
- const endX = command === 'T' ? D(args[0]) : currentX.plus(D(args[0]));
1115
- const endY = command === 'T' ? D(args[1]) : currentY.plus(D(args[1]));
1439
+ if (args.length < 2)
1440
+ throw new Error(
1441
+ `removeZeroLengthSegments: T command at index ${idx} requires 2 args, got ${args.length}`,
1442
+ );
1443
+ const endX = command === "T" ? D(args[0]) : currentX.plus(D(args[0]));
1444
+ const endY = command === "T" ? D(args[1]) : currentY.plus(D(args[1]));
1116
1445
  // Calculate implicit control point
1117
1446
  let cpX, cpY;
1118
1447
  if (prevCpX !== null && prevCpY !== null) {
@@ -1125,8 +1454,18 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1125
1454
  cpY = currentY;
1126
1455
  }
1127
1456
  // Check if ALL points (start, implicit CP, end) are at same location
1128
- if (isZeroLengthSegment({ x: currentX, y: currentY }, { x: endX, y: endY }, tol) &&
1129
- isZeroLengthSegment({ x: currentX, y: currentY }, { x: cpX, y: cpY }, tol)) {
1457
+ if (
1458
+ isZeroLengthSegment(
1459
+ { x: currentX, y: currentY },
1460
+ { x: endX, y: endY },
1461
+ tol,
1462
+ ) &&
1463
+ isZeroLengthSegment(
1464
+ { x: currentX, y: currentY },
1465
+ { x: cpX, y: cpY },
1466
+ tol,
1467
+ )
1468
+ ) {
1130
1469
  keep = false;
1131
1470
  removeCount++;
1132
1471
  }
@@ -1136,13 +1475,17 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1136
1475
  prevCpX = cpX;
1137
1476
  prevCpY = cpY;
1138
1477
  // T doesn't affect cubic control points
1139
- prevCp2X = null; prevCp2Y = null;
1478
+ prevCp2X = null;
1479
+ prevCp2Y = null;
1140
1480
  break;
1141
1481
  }
1142
1482
 
1143
- case 'H': {
1144
- if (args.length < 1) throw new Error(`removeZeroLengthSegments: H command at index ${idx} requires 1 arg, got ${args.length}`);
1145
- const endX = command === 'H' ? D(args[0]) : currentX.plus(D(args[0]));
1483
+ case "H": {
1484
+ if (args.length < 1)
1485
+ throw new Error(
1486
+ `removeZeroLengthSegments: H command at index ${idx} requires 1 arg, got ${args.length}`,
1487
+ );
1488
+ const endX = command === "H" ? D(args[0]) : currentX.plus(D(args[0]));
1146
1489
  if (endX.minus(currentX).abs().lessThan(tol)) {
1147
1490
  keep = false;
1148
1491
  removeCount++;
@@ -1150,14 +1493,19 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1150
1493
  // CRITICAL: Always update position, even when removing segment (consistency with L command)
1151
1494
  currentX = endX;
1152
1495
  // Reset previous control points (not a curve command)
1153
- prevCp2X = null; prevCp2Y = null;
1154
- prevCpX = null; prevCpY = null;
1496
+ prevCp2X = null;
1497
+ prevCp2Y = null;
1498
+ prevCpX = null;
1499
+ prevCpY = null;
1155
1500
  break;
1156
1501
  }
1157
1502
 
1158
- case 'V': {
1159
- if (args.length < 1) throw new Error(`removeZeroLengthSegments: V command at index ${idx} requires 1 arg, got ${args.length}`);
1160
- const endY = command === 'V' ? D(args[0]) : currentY.plus(D(args[0]));
1503
+ case "V": {
1504
+ if (args.length < 1)
1505
+ throw new Error(
1506
+ `removeZeroLengthSegments: V command at index ${idx} requires 1 arg, got ${args.length}`,
1507
+ );
1508
+ const endY = command === "V" ? D(args[0]) : currentY.plus(D(args[0]));
1161
1509
  if (endY.minus(currentY).abs().lessThan(tol)) {
1162
1510
  keep = false;
1163
1511
  removeCount++;
@@ -1165,25 +1513,42 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1165
1513
  // CRITICAL: Always update position, even when removing segment (consistency with L command)
1166
1514
  currentY = endY;
1167
1515
  // Reset previous control points (not a curve command)
1168
- prevCp2X = null; prevCp2Y = null;
1169
- prevCpX = null; prevCpY = null;
1516
+ prevCp2X = null;
1517
+ prevCp2Y = null;
1518
+ prevCpX = null;
1519
+ prevCpY = null;
1170
1520
  break;
1171
1521
  }
1172
1522
 
1173
- case 'C': {
1174
- if (args.length < 6) throw new Error(`removeZeroLengthSegments: C command at index ${idx} requires 6 args, got ${args.length}`);
1175
- const endX = command === 'C' ? D(args[4]) : currentX.plus(D(args[4]));
1176
- const endY = command === 'C' ? D(args[5]) : currentY.plus(D(args[5]));
1523
+ case "C": {
1524
+ if (args.length < 6)
1525
+ throw new Error(
1526
+ `removeZeroLengthSegments: C command at index ${idx} requires 6 args, got ${args.length}`,
1527
+ );
1528
+ const endX = command === "C" ? D(args[4]) : currentX.plus(D(args[4]));
1529
+ const endY = command === "C" ? D(args[5]) : currentY.plus(D(args[5]));
1177
1530
  // For curves, also check if all control points are at the same location
1178
- const cp1X = command === 'C' ? D(args[0]) : currentX.plus(D(args[0]));
1179
- const cp1Y = command === 'C' ? D(args[1]) : currentY.plus(D(args[1]));
1180
- const cp2X = command === 'C' ? D(args[2]) : currentX.plus(D(args[2]));
1181
- const cp2Y = command === 'C' ? D(args[3]) : currentY.plus(D(args[3]));
1531
+ const cp1X = command === "C" ? D(args[0]) : currentX.plus(D(args[0]));
1532
+ const cp1Y = command === "C" ? D(args[1]) : currentY.plus(D(args[1]));
1533
+ const cp2X = command === "C" ? D(args[2]) : currentX.plus(D(args[2]));
1534
+ const cp2Y = command === "C" ? D(args[3]) : currentY.plus(D(args[3]));
1182
1535
 
1183
1536
  const allSame =
1184
- isZeroLengthSegment({ x: currentX, y: currentY }, { x: endX, y: endY }, tol) &&
1185
- isZeroLengthSegment({ x: currentX, y: currentY }, { x: cp1X, y: cp1Y }, tol) &&
1186
- isZeroLengthSegment({ x: currentX, y: currentY }, { x: cp2X, y: cp2Y }, tol);
1537
+ isZeroLengthSegment(
1538
+ { x: currentX, y: currentY },
1539
+ { x: endX, y: endY },
1540
+ tol,
1541
+ ) &&
1542
+ isZeroLengthSegment(
1543
+ { x: currentX, y: currentY },
1544
+ { x: cp1X, y: cp1Y },
1545
+ tol,
1546
+ ) &&
1547
+ isZeroLengthSegment(
1548
+ { x: currentX, y: currentY },
1549
+ { x: cp2X, y: cp2Y },
1550
+ tol,
1551
+ );
1187
1552
 
1188
1553
  if (allSame) {
1189
1554
  keep = false;
@@ -1195,19 +1560,33 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1195
1560
  prevCp2X = cp2X;
1196
1561
  prevCp2Y = cp2Y;
1197
1562
  // C doesn't affect quadratic control points
1198
- prevCpX = null; prevCpY = null;
1563
+ prevCpX = null;
1564
+ prevCpY = null;
1199
1565
  break;
1200
1566
  }
1201
1567
 
1202
- case 'Q': {
1568
+ case "Q": {
1203
1569
  // Quadratic Bezier: x1 y1 x y (4 args)
1204
- if (args.length < 4) throw new Error(`removeZeroLengthSegments: Q command at index ${idx} requires 4 args, got ${args.length}`);
1205
- const endX = command === 'Q' ? D(args[2]) : currentX.plus(D(args[2]));
1206
- const endY = command === 'Q' ? D(args[3]) : currentY.plus(D(args[3]));
1207
- const cpX = command === 'Q' ? D(args[0]) : currentX.plus(D(args[0]));
1208
- const cpY = command === 'Q' ? D(args[1]) : currentY.plus(D(args[1]));
1209
- if (isZeroLengthSegment({ x: currentX, y: currentY }, { x: endX, y: endY }, tol) &&
1210
- isZeroLengthSegment({ x: currentX, y: currentY }, { x: cpX, y: cpY }, tol)) {
1570
+ if (args.length < 4)
1571
+ throw new Error(
1572
+ `removeZeroLengthSegments: Q command at index ${idx} requires 4 args, got ${args.length}`,
1573
+ );
1574
+ const endX = command === "Q" ? D(args[2]) : currentX.plus(D(args[2]));
1575
+ const endY = command === "Q" ? D(args[3]) : currentY.plus(D(args[3]));
1576
+ const cpX = command === "Q" ? D(args[0]) : currentX.plus(D(args[0]));
1577
+ const cpY = command === "Q" ? D(args[1]) : currentY.plus(D(args[1]));
1578
+ if (
1579
+ isZeroLengthSegment(
1580
+ { x: currentX, y: currentY },
1581
+ { x: endX, y: endY },
1582
+ tol,
1583
+ ) &&
1584
+ isZeroLengthSegment(
1585
+ { x: currentX, y: currentY },
1586
+ { x: cpX, y: cpY },
1587
+ tol,
1588
+ )
1589
+ ) {
1211
1590
  keep = false;
1212
1591
  removeCount++;
1213
1592
  }
@@ -1217,18 +1596,22 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1217
1596
  prevCpX = cpX;
1218
1597
  prevCpY = cpY;
1219
1598
  // Q doesn't affect cubic control points
1220
- prevCp2X = null; prevCp2Y = null;
1599
+ prevCp2X = null;
1600
+ prevCp2Y = null;
1221
1601
  break;
1222
1602
  }
1223
1603
 
1224
- case 'S': {
1604
+ case "S": {
1225
1605
  // Smooth cubic Bezier: x2 y2 x y (4 args)
1226
1606
  // First control point is reflected from previous C/S, or current position if none
1227
- if (args.length < 4) throw new Error(`removeZeroLengthSegments: S command at index ${idx} requires 4 args, got ${args.length}`);
1228
- const endX = command === 'S' ? D(args[2]) : currentX.plus(D(args[2]));
1229
- const endY = command === 'S' ? D(args[3]) : currentY.plus(D(args[3]));
1230
- const cp2X = command === 'S' ? D(args[0]) : currentX.plus(D(args[0]));
1231
- const cp2Y = command === 'S' ? D(args[1]) : currentY.plus(D(args[1]));
1607
+ if (args.length < 4)
1608
+ throw new Error(
1609
+ `removeZeroLengthSegments: S command at index ${idx} requires 4 args, got ${args.length}`,
1610
+ );
1611
+ const endX = command === "S" ? D(args[2]) : currentX.plus(D(args[2]));
1612
+ const endY = command === "S" ? D(args[3]) : currentY.plus(D(args[3]));
1613
+ const cp2X = command === "S" ? D(args[0]) : currentX.plus(D(args[0]));
1614
+ const cp2Y = command === "S" ? D(args[1]) : currentY.plus(D(args[1]));
1232
1615
  // Calculate implicit first control point
1233
1616
  let cp1X, cp1Y;
1234
1617
  if (prevCp2X !== null && prevCp2Y !== null) {
@@ -1241,9 +1624,23 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1241
1624
  cp1Y = currentY;
1242
1625
  }
1243
1626
  // Check if ALL points (start, implicit CP1, CP2, end) are at same location
1244
- if (isZeroLengthSegment({ x: currentX, y: currentY }, { x: endX, y: endY }, tol) &&
1245
- isZeroLengthSegment({ x: currentX, y: currentY }, { x: cp1X, y: cp1Y }, tol) &&
1246
- isZeroLengthSegment({ x: currentX, y: currentY }, { x: cp2X, y: cp2Y }, tol)) {
1627
+ if (
1628
+ isZeroLengthSegment(
1629
+ { x: currentX, y: currentY },
1630
+ { x: endX, y: endY },
1631
+ tol,
1632
+ ) &&
1633
+ isZeroLengthSegment(
1634
+ { x: currentX, y: currentY },
1635
+ { x: cp1X, y: cp1Y },
1636
+ tol,
1637
+ ) &&
1638
+ isZeroLengthSegment(
1639
+ { x: currentX, y: currentY },
1640
+ { x: cp2X, y: cp2Y },
1641
+ tol,
1642
+ )
1643
+ ) {
1247
1644
  keep = false;
1248
1645
  removeCount++;
1249
1646
  }
@@ -1253,15 +1650,25 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1253
1650
  prevCp2X = cp2X;
1254
1651
  prevCp2Y = cp2Y;
1255
1652
  // S doesn't affect quadratic control points
1256
- prevCpX = null; prevCpY = null;
1653
+ prevCpX = null;
1654
+ prevCpY = null;
1257
1655
  break;
1258
1656
  }
1259
1657
 
1260
- case 'A': {
1261
- if (args.length < 7) throw new Error(`removeZeroLengthSegments: A command at index ${idx} requires 7 args, got ${args.length}`);
1262
- const endX = command === 'A' ? D(args[5]) : currentX.plus(D(args[5]));
1263
- const endY = command === 'A' ? D(args[6]) : currentY.plus(D(args[6]));
1264
- if (isZeroLengthSegment({ x: currentX, y: currentY }, { x: endX, y: endY }, tol)) {
1658
+ case "A": {
1659
+ if (args.length < 7)
1660
+ throw new Error(
1661
+ `removeZeroLengthSegments: A command at index ${idx} requires 7 args, got ${args.length}`,
1662
+ );
1663
+ const endX = command === "A" ? D(args[5]) : currentX.plus(D(args[5]));
1664
+ const endY = command === "A" ? D(args[6]) : currentY.plus(D(args[6]));
1665
+ if (
1666
+ isZeroLengthSegment(
1667
+ { x: currentX, y: currentY },
1668
+ { x: endX, y: endY },
1669
+ tol,
1670
+ )
1671
+ ) {
1265
1672
  keep = false;
1266
1673
  removeCount++;
1267
1674
  }
@@ -1269,26 +1676,38 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1269
1676
  currentX = endX;
1270
1677
  currentY = endY;
1271
1678
  // Reset previous control points (not a curve command)
1272
- prevCp2X = null; prevCp2Y = null;
1273
- prevCpX = null; prevCpY = null;
1679
+ prevCp2X = null;
1680
+ prevCp2Y = null;
1681
+ prevCpX = null;
1682
+ prevCpY = null;
1274
1683
  break;
1275
1684
  }
1276
1685
 
1277
- case 'Z':
1686
+ case "Z":
1278
1687
  // Z command goes back to start - check if already there
1279
- if (isZeroLengthSegment({ x: currentX, y: currentY }, { x: startX, y: startY }, tol)) {
1688
+ if (
1689
+ isZeroLengthSegment(
1690
+ { x: currentX, y: currentY },
1691
+ { x: startX, y: startY },
1692
+ tol,
1693
+ )
1694
+ ) {
1280
1695
  // Still keep Z for path closure, but note it's zero-length
1281
1696
  }
1282
1697
  currentX = startX;
1283
1698
  currentY = startY;
1284
1699
  // Reset previous control points (new subpath after closure)
1285
- prevCp2X = null; prevCp2Y = null;
1286
- prevCpX = null; prevCpY = null;
1700
+ prevCp2X = null;
1701
+ prevCp2Y = null;
1702
+ prevCpX = null;
1703
+ prevCpY = null;
1287
1704
  break;
1288
1705
  default:
1289
1706
  // Unknown commands don't affect control point tracking
1290
- prevCp2X = null; prevCp2Y = null;
1291
- prevCpX = null; prevCpY = null;
1707
+ prevCp2X = null;
1708
+ prevCp2Y = null;
1709
+ prevCpX = null;
1710
+ prevCpY = null;
1292
1711
  break;
1293
1712
  }
1294
1713
 
@@ -1300,7 +1719,7 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1300
1719
  return {
1301
1720
  pathData: result,
1302
1721
  removeCount,
1303
- verified: true
1722
+ verified: true,
1304
1723
  };
1305
1724
  }
1306
1725
 
@@ -1308,11 +1727,7 @@ export function removeZeroLengthSegments(pathData, tolerance = EPSILON) {
1308
1727
  // Exports
1309
1728
  // ============================================================================
1310
1729
 
1311
- export {
1312
- EPSILON,
1313
- DEFAULT_TOLERANCE,
1314
- D
1315
- };
1730
+ export { EPSILON, DEFAULT_TOLERANCE, D };
1316
1731
 
1317
1732
  export default {
1318
1733
  // Point utilities
@@ -1355,5 +1770,5 @@ export default {
1355
1770
 
1356
1771
  // Constants
1357
1772
  EPSILON,
1358
- DEFAULT_TOLERANCE
1773
+ DEFAULT_TOLERANCE,
1359
1774
  };