@emasoft/svg-matrix 1.0.27 → 1.0.29

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 (46) hide show
  1. package/README.md +325 -0
  2. package/bin/svg-matrix.js +994 -378
  3. package/bin/svglinter.cjs +4172 -433
  4. package/bin/svgm.js +744 -184
  5. package/package.json +16 -4
  6. package/src/animation-references.js +71 -52
  7. package/src/arc-length.js +160 -96
  8. package/src/bezier-analysis.js +257 -117
  9. package/src/bezier-intersections.js +411 -148
  10. package/src/browser-verify.js +240 -100
  11. package/src/clip-path-resolver.js +350 -142
  12. package/src/convert-path-data.js +279 -134
  13. package/src/css-specificity.js +78 -70
  14. package/src/flatten-pipeline.js +751 -263
  15. package/src/geometry-to-path.js +511 -182
  16. package/src/index.js +191 -46
  17. package/src/inkscape-support.js +404 -0
  18. package/src/marker-resolver.js +278 -164
  19. package/src/mask-resolver.js +209 -98
  20. package/src/matrix.js +147 -67
  21. package/src/mesh-gradient.js +187 -96
  22. package/src/off-canvas-detection.js +201 -104
  23. package/src/path-analysis.js +187 -107
  24. package/src/path-data-plugins.js +628 -167
  25. package/src/path-simplification.js +0 -1
  26. package/src/pattern-resolver.js +125 -88
  27. package/src/polygon-clip.js +111 -66
  28. package/src/svg-boolean-ops.js +194 -118
  29. package/src/svg-collections.js +48 -19
  30. package/src/svg-flatten.js +282 -164
  31. package/src/svg-parser.js +427 -200
  32. package/src/svg-rendering-context.js +147 -104
  33. package/src/svg-toolbox.js +16411 -3298
  34. package/src/svg2-polyfills.js +114 -245
  35. package/src/transform-decomposition.js +46 -41
  36. package/src/transform-optimization.js +89 -68
  37. package/src/transforms2d.js +49 -16
  38. package/src/transforms3d.js +58 -22
  39. package/src/use-symbol-resolver.js +150 -110
  40. package/src/vector.js +67 -15
  41. package/src/vendor/README.md +110 -0
  42. package/src/vendor/inkscape-hatch-polyfill.js +401 -0
  43. package/src/vendor/inkscape-hatch-polyfill.min.js +8 -0
  44. package/src/vendor/inkscape-mesh-polyfill.js +843 -0
  45. package/src/vendor/inkscape-mesh-polyfill.min.js +8 -0
  46. package/src/verification.js +288 -124
package/src/arc-length.js CHANGED
@@ -13,13 +13,13 @@
13
13
  * - 10^65x better precision than float64 implementations
14
14
  */
15
15
 
16
- import Decimal from 'decimal.js';
17
- import { bezierDerivative, bezierPoint } from './bezier-analysis.js';
16
+ import Decimal from "decimal.js";
17
+ import { bezierDerivative, bezierPoint } from "./bezier-analysis.js";
18
18
 
19
19
  // Ensure high precision
20
20
  Decimal.set({ precision: 80 });
21
21
 
22
- const D = x => (x instanceof Decimal ? x : new Decimal(x));
22
+ const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
23
23
 
24
24
  // ============================================================================
25
25
  // GAUSS-LEGENDRE QUADRATURE NODES AND WEIGHTS
@@ -33,47 +33,47 @@ const GAUSS_LEGENDRE = {
33
33
  // 5-point rule (sufficient for most cases)
34
34
  5: {
35
35
  nodes: [
36
- '-0.90617984593866399279762687829939296512565191076',
37
- '-0.53846931010568309103631442070020880496728660690',
38
- '0',
39
- '0.53846931010568309103631442070020880496728660690',
40
- '0.90617984593866399279762687829939296512565191076'
36
+ "-0.90617984593866399279762687829939296512565191076",
37
+ "-0.53846931010568309103631442070020880496728660690",
38
+ "0",
39
+ "0.53846931010568309103631442070020880496728660690",
40
+ "0.90617984593866399279762687829939296512565191076",
41
41
  ],
42
42
  weights: [
43
- '0.23692688505618908751426404071991736264326000221',
44
- '0.47862867049936646804129151483563819291229555035',
45
- '0.56888888888888888888888888888888888888888888889',
46
- '0.47862867049936646804129151483563819291229555035',
47
- '0.23692688505618908751426404071991736264326000221'
48
- ]
43
+ "0.23692688505618908751426404071991736264326000221",
44
+ "0.47862867049936646804129151483563819291229555035",
45
+ "0.56888888888888888888888888888888888888888888889",
46
+ "0.47862867049936646804129151483563819291229555035",
47
+ "0.23692688505618908751426404071991736264326000221",
48
+ ],
49
49
  },
50
50
  // 10-point rule (for higher accuracy)
51
51
  10: {
52
52
  nodes: [
53
- '-0.97390652851717172007796401208445205342826994669',
54
- '-0.86506336668898451073209668842349304852754301497',
55
- '-0.67940956829902440623432736511487357576929471183',
56
- '-0.43339539412924719079926594316578416220007183765',
57
- '-0.14887433898163121088482600112971998461756485942',
58
- '0.14887433898163121088482600112971998461756485942',
59
- '0.43339539412924719079926594316578416220007183765',
60
- '0.67940956829902440623432736511487357576929471183',
61
- '0.86506336668898451073209668842349304852754301497',
62
- '0.97390652851717172007796401208445205342826994669'
53
+ "-0.97390652851717172007796401208445205342826994669",
54
+ "-0.86506336668898451073209668842349304852754301497",
55
+ "-0.67940956829902440623432736511487357576929471183",
56
+ "-0.43339539412924719079926594316578416220007183765",
57
+ "-0.14887433898163121088482600112971998461756485942",
58
+ "0.14887433898163121088482600112971998461756485942",
59
+ "0.43339539412924719079926594316578416220007183765",
60
+ "0.67940956829902440623432736511487357576929471183",
61
+ "0.86506336668898451073209668842349304852754301497",
62
+ "0.97390652851717172007796401208445205342826994669",
63
63
  ],
64
64
  weights: [
65
- '0.06667134430868813759356880989333179285786483432',
66
- '0.14945134915058059314577633965769733240255644326',
67
- '0.21908636251598204399553493422816219682140867715',
68
- '0.26926671930999635509122692156946935285975993846',
69
- '0.29552422471475287017389299465133832942104671702',
70
- '0.29552422471475287017389299465133832942104671702',
71
- '0.26926671930999635509122692156946935285975993846',
72
- '0.21908636251598204399553493422816219682140867715',
73
- '0.14945134915058059314577633965769733240255644326',
74
- '0.06667134430868813759356880989333179285786483432'
75
- ]
76
- }
65
+ "0.06667134430868813759356880989333179285786483432",
66
+ "0.14945134915058059314577633965769733240255644326",
67
+ "0.21908636251598204399553493422816219682140867715",
68
+ "0.26926671930999635509122692156946935285975993846",
69
+ "0.29552422471475287017389299465133832942104671702",
70
+ "0.29552422471475287017389299465133832942104671702",
71
+ "0.26926671930999635509122692156946935285975993846",
72
+ "0.21908636251598204399553493422816219682140867715",
73
+ "0.14945134915058059314577633965769733240255644326",
74
+ "0.06667134430868813759356880989333179285786483432",
75
+ ],
76
+ },
77
77
  };
78
78
 
79
79
  // ============================================================================
@@ -86,7 +86,7 @@ const GAUSS_LEGENDRE = {
86
86
  * the curve derivative is essentially zero. At such points, Newton's method
87
87
  * would divide by near-zero, causing instability. We switch to bisection instead.
88
88
  */
89
- const NEAR_ZERO_SPEED_THRESHOLD = new Decimal('1e-60');
89
+ const NEAR_ZERO_SPEED_THRESHOLD = new Decimal("1e-60");
90
90
 
91
91
  /**
92
92
  * Default tolerance for arc length computation.
@@ -94,7 +94,7 @@ const NEAR_ZERO_SPEED_THRESHOLD = new Decimal('1e-60');
94
94
  * The value 1e-30 provides extremely high precision (suitable for arbitrary
95
95
  * precision arithmetic) while still converging in reasonable time.
96
96
  */
97
- const DEFAULT_ARC_LENGTH_TOLERANCE = '1e-30';
97
+ const DEFAULT_ARC_LENGTH_TOLERANCE = "1e-30";
98
98
 
99
99
  /**
100
100
  * Subdivision convergence threshold.
@@ -102,7 +102,7 @@ const DEFAULT_ARC_LENGTH_TOLERANCE = '1e-30';
102
102
  * by comparing 5-point and 10-point Gauss-Legendre results. When results
103
103
  * differ by less than this, we accept the higher-order result.
104
104
  */
105
- const SUBDIVISION_CONVERGENCE_THRESHOLD = new Decimal('1e-15');
105
+ const _SUBDIVISION_CONVERGENCE_THRESHOLD = new Decimal("1e-15");
106
106
 
107
107
  /**
108
108
  * Tolerance for table roundtrip verification.
@@ -110,14 +110,14 @@ const SUBDIVISION_CONVERGENCE_THRESHOLD = new Decimal('1e-15');
110
110
  * produces consistent results. This tolerance accounts for interpolation error
111
111
  * in table-based lookups.
112
112
  */
113
- const TABLE_ROUNDTRIP_TOLERANCE = new Decimal('1e-20');
113
+ const TABLE_ROUNDTRIP_TOLERANCE = new Decimal("1e-20");
114
114
 
115
115
  /**
116
116
  * Maximum relative error for subdivision comparison verification.
117
117
  * WHY: When comparing adaptive quadrature vs subdivision methods, this tolerance
118
118
  * accounts for the inherent approximation in chord-based subdivision.
119
119
  */
120
- const SUBDIVISION_COMPARISON_TOLERANCE = '1e-20';
120
+ const SUBDIVISION_COMPARISON_TOLERANCE = "1e-20";
121
121
 
122
122
  // ============================================================================
123
123
  // ARC LENGTH COMPUTATION
@@ -148,13 +148,15 @@ export function arcLength(points, t0 = 0, t1 = 1, options = {}) {
148
148
  // at least 2 control points to define a curve. Catching this early prevents
149
149
  // cryptic errors deep in the computation.
150
150
  if (!points || !Array.isArray(points) || points.length < 2) {
151
- throw new Error('arcLength: points must be an array with at least 2 control points');
151
+ throw new Error(
152
+ "arcLength: points must be an array with at least 2 control points",
153
+ );
152
154
  }
153
155
 
154
156
  const {
155
157
  tolerance = DEFAULT_ARC_LENGTH_TOLERANCE,
156
158
  maxDepth = 50,
157
- minDepth = 3
159
+ minDepth = 3,
158
160
  } = options;
159
161
 
160
162
  const t0D = D(t0);
@@ -172,13 +174,13 @@ export function arcLength(points, t0 = 0, t1 = 1, options = {}) {
172
174
 
173
175
  // Use adaptive quadrature
174
176
  return adaptiveQuadrature(
175
- t => speedAtT(points, t),
177
+ (t) => speedAtT(points, t),
176
178
  t0D,
177
179
  t1D,
178
180
  tol,
179
181
  maxDepth,
180
182
  minDepth,
181
- 0
183
+ 0,
182
184
  );
183
185
  }
184
186
 
@@ -188,8 +190,8 @@ export function arcLength(points, t0 = 0, t1 = 1, options = {}) {
188
190
  * WHY: Speed is the magnitude of the velocity vector (first derivative).
189
191
  * This is the integrand for arc length: L = integral of |B'(t)| dt.
190
192
  *
191
- * @param {Array} points - Control points
192
- * @param {Decimal} t - Parameter
193
+ * @param {Array} points - Control points [[x,y], ...]
194
+ * @param {Decimal} t - Parameter in [0, 1]
193
195
  * @returns {Decimal} Speed (magnitude of derivative)
194
196
  */
195
197
  function speedAtT(points, t) {
@@ -211,14 +213,14 @@ function speedAtT(points, t) {
211
213
  * Subdivides intervals where the integrand varies significantly,
212
214
  * ensuring accuracy while minimizing computation.
213
215
  *
214
- * @param {Function} f - Function to integrate
216
+ * @param {Function} f - Function to integrate (takes Decimal, returns Decimal)
215
217
  * @param {Decimal} a - Start of interval
216
218
  * @param {Decimal} b - End of interval
217
219
  * @param {Decimal} tol - Error tolerance
218
220
  * @param {number} maxDepth - Maximum recursion depth
219
221
  * @param {number} minDepth - Minimum recursion depth
220
- * @param {number} depth - Current depth
221
- * @returns {Decimal} Integral value
222
+ * @param {number} depth - Current recursion depth
223
+ * @returns {Decimal} Integral approximation
222
224
  */
223
225
  function adaptiveQuadrature(f, a, b, tol, maxDepth, minDepth, depth) {
224
226
  // Compute integral using 5-point and 10-point rules
@@ -236,8 +238,24 @@ function adaptiveQuadrature(f, a, b, tol, maxDepth, minDepth, depth) {
236
238
  const mid = a.plus(b).div(2);
237
239
  const halfTol = tol.div(2);
238
240
 
239
- const leftIntegral = adaptiveQuadrature(f, a, mid, halfTol, maxDepth, minDepth, depth + 1);
240
- const rightIntegral = adaptiveQuadrature(f, mid, b, halfTol, maxDepth, minDepth, depth + 1);
241
+ const leftIntegral = adaptiveQuadrature(
242
+ f,
243
+ a,
244
+ mid,
245
+ halfTol,
246
+ maxDepth,
247
+ minDepth,
248
+ depth + 1,
249
+ );
250
+ const rightIntegral = adaptiveQuadrature(
251
+ f,
252
+ mid,
253
+ b,
254
+ halfTol,
255
+ maxDepth,
256
+ minDepth,
257
+ depth + 1,
258
+ );
241
259
 
242
260
  return leftIntegral.plus(rightIntegral);
243
261
  }
@@ -248,10 +266,10 @@ function adaptiveQuadrature(f, a, b, tol, maxDepth, minDepth, depth) {
248
266
  * Transforms integral from [a,b] to [-1,1] and applies formula:
249
267
  * integral ≈ (b-a)/2 * sum of (weight[i] * f(transformed_node[i]))
250
268
  *
251
- * @param {Function} f - Function to integrate
269
+ * @param {Function} f - Function to integrate (takes Decimal, returns Decimal)
252
270
  * @param {Decimal} a - Start of interval
253
271
  * @param {Decimal} b - End of interval
254
- * @param {number} order - Number of points (5 or 10)
272
+ * @param {number} order - Number of quadrature points (5 or 10)
255
273
  * @returns {Decimal} Integral approximation
256
274
  */
257
275
  function gaussLegendre(f, a, b, order) {
@@ -306,14 +324,16 @@ export function inverseArcLength(points, targetLength, options = {}) {
306
324
  // WHY: inverseArcLength calls arcLength internally, which requires valid points.
307
325
  // Catching this early provides clearer error messages to users.
308
326
  if (!points || !Array.isArray(points) || points.length < 2) {
309
- throw new Error('inverseArcLength: points must be an array with at least 2 control points');
327
+ throw new Error(
328
+ "inverseArcLength: points must be an array with at least 2 control points",
329
+ );
310
330
  }
311
331
 
312
332
  const {
313
333
  tolerance = DEFAULT_ARC_LENGTH_TOLERANCE,
314
334
  maxIterations = 100,
315
335
  lengthTolerance = DEFAULT_ARC_LENGTH_TOLERANCE,
316
- initialT
336
+ initialT,
317
337
  } = options;
318
338
 
319
339
  const target = D(targetLength);
@@ -325,7 +345,7 @@ export function inverseArcLength(points, targetLength, options = {}) {
325
345
  // a magnitude). Accepting negative values would be nonsensical and lead to
326
346
  // incorrect results or infinite loops in Newton's method.
327
347
  if (target.lt(0)) {
328
- throw new Error('inverseArcLength: targetLength must be non-negative');
348
+ throw new Error("inverseArcLength: targetLength must be non-negative");
329
349
  }
330
350
 
331
351
  // Handle edge case: zero length
@@ -403,7 +423,7 @@ export function inverseArcLength(points, targetLength, options = {}) {
403
423
  t,
404
424
  length: finalLength,
405
425
  iterations,
406
- converged
426
+ converged,
407
427
  };
408
428
  }
409
429
 
@@ -423,10 +443,10 @@ export function pathArcLength(segments, options = {}) {
423
443
  // WHY: We need to iterate over segments and call arcLength on each.
424
444
  // Catching invalid input early prevents cryptic errors in the loop.
425
445
  if (!segments || !Array.isArray(segments)) {
426
- throw new Error('pathArcLength: segments must be an array');
446
+ throw new Error("pathArcLength: segments must be an array");
427
447
  }
428
448
  if (segments.length === 0) {
429
- throw new Error('pathArcLength: segments array must not be empty');
449
+ throw new Error("pathArcLength: segments array must not be empty");
430
450
  }
431
451
 
432
452
  let total = D(0);
@@ -451,10 +471,10 @@ export function pathInverseArcLength(segments, targetLength, options = {}) {
451
471
  // INPUT VALIDATION: Ensure segments is a valid array
452
472
  // WHY: We need to iterate over segments to find which one contains the target length.
453
473
  if (!segments || !Array.isArray(segments)) {
454
- throw new Error('pathInverseArcLength: segments must be an array');
474
+ throw new Error("pathInverseArcLength: segments must be an array");
455
475
  }
456
476
  if (segments.length === 0) {
457
- throw new Error('pathInverseArcLength: segments array must not be empty');
477
+ throw new Error("pathInverseArcLength: segments array must not be empty");
458
478
  }
459
479
 
460
480
  const target = D(targetLength);
@@ -462,7 +482,7 @@ export function pathInverseArcLength(segments, targetLength, options = {}) {
462
482
  // FAIL FAST: Negative arc length is invalid
463
483
  // WHY: Same reason as inverseArcLength - arc length is non-negative by definition.
464
484
  if (target.lt(0)) {
465
- throw new Error('pathInverseArcLength: targetLength must be non-negative');
485
+ throw new Error("pathInverseArcLength: targetLength must be non-negative");
466
486
  }
467
487
 
468
488
  // EDGE CASE: Zero target length
@@ -471,7 +491,7 @@ export function pathInverseArcLength(segments, targetLength, options = {}) {
471
491
  return {
472
492
  segmentIndex: 0,
473
493
  t: D(0),
474
- totalLength: D(0)
494
+ totalLength: D(0),
475
495
  };
476
496
  }
477
497
 
@@ -489,7 +509,7 @@ export function pathInverseArcLength(segments, targetLength, options = {}) {
489
509
  return {
490
510
  segmentIndex: i,
491
511
  t,
492
- totalLength: accumulated.plus(arcLength(segments[i], 0, t, options))
512
+ totalLength: accumulated.plus(arcLength(segments[i], 0, t, options)),
493
513
  };
494
514
  }
495
515
 
@@ -500,7 +520,7 @@ export function pathInverseArcLength(segments, targetLength, options = {}) {
500
520
  return {
501
521
  segmentIndex: segments.length - 1,
502
522
  t: D(1),
503
- totalLength: accumulated
523
+ totalLength: accumulated,
504
524
  };
505
525
  }
506
526
 
@@ -522,10 +542,14 @@ export function pathInverseArcLength(segments, targetLength, options = {}) {
522
542
  export function createArcLengthTable(points, samples = 100, options = {}) {
523
543
  // Input validation
524
544
  if (!points || points.length < 2) {
525
- throw new Error('createArcLengthTable: points must have at least 2 control points');
545
+ throw new Error(
546
+ "createArcLengthTable: points must have at least 2 control points",
547
+ );
526
548
  }
527
549
  if (samples < 2) {
528
- throw new Error('createArcLengthTable: samples must be at least 2 (for binary search to work)');
550
+ throw new Error(
551
+ "createArcLengthTable: samples must be at least 2 (for binary search to work)",
552
+ );
529
553
  }
530
554
 
531
555
  const table = [];
@@ -601,7 +625,7 @@ export function createArcLengthTable(points, samples = 100, options = {}) {
601
625
  // Use approxT as starting point for Newton
602
626
  const { t } = inverseArcLength(points, s, { ...opts, initialT: approxT });
603
627
  return t;
604
- }
628
+ },
605
629
  };
606
630
  }
607
631
 
@@ -623,17 +647,23 @@ export function verifyArcLength(points, computedLength = null) {
623
647
  // WHY: This function needs to access points[0], points[length-1], and iterate
624
648
  // over points to compute polygon length. Invalid input would cause errors.
625
649
  if (!points || !Array.isArray(points) || points.length < 2) {
626
- throw new Error('verifyArcLength: points must be an array with at least 2 control points');
650
+ throw new Error(
651
+ "verifyArcLength: points must be an array with at least 2 control points",
652
+ );
627
653
  }
628
654
 
629
655
  const errors = [];
630
656
 
631
657
  // Compute arc length if not provided
632
- const length = computedLength !== null ? D(computedLength) : arcLength(points);
658
+ const length =
659
+ computedLength !== null ? D(computedLength) : arcLength(points);
633
660
 
634
661
  // Chord length (straight line from start to end)
635
662
  const [x0, y0] = [D(points[0][0]), D(points[0][1])];
636
- const [xn, yn] = [D(points[points.length - 1][0]), D(points[points.length - 1][1])];
663
+ const [xn, yn] = [
664
+ D(points[points.length - 1][0]),
665
+ D(points[points.length - 1][1]),
666
+ ];
637
667
  const chordLength = xn.minus(x0).pow(2).plus(yn.minus(y0).pow(2)).sqrt();
638
668
 
639
669
  // Control polygon length
@@ -641,7 +671,9 @@ export function verifyArcLength(points, computedLength = null) {
641
671
  for (let i = 0; i < points.length - 1; i++) {
642
672
  const [x1, y1] = [D(points[i][0]), D(points[i][1])];
643
673
  const [x2, y2] = [D(points[i + 1][0]), D(points[i + 1][1])];
644
- polygonLength = polygonLength.plus(x2.minus(x1).pow(2).plus(y2.minus(y1).pow(2)).sqrt());
674
+ polygonLength = polygonLength.plus(
675
+ x2.minus(x1).pow(2).plus(y2.minus(y1).pow(2)).sqrt(),
676
+ );
645
677
  }
646
678
 
647
679
  // Check bounds
@@ -658,7 +690,7 @@ export function verifyArcLength(points, computedLength = null) {
658
690
  polygonLength,
659
691
  arcLength: length,
660
692
  ratio: chordLength.gt(0) ? length.div(chordLength) : D(1),
661
- errors
693
+ errors,
662
694
  };
663
695
  }
664
696
 
@@ -671,12 +703,18 @@ export function verifyArcLength(points, computedLength = null) {
671
703
  * @param {number|string|Decimal} [tolerance='1e-25'] - Maximum error
672
704
  * @returns {{valid: boolean, targetLength: Decimal, foundT: Decimal, verifiedLength: Decimal, error: Decimal}}
673
705
  */
674
- export function verifyInverseArcLength(points, targetLength, tolerance = '1e-25') {
706
+ export function verifyInverseArcLength(
707
+ points,
708
+ targetLength,
709
+ tolerance = "1e-25",
710
+ ) {
675
711
  // INPUT VALIDATION: Ensure points array is valid
676
712
  // WHY: This function calls inverseArcLength and arcLength, both of which require
677
713
  // valid points. We validate early for clearer error messages.
678
714
  if (!points || !Array.isArray(points) || points.length < 2) {
679
- throw new Error('verifyInverseArcLength: points must be an array with at least 2 control points');
715
+ throw new Error(
716
+ "verifyInverseArcLength: points must be an array with at least 2 control points",
717
+ );
680
718
  }
681
719
 
682
720
  const target = D(targetLength);
@@ -684,7 +722,9 @@ export function verifyInverseArcLength(points, targetLength, tolerance = '1e-25'
684
722
  // FAIL FAST: Validate targetLength is non-negative
685
723
  // WHY: Negative arc lengths are mathematically invalid. This prevents nonsensical tests.
686
724
  if (target.lt(0)) {
687
- throw new Error('verifyInverseArcLength: targetLength must be non-negative');
725
+ throw new Error(
726
+ "verifyInverseArcLength: targetLength must be non-negative",
727
+ );
688
728
  }
689
729
 
690
730
  const tol = D(tolerance);
@@ -704,7 +744,7 @@ export function verifyInverseArcLength(points, targetLength, tolerance = '1e-25'
704
744
  foundT,
705
745
  verifiedLength,
706
746
  error,
707
- converged
747
+ converged,
708
748
  };
709
749
  }
710
750
 
@@ -717,12 +757,18 @@ export function verifyInverseArcLength(points, targetLength, tolerance = '1e-25'
717
757
  * @param {number|string|Decimal} [tolerance='1e-20'] - Maximum difference
718
758
  * @returns {{valid: boolean, quadratureLength: Decimal, subdivisionLength: Decimal, difference: Decimal}}
719
759
  */
720
- export function verifyArcLengthBySubdivision(points, subdivisions = 16, tolerance = SUBDIVISION_COMPARISON_TOLERANCE) {
760
+ export function verifyArcLengthBySubdivision(
761
+ points,
762
+ subdivisions = 16,
763
+ tolerance = SUBDIVISION_COMPARISON_TOLERANCE,
764
+ ) {
721
765
  // INPUT VALIDATION: Ensure points array is valid
722
766
  // WHY: This function calls arcLength and bezierPoint, both of which require
723
767
  // valid control points. Early validation provides better error messages.
724
768
  if (!points || !Array.isArray(points) || points.length < 2) {
725
- throw new Error('verifyArcLengthBySubdivision: points must be an array with at least 2 control points');
769
+ throw new Error(
770
+ "verifyArcLengthBySubdivision: points must be an array with at least 2 control points",
771
+ );
726
772
  }
727
773
 
728
774
  const tol = D(tolerance);
@@ -740,7 +786,9 @@ export function verifyArcLengthBySubdivision(points, subdivisions = 16, toleranc
740
786
 
741
787
  const dx = D(currPoint[0]).minus(D(prevPoint[0]));
742
788
  const dy = D(currPoint[1]).minus(D(prevPoint[1]));
743
- subdivisionLength = subdivisionLength.plus(dx.pow(2).plus(dy.pow(2)).sqrt());
789
+ subdivisionLength = subdivisionLength.plus(
790
+ dx.pow(2).plus(dy.pow(2)).sqrt(),
791
+ );
744
792
 
745
793
  prevPoint = currPoint;
746
794
  }
@@ -754,7 +802,7 @@ export function verifyArcLengthBySubdivision(points, subdivisions = 16, toleranc
754
802
  quadratureLength,
755
803
  subdivisionLength,
756
804
  difference,
757
- underestimate: quadratureLength.gt(subdivisionLength)
805
+ underestimate: quadratureLength.gt(subdivisionLength),
758
806
  };
759
807
  }
760
808
 
@@ -766,19 +814,25 @@ export function verifyArcLengthBySubdivision(points, subdivisions = 16, toleranc
766
814
  * @param {number|string|Decimal} [tolerance='1e-30'] - Maximum error
767
815
  * @returns {{valid: boolean, totalLength: Decimal, leftLength: Decimal, rightLength: Decimal, sum: Decimal, error: Decimal}}
768
816
  */
769
- export function verifyArcLengthAdditivity(points, t, tolerance = DEFAULT_ARC_LENGTH_TOLERANCE) {
817
+ export function verifyArcLengthAdditivity(
818
+ points,
819
+ t,
820
+ tolerance = DEFAULT_ARC_LENGTH_TOLERANCE,
821
+ ) {
770
822
  // INPUT VALIDATION: Ensure points array is valid
771
823
  // WHY: This function calls arcLength multiple times with the same points array.
772
824
  // Validating once here is more efficient than letting each call validate.
773
825
  if (!points || !Array.isArray(points) || points.length < 2) {
774
- throw new Error('verifyArcLengthAdditivity: points must be an array with at least 2 control points');
826
+ throw new Error(
827
+ "verifyArcLengthAdditivity: points must be an array with at least 2 control points",
828
+ );
775
829
  }
776
830
 
777
831
  const tD = D(t);
778
832
  // PARAMETER VALIDATION: t must be in [0, 1] for additivity to make sense
779
833
  // WHY: Arc length additivity L(0,t) + L(t,1) = L(0,1) only holds for t in [0,1]
780
834
  if (tD.lt(0) || tD.gt(1)) {
781
- throw new Error('verifyArcLengthAdditivity: t must be in range [0, 1]');
835
+ throw new Error("verifyArcLengthAdditivity: t must be in range [0, 1]");
782
836
  }
783
837
 
784
838
  const tol = D(tolerance);
@@ -796,7 +850,7 @@ export function verifyArcLengthAdditivity(points, t, tolerance = DEFAULT_ARC_LEN
796
850
  leftLength,
797
851
  rightLength,
798
852
  sum,
799
- error
853
+ error,
800
854
  };
801
855
  }
802
856
 
@@ -812,7 +866,9 @@ export function verifyArcLengthTable(points, samples = 50) {
812
866
  // WHY: This function calls createArcLengthTable and arcLength, both requiring
813
867
  // valid points. Early validation provides better diagnostics.
814
868
  if (!points || !Array.isArray(points) || points.length < 2) {
815
- throw new Error('verifyArcLengthTable: points must be an array with at least 2 control points');
869
+ throw new Error(
870
+ "verifyArcLengthTable: points must be an array with at least 2 control points",
871
+ );
816
872
  }
817
873
 
818
874
  const errors = [];
@@ -840,7 +896,9 @@ export function verifyArcLengthTable(points, samples = 50) {
840
896
  // Verify table boundaries
841
897
  const firstEntry = table.table[0];
842
898
  if (!firstEntry.t.isZero() || !firstEntry.length.isZero()) {
843
- errors.push(`First entry should be t=0, length=0, got t=${firstEntry.t}, length=${firstEntry.length}`);
899
+ errors.push(
900
+ `First entry should be t=0, length=0, got t=${firstEntry.t}, length=${firstEntry.length}`,
901
+ );
844
902
  }
845
903
 
846
904
  const lastEntry = table.table[table.table.length - 1];
@@ -853,7 +911,9 @@ export function verifyArcLengthTable(points, samples = 50) {
853
911
  const tableTotalDiff = table.totalLength.minus(directLength).abs();
854
912
  // WHY: Use TABLE_ROUNDTRIP_TOLERANCE to account for accumulated segment errors
855
913
  if (tableTotalDiff.gt(TABLE_ROUNDTRIP_TOLERANCE)) {
856
- errors.push(`Table total length ${table.totalLength} differs from direct computation ${directLength}`);
914
+ errors.push(
915
+ `Table total length ${table.totalLength} differs from direct computation ${directLength}`,
916
+ );
857
917
  }
858
918
 
859
919
  // Verify getT roundtrip for a few values
@@ -864,7 +924,9 @@ export function verifyArcLengthTable(points, samples = 50) {
864
924
  const roundtripError = recoveredLength.minus(targetLength).abs();
865
925
 
866
926
  if (roundtripError.gt(table.totalLength.div(samples).times(2))) {
867
- errors.push(`getT roundtrip error too large at ${fraction}: ${roundtripError}`);
927
+ errors.push(
928
+ `getT roundtrip error too large at ${fraction}: ${roundtripError}`,
929
+ );
868
930
  }
869
931
  }
870
932
 
@@ -874,7 +936,7 @@ export function verifyArcLengthTable(points, samples = 50) {
874
936
  isMonotonic,
875
937
  maxGap,
876
938
  tableSize: table.table.length,
877
- totalLength: table.totalLength
939
+ totalLength: table.totalLength,
878
940
  };
879
941
  }
880
942
 
@@ -885,12 +947,14 @@ export function verifyArcLengthTable(points, samples = 50) {
885
947
  * @param {Object} [options] - Options
886
948
  * @returns {{valid: boolean, results: Object}}
887
949
  */
888
- export function verifyAllArcLengthFunctions(points, options = {}) {
950
+ export function verifyAllArcLengthFunctions(points, _options = {}) {
889
951
  // INPUT VALIDATION: Ensure points array is valid
890
952
  // WHY: This function orchestrates multiple verification functions, all of which
891
953
  // require valid points. Validating once at the top prevents redundant checks.
892
954
  if (!points || !Array.isArray(points) || points.length < 2) {
893
- throw new Error('verifyAllArcLengthFunctions: points must be an array with at least 2 control points');
955
+ throw new Error(
956
+ "verifyAllArcLengthFunctions: points must be an array with at least 2 control points",
957
+ );
894
958
  }
895
959
 
896
960
  const results = {};
@@ -911,11 +975,11 @@ export function verifyAllArcLengthFunctions(points, options = {}) {
911
975
  // 5. Verify table
912
976
  results.table = verifyArcLengthTable(points, 20);
913
977
 
914
- const allValid = Object.values(results).every(r => r.valid);
978
+ const allValid = Object.values(results).every((r) => r.valid);
915
979
 
916
980
  return {
917
981
  valid: allValid,
918
- results
982
+ results,
919
983
  };
920
984
  }
921
985
 
@@ -936,5 +1000,5 @@ export default {
936
1000
  verifyArcLengthBySubdivision,
937
1001
  verifyArcLengthAdditivity,
938
1002
  verifyArcLengthTable,
939
- verifyAllArcLengthFunctions
1003
+ verifyAllArcLengthFunctions,
940
1004
  };