@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
@@ -15,18 +15,18 @@
15
15
  * @version 1.0.0
16
16
  */
17
17
 
18
- import Decimal from 'decimal.js';
18
+ import Decimal from "decimal.js";
19
19
  import {
20
20
  bezierPoint,
21
21
  bezierBoundingBox,
22
22
  bezierSplit,
23
23
  bezierCrop,
24
- bezierDerivative
25
- } from './bezier-analysis.js';
24
+ bezierDerivative,
25
+ } from "./bezier-analysis.js";
26
26
 
27
27
  Decimal.set({ precision: 80 });
28
28
 
29
- const D = x => (x instanceof Decimal ? x : new Decimal(x));
29
+ const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
30
30
 
31
31
  // ============================================================================
32
32
  // LINE-LINE INTERSECTION
@@ -35,10 +35,10 @@ const D = x => (x instanceof Decimal ? x : new Decimal(x));
35
35
  // Numerical thresholds (documented magic numbers)
36
36
  // WHY: Centralizing magic numbers as constants improves maintainability and makes
37
37
  // the code self-documenting. These thresholds were tuned for 80-digit precision arithmetic.
38
- const PARALLEL_THRESHOLD = '1e-60'; // Below this, lines considered parallel
39
- const SINGULARITY_THRESHOLD = '1e-50'; // Below this, Jacobian considered singular
40
- const INTERSECTION_VERIFY_FACTOR = 100; // Multiplier for intersection verification
41
- const DEDUP_TOLERANCE_FACTOR = 1000; // Multiplier for duplicate detection
38
+ const PARALLEL_THRESHOLD = "1e-60"; // Below this, lines considered parallel
39
+ const SINGULARITY_THRESHOLD = "1e-50"; // Below this, Jacobian considered singular
40
+ const INTERSECTION_VERIFY_FACTOR = 100; // Multiplier for intersection verification
41
+ const DEDUP_TOLERANCE_FACTOR = 1000; // Multiplier for duplicate detection
42
42
 
43
43
  /** Maximum Newton iterations for intersection refinement */
44
44
  const MAX_NEWTON_ITERATIONS = 30;
@@ -47,7 +47,7 @@ const MAX_NEWTON_ITERATIONS = 30;
47
47
  const MAX_INTERSECTION_RECURSION_DEPTH = 50;
48
48
 
49
49
  /** Minimum parameter separation for self-intersection detection */
50
- const DEFAULT_MIN_SEPARATION = '0.01';
50
+ const DEFAULT_MIN_SEPARATION = "0.01";
51
51
 
52
52
  /** Maximum bisection iterations for bezier-line refinement */
53
53
  const MAX_BISECTION_ITERATIONS = 100;
@@ -65,10 +65,10 @@ const MAX_BISECTION_ITERATIONS = 100;
65
65
  export function lineLineIntersection(line1, line2) {
66
66
  // Input validation
67
67
  if (!line1 || !Array.isArray(line1) || line1.length !== 2) {
68
- throw new Error('lineLineIntersection: line1 must be an array of 2 points');
68
+ throw new Error("lineLineIntersection: line1 must be an array of 2 points");
69
69
  }
70
70
  if (!line2 || !Array.isArray(line2) || line2.length !== 2) {
71
- throw new Error('lineLineIntersection: line2 must be an array of 2 points');
71
+ throw new Error("lineLineIntersection: line2 must be an array of 2 points");
72
72
  }
73
73
 
74
74
  const [x1, y1] = [D(line1[0][0]), D(line1[0][1])];
@@ -102,11 +102,13 @@ export function lineLineIntersection(line1, line2) {
102
102
  const px = x1.plus(dx1.times(t1));
103
103
  const py = y1.plus(dy1.times(t1));
104
104
 
105
- return [{
106
- t1,
107
- t2,
108
- point: [px, py]
109
- }];
105
+ return [
106
+ {
107
+ t1,
108
+ t2,
109
+ point: [px, py],
110
+ },
111
+ ];
110
112
  }
111
113
 
112
114
  return [];
@@ -130,15 +132,19 @@ export function lineLineIntersection(line1, line2) {
130
132
  * @returns {Array} Intersections [{t1 (bezier), t2 (line), point}]
131
133
  */
132
134
  export function bezierLineIntersection(bezier, line, options = {}) {
133
- const { tolerance = '1e-30', samplesPerDegree = 20 } = options;
135
+ const { tolerance = "1e-30", samplesPerDegree = 20 } = options;
134
136
  const tol = D(tolerance);
135
137
 
136
138
  // Input validation
137
139
  if (!bezier || !Array.isArray(bezier) || bezier.length < 2) {
138
- throw new Error('bezierLineIntersection: bezier must have at least 2 control points');
140
+ throw new Error(
141
+ "bezierLineIntersection: bezier must have at least 2 control points",
142
+ );
139
143
  }
140
144
  if (!line || !Array.isArray(line) || line.length !== 2) {
141
- throw new Error('bezierLineIntersection: line must be an array of 2 points');
145
+ throw new Error(
146
+ "bezierLineIntersection: line must be an array of 2 points",
147
+ );
142
148
  }
143
149
 
144
150
  const [lx0, ly0] = [D(line[0][0]), D(line[0][1])];
@@ -169,7 +175,7 @@ export function bezierLineIntersection(bezier, line, options = {}) {
169
175
 
170
176
  // Distance from line (signed)
171
177
  const dist = by.minus(ly0).times(dlx).minus(bx.minus(lx0).times(dly));
172
- const sign = dist.isNegative() ? -1 : (dist.isZero() ? 0 : 1);
178
+ const sign = dist.isNegative() ? -1 : dist.isZero() ? 0 : 1;
173
179
 
174
180
  if (sign === 0) {
175
181
  // Exactly on line
@@ -218,6 +224,12 @@ export function bezierLineIntersection(bezier, line, options = {}) {
218
224
 
219
225
  /**
220
226
  * Refine Bezier-line intersection using bisection.
227
+ * @param {Array} bezier - Bezier control points
228
+ * @param {Array} line - Line segment [[x0,y0], [x1,y1]]
229
+ * @param {Decimal} t0 - Start of bracket interval
230
+ * @param {Decimal} t1 - End of bracket interval
231
+ * @param {Decimal} tol - Tolerance for convergence
232
+ * @returns {Decimal} Refined parameter value
221
233
  */
222
234
  function refineBezierLineRoot(bezier, line, t0, t1, tol) {
223
235
  const [lx0, ly0] = [D(line[0][0]), D(line[0][1])];
@@ -228,13 +240,13 @@ function refineBezierLineRoot(bezier, line, t0, t1, tol) {
228
240
  let lo = D(t0);
229
241
  let hi = D(t1);
230
242
 
231
- const evalDist = t => {
243
+ const evalDist = (t) => {
232
244
  const [bx, by] = bezierPoint(bezier, t);
233
245
  return by.minus(ly0).times(dlx).minus(bx.minus(lx0).times(dly));
234
246
  };
235
247
 
236
248
  let fLo = evalDist(lo);
237
- let fHi = evalDist(hi);
249
+ let _fHi = evalDist(hi);
238
250
 
239
251
  // WHY: Use named constant instead of magic number for clarity and maintainability
240
252
  for (let i = 0; i < MAX_BISECTION_ITERATIONS; i++) {
@@ -245,12 +257,15 @@ function refineBezierLineRoot(bezier, line, t0, t1, tol) {
245
257
  return mid;
246
258
  }
247
259
 
248
- if ((fLo.isNegative() && fMid.isNegative()) || (fLo.isPositive() && fMid.isPositive())) {
260
+ if (
261
+ (fLo.isNegative() && fMid.isNegative()) ||
262
+ (fLo.isPositive() && fMid.isPositive())
263
+ ) {
249
264
  lo = mid;
250
265
  fLo = fMid;
251
266
  } else {
252
267
  hi = mid;
253
- fHi = fMid;
268
+ _fHi = fMid;
254
269
  }
255
270
  }
256
271
 
@@ -278,17 +293,19 @@ function refineBezierLineRoot(bezier, line, t0, t1, tol) {
278
293
  */
279
294
  export function bezierBezierIntersection(bezier1, bezier2, options = {}) {
280
295
  // WHY: Use named constant as default instead of hardcoded 50 for clarity
281
- const {
282
- tolerance = '1e-30',
283
- maxDepth = MAX_INTERSECTION_RECURSION_DEPTH
284
- } = options;
296
+ const { tolerance = "1e-30", maxDepth = MAX_INTERSECTION_RECURSION_DEPTH } =
297
+ options;
285
298
 
286
299
  // Input validation
287
300
  if (!bezier1 || !Array.isArray(bezier1) || bezier1.length < 2) {
288
- throw new Error('bezierBezierIntersection: bezier1 must have at least 2 control points');
301
+ throw new Error(
302
+ "bezierBezierIntersection: bezier1 must have at least 2 control points",
303
+ );
289
304
  }
290
305
  if (!bezier2 || !Array.isArray(bezier2) || bezier2.length < 2) {
291
- throw new Error('bezierBezierIntersection: bezier2 must have at least 2 control points');
306
+ throw new Error(
307
+ "bezierBezierIntersection: bezier2 must have at least 2 control points",
308
+ );
292
309
  }
293
310
 
294
311
  const tol = D(tolerance);
@@ -388,7 +405,7 @@ function refineIntersection(bez1, bez2, t1, t2, tol) {
388
405
  t1: currentT1,
389
406
  t2: currentT2,
390
407
  point: [x1, y1],
391
- error
408
+ error,
392
409
  };
393
410
  }
394
411
 
@@ -422,13 +439,16 @@ function refineIntersection(bez1, bez2, t1, t2, tol) {
422
439
  // so it may not reflect the final accuracy. We need to recompute error for the converged parameters.
423
440
  const [finalX, finalY] = bezierPoint(bez1, currentT1);
424
441
  const [finalX2, finalY2] = bezierPoint(bez2, currentT2);
425
- const finalError = D(finalX).minus(D(finalX2)).pow(2)
426
- .plus(D(finalY).minus(D(finalY2)).pow(2)).sqrt();
442
+ const finalError = D(finalX)
443
+ .minus(D(finalX2))
444
+ .pow(2)
445
+ .plus(D(finalY).minus(D(finalY2)).pow(2))
446
+ .sqrt();
427
447
  return {
428
448
  t1: currentT1,
429
449
  t2: currentT2,
430
450
  point: [finalX, finalY],
431
- error: finalError
451
+ error: finalError,
432
452
  };
433
453
  }
434
454
  }
@@ -438,6 +458,9 @@ function refineIntersection(bez1, bez2, t1, t2, tol) {
438
458
 
439
459
  /**
440
460
  * Check if two bounding boxes overlap.
461
+ * @param {Object} bbox1 - First bounding box with {xmin, xmax, ymin, ymax}
462
+ * @param {Object} bbox2 - Second bounding box with {xmin, xmax, ymin, ymax}
463
+ * @returns {boolean} True if bounding boxes overlap
441
464
  */
442
465
  function bboxOverlap(bbox1, bbox2) {
443
466
  // INPUT VALIDATION
@@ -446,14 +469,19 @@ function bboxOverlap(bbox1, bbox2) {
446
469
  return false; // No overlap if either bbox is missing
447
470
  }
448
471
 
449
- return !(bbox1.xmax.lt(bbox2.xmin) ||
450
- bbox1.xmin.gt(bbox2.xmax) ||
451
- bbox1.ymax.lt(bbox2.ymin) ||
452
- bbox1.ymin.gt(bbox2.ymax));
472
+ return !(
473
+ bbox1.xmax.lt(bbox2.xmin) ||
474
+ bbox1.xmin.gt(bbox2.xmax) ||
475
+ bbox1.ymax.lt(bbox2.ymin) ||
476
+ bbox1.ymin.gt(bbox2.ymax)
477
+ );
453
478
  }
454
479
 
455
480
  /**
456
481
  * Remove duplicate intersections.
482
+ * @param {Array} intersections - Array of intersection objects with t1, t2 parameters
483
+ * @param {Decimal} tol - Tolerance for considering intersections duplicate
484
+ * @returns {Array} Array of unique intersections
457
485
  */
458
486
  function deduplicateIntersections(intersections, tol) {
459
487
  const result = [];
@@ -465,7 +493,10 @@ function deduplicateIntersections(intersections, tol) {
465
493
  const dt1 = isect.t1.minus(existing.t1).abs();
466
494
  const dt2 = isect.t2.minus(existing.t2).abs();
467
495
 
468
- if (dt1.lt(tol.times(DEDUP_TOLERANCE_FACTOR)) && dt2.lt(tol.times(DEDUP_TOLERANCE_FACTOR))) {
496
+ if (
497
+ dt1.lt(tol.times(DEDUP_TOLERANCE_FACTOR)) &&
498
+ dt2.lt(tol.times(DEDUP_TOLERANCE_FACTOR))
499
+ ) {
469
500
  isDuplicate = true;
470
501
  break;
471
502
  }
@@ -498,13 +529,19 @@ function deduplicateIntersections(intersections, tol) {
498
529
  */
499
530
  export function bezierSelfIntersection(bezier, options = {}) {
500
531
  // WHY: Use named constants as defaults instead of hardcoded values for clarity
501
- const { tolerance = '1e-30', minSeparation = DEFAULT_MIN_SEPARATION, maxDepth = 30 } = options;
532
+ const {
533
+ tolerance = "1e-30",
534
+ minSeparation = DEFAULT_MIN_SEPARATION,
535
+ maxDepth = 30,
536
+ } = options;
502
537
  const tol = D(tolerance);
503
538
  const minSep = D(minSeparation);
504
539
 
505
540
  // Input validation
506
541
  if (!bezier || bezier.length < 2) {
507
- throw new Error('bezierSelfIntersection: bezier must have at least 2 control points');
542
+ throw new Error(
543
+ "bezierSelfIntersection: bezier must have at least 2 control points",
544
+ );
508
545
  }
509
546
 
510
547
  // Self-intersections only possible for cubic and higher
@@ -537,7 +574,10 @@ export function bezierSelfIntersection(bezier, options = {}) {
537
574
  const rightPts = bezierCrop(bezier, tmid, tmax);
538
575
 
539
576
  // Find intersections between left and right portions
540
- const isects = bezierBezierIntersection(leftPts, rightPts, { tolerance, maxDepth: maxDepth - depth });
577
+ const isects = bezierBezierIntersection(leftPts, rightPts, {
578
+ tolerance,
579
+ maxDepth: maxDepth - depth,
580
+ });
541
581
 
542
582
  for (const isect of isects) {
543
583
  // Map from cropped parameter space [0,1] back to original range
@@ -552,7 +592,7 @@ export function bezierSelfIntersection(bezier, options = {}) {
552
592
  results.push({
553
593
  t1: Decimal.min(origT1, origT2),
554
594
  t2: Decimal.max(origT1, origT2),
555
- point: isect.point
595
+ point: isect.point,
556
596
  });
557
597
  }
558
598
  }
@@ -569,7 +609,7 @@ export function bezierSelfIntersection(bezier, options = {}) {
569
609
  // The recursive subdivision can find the same intersection from multiple branches,
570
610
  // with slightly different parameter values. Use minSep as the dedup tolerance
571
611
  // since intersections closer than minSep in parameter space are considered the same.
572
- const dedupTol = minSep.div(10); // Use 1/10 of minSeparation for deduplication
612
+ const dedupTol = minSep.div(10); // Use 1/10 of minSeparation for deduplication
573
613
  const deduped = deduplicateIntersections(results, dedupTol);
574
614
 
575
615
  // WHY: After finding rough intersections via subdivision on cropped curves,
@@ -577,7 +617,13 @@ export function bezierSelfIntersection(bezier, options = {}) {
577
617
  // full precision because we're optimizing directly on the original parameters.
578
618
  const refined = [];
579
619
  for (const isect of deduped) {
580
- const refinedIsect = refineSelfIntersection(bezier, isect.t1, isect.t2, tol, minSep);
620
+ const refinedIsect = refineSelfIntersection(
621
+ bezier,
622
+ isect.t1,
623
+ isect.t2,
624
+ tol,
625
+ minSep,
626
+ );
581
627
  if (refinedIsect) {
582
628
  refined.push(refinedIsect);
583
629
  } else {
@@ -614,8 +660,14 @@ function refineSelfIntersection(bezier, t1Init, t2Init, tol, minSep) {
614
660
  const mid = t1.plus(t2).div(2);
615
661
  t1 = mid.minus(minSep.div(2));
616
662
  t2 = mid.plus(minSep.div(2));
617
- if (t1.lt(0)) { t1 = D(0); t2 = minSep; }
618
- if (t2.gt(1)) { t2 = D(1); t1 = D(1).minus(minSep); }
663
+ if (t1.lt(0)) {
664
+ t1 = D(0);
665
+ t2 = minSep;
666
+ }
667
+ if (t2.gt(1)) {
668
+ t2 = D(1);
669
+ t1 = D(1).minus(minSep);
670
+ }
619
671
  }
620
672
 
621
673
  // Evaluate curve at both parameters
@@ -632,8 +684,8 @@ function refineSelfIntersection(bezier, t1Init, t2Init, tol, minSep) {
632
684
  return {
633
685
  t1: Decimal.min(t1, t2),
634
686
  t2: Decimal.max(t1, t2),
635
- point: [x1.plus(x2).div(2), y1.plus(y2).div(2)], // Average of both points
636
- error
687
+ point: [x1.plus(x2).div(2), y1.plus(y2).div(2)], // Average of both points
688
+ error,
637
689
  };
638
690
  }
639
691
 
@@ -649,9 +701,9 @@ function refineSelfIntersection(bezier, t1Init, t2Init, tol, minSep) {
649
701
  // Singular Jacobian - curves are nearly parallel at these points
650
702
  // Try bisection step instead
651
703
  if (fx.isNegative()) {
652
- t1 = t1.plus(D('0.0001'));
704
+ t1 = t1.plus(D("0.0001"));
653
705
  } else {
654
- t2 = t2.minus(D('0.0001'));
706
+ t2 = t2.minus(D("0.0001"));
655
707
  }
656
708
  continue;
657
709
  }
@@ -662,8 +714,8 @@ function refineSelfIntersection(bezier, t1Init, t2Init, tol, minSep) {
662
714
  // J^{-1} = (1/det) * [[-dy2, dx2], [-dy1, dx1]]
663
715
  // J^{-1} * f = (1/det) * [-dy2*fx + dx2*fy, -dy1*fx + dx1*fy]
664
716
  // Newton update: t_new = t - J^{-1}*f
665
- const dt1 = dx2.times(fy).minus(dy2.times(fx)).div(det); // -dy2*fx + dx2*fy
666
- const dt2 = dx1.times(fy).minus(dy1.times(fx)).div(det); // -dy1*fx + dx1*fy
717
+ const dt1 = dx2.times(fy).minus(dy2.times(fx)).div(det); // -dy2*fx + dx2*fy
718
+ const dt2 = dx1.times(fy).minus(dy1.times(fx)).div(det); // -dy1*fx + dx1*fy
667
719
 
668
720
  t1 = t1.minus(dt1);
669
721
  t2 = t2.minus(dt2);
@@ -673,19 +725,22 @@ function refineSelfIntersection(bezier, t1Init, t2Init, tol, minSep) {
673
725
  // Recompute final error
674
726
  const [finalX1, finalY1] = bezierPoint(bezier, t1);
675
727
  const [finalX2, finalY2] = bezierPoint(bezier, t2);
676
- const finalError = finalX1.minus(finalX2).pow(2)
677
- .plus(finalY1.minus(finalY2).pow(2)).sqrt();
728
+ const finalError = finalX1
729
+ .minus(finalX2)
730
+ .pow(2)
731
+ .plus(finalY1.minus(finalY2).pow(2))
732
+ .sqrt();
678
733
 
679
734
  return {
680
735
  t1: Decimal.min(t1, t2),
681
736
  t2: Decimal.max(t1, t2),
682
737
  point: [finalX1.plus(finalX2).div(2), finalY1.plus(finalY2).div(2)],
683
- error: finalError
738
+ error: finalError,
684
739
  };
685
740
  }
686
741
  }
687
742
 
688
- return null; // Failed to converge
743
+ return null; // Failed to converge
689
744
  }
690
745
 
691
746
  // ============================================================================
@@ -704,10 +759,10 @@ export function pathPathIntersection(path1, path2, options = {}) {
704
759
  // INPUT VALIDATION
705
760
  // WHY: Prevent cryptic errors from undefined/null paths. Fail fast with clear messages.
706
761
  if (!path1 || !Array.isArray(path1)) {
707
- throw new Error('pathPathIntersection: path1 must be an array');
762
+ throw new Error("pathPathIntersection: path1 must be an array");
708
763
  }
709
764
  if (!path2 || !Array.isArray(path2)) {
710
- throw new Error('pathPathIntersection: path2 must be an array');
765
+ throw new Error("pathPathIntersection: path2 must be an array");
711
766
  }
712
767
 
713
768
  // Handle empty paths gracefully
@@ -728,7 +783,7 @@ export function pathPathIntersection(path1, path2, options = {}) {
728
783
  segment2: j,
729
784
  t1: isect.t1,
730
785
  t2: isect.t2,
731
- point: isect.point
786
+ point: isect.point,
732
787
  });
733
788
  }
734
789
  }
@@ -748,7 +803,7 @@ export function pathSelfIntersection(path, options = {}) {
748
803
  // INPUT VALIDATION
749
804
  // WHY: Prevent cryptic errors from undefined/null path. Fail fast with clear messages.
750
805
  if (!path || !Array.isArray(path)) {
751
- throw new Error('pathSelfIntersection: path must be an array');
806
+ throw new Error("pathSelfIntersection: path must be an array");
752
807
  }
753
808
 
754
809
  // Handle empty or single-segment paths
@@ -768,7 +823,7 @@ export function pathSelfIntersection(path, options = {}) {
768
823
  segment2: i,
769
824
  t1: isect.t1,
770
825
  t2: isect.t2,
771
- point: isect.point
826
+ point: isect.point,
772
827
  });
773
828
  }
774
829
  }
@@ -779,7 +834,7 @@ export function pathSelfIntersection(path, options = {}) {
779
834
  // WHY: j starts at i+2, so segments i and j are never adjacent (which would be i and i+1)
780
835
  // However, for closed paths, first (i=0) and last (j=path.length-1) segments ARE adjacent
781
836
  // because they share the start/end vertex. Skip this pair.
782
- const isClosedPathAdjacent = (i === 0 && j === path.length - 1);
837
+ const isClosedPathAdjacent = i === 0 && j === path.length - 1;
783
838
  if (isClosedPathAdjacent) continue;
784
839
 
785
840
  const isects = bezierBezierIntersection(path[i], path[j], options);
@@ -790,7 +845,7 @@ export function pathSelfIntersection(path, options = {}) {
790
845
  segment2: j,
791
846
  t1: isect.t1,
792
847
  t2: isect.t2,
793
- point: isect.point
848
+ point: isect.point,
794
849
  });
795
850
  }
796
851
  }
@@ -812,17 +867,26 @@ export function pathSelfIntersection(path, options = {}) {
812
867
  * @param {string} [tolerance='1e-20'] - Verification tolerance
813
868
  * @returns {{valid: boolean, distance: Decimal}}
814
869
  */
815
- export function verifyIntersection(bez1, bez2, intersection, tolerance = '1e-20') {
870
+ export function verifyIntersection(
871
+ bez1,
872
+ bez2,
873
+ intersection,
874
+ tolerance = "1e-20",
875
+ ) {
816
876
  // INPUT VALIDATION
817
877
  // WHY: Ensure all required data is present before computation. Prevents undefined errors.
818
878
  if (!bez1 || !Array.isArray(bez1) || bez1.length < 2) {
819
- throw new Error('verifyIntersection: bez1 must have at least 2 control points');
879
+ throw new Error(
880
+ "verifyIntersection: bez1 must have at least 2 control points",
881
+ );
820
882
  }
821
883
  if (!bez2 || !Array.isArray(bez2) || bez2.length < 2) {
822
- throw new Error('verifyIntersection: bez2 must have at least 2 control points');
884
+ throw new Error(
885
+ "verifyIntersection: bez2 must have at least 2 control points",
886
+ );
823
887
  }
824
888
  if (!intersection) {
825
- throw new Error('verifyIntersection: intersection object is required');
889
+ throw new Error("verifyIntersection: intersection object is required");
826
890
  }
827
891
 
828
892
  const tol = D(tolerance);
@@ -836,7 +900,7 @@ export function verifyIntersection(bez1, bez2, intersection, tolerance = '1e-20'
836
900
  valid: distance.lt(tol),
837
901
  distance,
838
902
  point1: [x1, y1],
839
- point2: [x2, y2]
903
+ point2: [x2, y2],
840
904
  };
841
905
  }
842
906
 
@@ -852,23 +916,34 @@ export function verifyIntersection(bez1, bez2, intersection, tolerance = '1e-20'
852
916
  * @param {string} [tolerance='1e-40'] - Verification tolerance
853
917
  * @returns {{valid: boolean, parametricError1: Decimal, parametricError2: Decimal, algebraicError: Decimal, crossProductError: Decimal}}
854
918
  */
855
- export function verifyLineLineIntersection(line1, line2, intersection, tolerance = '1e-40') {
919
+ export function verifyLineLineIntersection(
920
+ line1,
921
+ line2,
922
+ intersection,
923
+ tolerance = "1e-40",
924
+ ) {
856
925
  // INPUT VALIDATION
857
926
  // WHY: Verify all required inputs before processing. Fail fast with clear error messages.
858
927
  if (!line1 || !Array.isArray(line1) || line1.length !== 2) {
859
- throw new Error('verifyLineLineIntersection: line1 must be an array of 2 points');
928
+ throw new Error(
929
+ "verifyLineLineIntersection: line1 must be an array of 2 points",
930
+ );
860
931
  }
861
932
  if (!line2 || !Array.isArray(line2) || line2.length !== 2) {
862
- throw new Error('verifyLineLineIntersection: line2 must be an array of 2 points');
933
+ throw new Error(
934
+ "verifyLineLineIntersection: line2 must be an array of 2 points",
935
+ );
863
936
  }
864
937
  if (!intersection) {
865
- throw new Error('verifyLineLineIntersection: intersection object is required');
938
+ throw new Error(
939
+ "verifyLineLineIntersection: intersection object is required",
940
+ );
866
941
  }
867
942
 
868
943
  const tol = D(tolerance);
869
944
 
870
945
  if (!intersection.t1) {
871
- return { valid: false, reason: 'No intersection provided' };
946
+ return { valid: false, reason: "No intersection provided" };
872
947
  }
873
948
 
874
949
  const [x1, y1] = [D(line1[0][0]), D(line1[0][1])];
@@ -886,17 +961,35 @@ export function verifyLineLineIntersection(line1, line2, intersection, tolerance
886
961
  const p2x = x3.plus(x4.minus(x3).times(t2));
887
962
  const p2y = y3.plus(y4.minus(y3).times(t2));
888
963
 
889
- const parametricError1 = px.minus(p1x).pow(2).plus(py.minus(p1y).pow(2)).sqrt();
890
- const parametricError2 = px.minus(p2x).pow(2).plus(py.minus(p2y).pow(2)).sqrt();
891
- const pointMismatchError = p1x.minus(p2x).pow(2).plus(p1y.minus(p2y).pow(2)).sqrt();
964
+ const parametricError1 = px
965
+ .minus(p1x)
966
+ .pow(2)
967
+ .plus(py.minus(p1y).pow(2))
968
+ .sqrt();
969
+ const parametricError2 = px
970
+ .minus(p2x)
971
+ .pow(2)
972
+ .plus(py.minus(p2y).pow(2))
973
+ .sqrt();
974
+ const pointMismatchError = p1x
975
+ .minus(p2x)
976
+ .pow(2)
977
+ .plus(p1y.minus(p2y).pow(2))
978
+ .sqrt();
892
979
 
893
980
  // 2. Algebraic verification: substitute into line equations
894
981
  // Line 1: (y - y1) / (y2 - y1) = (x - x1) / (x2 - x1)
895
982
  // Cross-multiply: (y - y1)(x2 - x1) = (x - x1)(y2 - y1)
896
- const algebraicError1 = py.minus(y1).times(x2.minus(x1))
897
- .minus(px.minus(x1).times(y2.minus(y1))).abs();
898
- const algebraicError2 = py.minus(y3).times(x4.minus(x3))
899
- .minus(px.minus(x3).times(y4.minus(y3))).abs();
983
+ const algebraicError1 = py
984
+ .minus(y1)
985
+ .times(x2.minus(x1))
986
+ .minus(px.minus(x1).times(y2.minus(y1)))
987
+ .abs();
988
+ const algebraicError2 = py
989
+ .minus(y3)
990
+ .times(x4.minus(x3))
991
+ .minus(px.minus(x3).times(y4.minus(y3)))
992
+ .abs();
900
993
 
901
994
  // 3. Cross product verification: vectors from endpoints to intersection should be collinear
902
995
  const v1x = px.minus(x1);
@@ -912,8 +1005,13 @@ export function verifyLineLineIntersection(line1, line2, intersection, tolerance
912
1005
  const crossProduct2 = v3x.times(v4y).minus(v3y.times(v4x)).abs();
913
1006
 
914
1007
  const maxError = Decimal.max(
915
- parametricError1, parametricError2, pointMismatchError,
916
- algebraicError1, algebraicError2, crossProduct1, crossProduct2
1008
+ parametricError1,
1009
+ parametricError2,
1010
+ pointMismatchError,
1011
+ algebraicError1,
1012
+ algebraicError2,
1013
+ crossProduct1,
1014
+ crossProduct2,
917
1015
  );
918
1016
 
919
1017
  return {
@@ -925,7 +1023,7 @@ export function verifyLineLineIntersection(line1, line2, intersection, tolerance
925
1023
  algebraicError2,
926
1024
  crossProduct1,
927
1025
  crossProduct2,
928
- maxError
1026
+ maxError,
929
1027
  };
930
1028
  }
931
1029
 
@@ -941,23 +1039,34 @@ export function verifyLineLineIntersection(line1, line2, intersection, tolerance
941
1039
  * @param {string} [tolerance='1e-30'] - Verification tolerance
942
1040
  * @returns {{valid: boolean, bezierError: Decimal, lineError: Decimal, signedDistance: Decimal}}
943
1041
  */
944
- export function verifyBezierLineIntersection(bezier, line, intersection, tolerance = '1e-30') {
1042
+ export function verifyBezierLineIntersection(
1043
+ bezier,
1044
+ line,
1045
+ intersection,
1046
+ tolerance = "1e-30",
1047
+ ) {
945
1048
  // INPUT VALIDATION
946
1049
  // WHY: Ensure all required inputs are valid before verification. Prevents undefined behavior.
947
1050
  if (!bezier || !Array.isArray(bezier) || bezier.length < 2) {
948
- throw new Error('verifyBezierLineIntersection: bezier must have at least 2 control points');
1051
+ throw new Error(
1052
+ "verifyBezierLineIntersection: bezier must have at least 2 control points",
1053
+ );
949
1054
  }
950
1055
  if (!line || !Array.isArray(line) || line.length !== 2) {
951
- throw new Error('verifyBezierLineIntersection: line must be an array of 2 points');
1056
+ throw new Error(
1057
+ "verifyBezierLineIntersection: line must be an array of 2 points",
1058
+ );
952
1059
  }
953
1060
  if (!intersection) {
954
- throw new Error('verifyBezierLineIntersection: intersection object is required');
1061
+ throw new Error(
1062
+ "verifyBezierLineIntersection: intersection object is required",
1063
+ );
955
1064
  }
956
1065
 
957
1066
  const tol = D(tolerance);
958
1067
 
959
1068
  if (intersection.t1 === undefined) {
960
- return { valid: false, reason: 'No intersection provided' };
1069
+ return { valid: false, reason: "No intersection provided" };
961
1070
  }
962
1071
 
963
1072
  const t1 = D(intersection.t1);
@@ -976,18 +1085,37 @@ export function verifyBezierLineIntersection(bezier, line, intersection, toleran
976
1085
  const dly = ly1.minus(ly0);
977
1086
  const expectedLineX = lx0.plus(dlx.times(t2));
978
1087
  const expectedLineY = ly0.plus(dly.times(t2));
979
- const lineError = px.minus(expectedLineX).pow(2).plus(py.minus(expectedLineY).pow(2)).sqrt();
1088
+ const lineError = px
1089
+ .minus(expectedLineX)
1090
+ .pow(2)
1091
+ .plus(py.minus(expectedLineY).pow(2))
1092
+ .sqrt();
980
1093
 
981
1094
  // 3. Signed distance from line
982
1095
  // dist = ((y - ly0) * dlx - (x - lx0) * dly) / sqrt(dlx^2 + dly^2)
983
1096
  const lineLen = dlx.pow(2).plus(dly.pow(2)).sqrt();
984
- const signedDistance = lineLen.isZero() ? D(0) :
985
- py.minus(ly0).times(dlx).minus(px.minus(lx0).times(dly)).div(lineLen).abs();
1097
+ const signedDistance = lineLen.isZero()
1098
+ ? D(0)
1099
+ : py
1100
+ .minus(ly0)
1101
+ .times(dlx)
1102
+ .minus(px.minus(lx0).times(dly))
1103
+ .div(lineLen)
1104
+ .abs();
986
1105
 
987
1106
  // 4. Verify bezier point matches line point
988
- const pointMismatch = bx.minus(expectedLineX).pow(2).plus(by.minus(expectedLineY).pow(2)).sqrt();
1107
+ const pointMismatch = bx
1108
+ .minus(expectedLineX)
1109
+ .pow(2)
1110
+ .plus(by.minus(expectedLineY).pow(2))
1111
+ .sqrt();
989
1112
 
990
- const maxError = Decimal.max(bezierError, lineError, signedDistance, pointMismatch);
1113
+ const maxError = Decimal.max(
1114
+ bezierError,
1115
+ lineError,
1116
+ signedDistance,
1117
+ pointMismatch,
1118
+ );
991
1119
 
992
1120
  return {
993
1121
  valid: maxError.lt(tol),
@@ -997,7 +1125,7 @@ export function verifyBezierLineIntersection(bezier, line, intersection, toleran
997
1125
  pointMismatch,
998
1126
  bezierPoint: [bx, by],
999
1127
  linePoint: [expectedLineX, expectedLineY],
1000
- maxError
1128
+ maxError,
1001
1129
  };
1002
1130
  }
1003
1131
 
@@ -1013,23 +1141,34 @@ export function verifyBezierLineIntersection(bezier, line, intersection, toleran
1013
1141
  * @param {string} [tolerance='1e-30'] - Verification tolerance
1014
1142
  * @returns {{valid: boolean, distance: Decimal, point1: Array, point2: Array, refinementConverged: boolean}}
1015
1143
  */
1016
- export function verifyBezierBezierIntersection(bez1, bez2, intersection, tolerance = '1e-30') {
1144
+ export function verifyBezierBezierIntersection(
1145
+ bez1,
1146
+ bez2,
1147
+ intersection,
1148
+ tolerance = "1e-30",
1149
+ ) {
1017
1150
  // INPUT VALIDATION
1018
1151
  // WHY: Validate inputs before verification to prevent unexpected errors from invalid data.
1019
1152
  if (!bez1 || !Array.isArray(bez1) || bez1.length < 2) {
1020
- throw new Error('verifyBezierBezierIntersection: bez1 must have at least 2 control points');
1153
+ throw new Error(
1154
+ "verifyBezierBezierIntersection: bez1 must have at least 2 control points",
1155
+ );
1021
1156
  }
1022
1157
  if (!bez2 || !Array.isArray(bez2) || bez2.length < 2) {
1023
- throw new Error('verifyBezierBezierIntersection: bez2 must have at least 2 control points');
1158
+ throw new Error(
1159
+ "verifyBezierBezierIntersection: bez2 must have at least 2 control points",
1160
+ );
1024
1161
  }
1025
1162
  if (!intersection) {
1026
- throw new Error('verifyBezierBezierIntersection: intersection object is required');
1163
+ throw new Error(
1164
+ "verifyBezierBezierIntersection: intersection object is required",
1165
+ );
1027
1166
  }
1028
1167
 
1029
1168
  const tol = D(tolerance);
1030
1169
 
1031
1170
  if (intersection.t1 === undefined) {
1032
- return { valid: false, reason: 'No intersection provided' };
1171
+ return { valid: false, reason: "No intersection provided" };
1033
1172
  }
1034
1173
 
1035
1174
  const t1 = D(intersection.t1);
@@ -1054,10 +1193,10 @@ export function verifyBezierBezierIntersection(bez1, bez2, intersection, toleran
1054
1193
  // If intersection is real, perturbations should converge back
1055
1194
  let refinementConverged = true;
1056
1195
  const perturbations = [
1057
- [D('0.001'), D(0)],
1058
- [D('-0.001'), D(0)],
1059
- [D(0), D('0.001')],
1060
- [D(0), D('-0.001')]
1196
+ [D("0.001"), D(0)],
1197
+ [D("-0.001"), D(0)],
1198
+ [D(0), D("0.001")],
1199
+ [D(0), D("-0.001")],
1061
1200
  ];
1062
1201
 
1063
1202
  for (const [dt1, dt2] of perturbations) {
@@ -1092,7 +1231,7 @@ export function verifyBezierBezierIntersection(bez1, bez2, intersection, toleran
1092
1231
  refinementConverged,
1093
1232
  t1InBounds,
1094
1233
  t2InBounds,
1095
- maxError
1234
+ maxError,
1096
1235
  };
1097
1236
  }
1098
1237
 
@@ -1108,21 +1247,28 @@ export function verifyBezierBezierIntersection(bez1, bez2, intersection, toleran
1108
1247
  * @param {string} [minSeparation='0.01'] - Minimum parameter separation
1109
1248
  * @returns {{valid: boolean, distance: Decimal, separation: Decimal}}
1110
1249
  */
1111
- export function verifySelfIntersection(bezier, intersection, tolerance = '1e-30', minSeparation = '0.01') {
1250
+ export function verifySelfIntersection(
1251
+ bezier,
1252
+ intersection,
1253
+ tolerance = "1e-30",
1254
+ minSeparation = "0.01",
1255
+ ) {
1112
1256
  // INPUT VALIDATION
1113
1257
  // WHY: Ensure curve and intersection data are valid before attempting verification.
1114
1258
  if (!bezier || !Array.isArray(bezier) || bezier.length < 2) {
1115
- throw new Error('verifySelfIntersection: bezier must have at least 2 control points');
1259
+ throw new Error(
1260
+ "verifySelfIntersection: bezier must have at least 2 control points",
1261
+ );
1116
1262
  }
1117
1263
  if (!intersection) {
1118
- throw new Error('verifySelfIntersection: intersection object is required');
1264
+ throw new Error("verifySelfIntersection: intersection object is required");
1119
1265
  }
1120
1266
 
1121
1267
  const tol = D(tolerance);
1122
1268
  const minSep = D(minSeparation);
1123
1269
 
1124
1270
  if (intersection.t1 === undefined) {
1125
- return { valid: false, reason: 'No intersection provided' };
1271
+ return { valid: false, reason: "No intersection provided" };
1126
1272
  }
1127
1273
 
1128
1274
  const t1 = D(intersection.t1);
@@ -1147,25 +1293,53 @@ export function verifySelfIntersection(bezier, intersection, tolerance = '1e-30'
1147
1293
 
1148
1294
  // 5. Verify by sampling nearby - true self-intersection is stable
1149
1295
  let stableIntersection = true;
1150
- const epsilon = D('0.0001');
1296
+ const epsilon = D("0.0001");
1151
1297
 
1152
1298
  // Sample points slightly before and after each parameter
1153
- const [xBefore1, yBefore1] = bezierPoint(bezier, Decimal.max(D(0), t1.minus(epsilon)));
1154
- const [xAfter1, yAfter1] = bezierPoint(bezier, Decimal.min(D(1), t1.plus(epsilon)));
1155
- const [xBefore2, yBefore2] = bezierPoint(bezier, Decimal.max(D(0), t2.minus(epsilon)));
1156
- const [xAfter2, yAfter2] = bezierPoint(bezier, Decimal.min(D(1), t2.plus(epsilon)));
1299
+ const [xBefore1, yBefore1] = bezierPoint(
1300
+ bezier,
1301
+ Decimal.max(D(0), t1.minus(epsilon)),
1302
+ );
1303
+ const [xAfter1, yAfter1] = bezierPoint(
1304
+ bezier,
1305
+ Decimal.min(D(1), t1.plus(epsilon)),
1306
+ );
1307
+ const [xBefore2, yBefore2] = bezierPoint(
1308
+ bezier,
1309
+ Decimal.max(D(0), t2.minus(epsilon)),
1310
+ );
1311
+ const [xAfter2, yAfter2] = bezierPoint(
1312
+ bezier,
1313
+ Decimal.min(D(1), t2.plus(epsilon)),
1314
+ );
1157
1315
 
1158
1316
  // The curve portions should cross (distances should increase on both sides)
1159
- const distBefore = xBefore1.minus(xBefore2).pow(2).plus(yBefore1.minus(yBefore2).pow(2)).sqrt();
1160
- const distAfter = xAfter1.minus(xAfter2).pow(2).plus(yAfter1.minus(yAfter2).pow(2)).sqrt();
1317
+ const distBefore = xBefore1
1318
+ .minus(xBefore2)
1319
+ .pow(2)
1320
+ .plus(yBefore1.minus(yBefore2).pow(2))
1321
+ .sqrt();
1322
+ const distAfter = xAfter1
1323
+ .minus(xAfter2)
1324
+ .pow(2)
1325
+ .plus(yAfter1.minus(yAfter2).pow(2))
1326
+ .sqrt();
1161
1327
 
1162
1328
  // Both neighboring distances should be larger than intersection distance
1163
- if (!distBefore.gt(distance.minus(tol)) || !distAfter.gt(distance.minus(tol))) {
1329
+ if (
1330
+ !distBefore.gt(distance.minus(tol)) ||
1331
+ !distAfter.gt(distance.minus(tol))
1332
+ ) {
1164
1333
  stableIntersection = false;
1165
1334
  }
1166
1335
 
1167
1336
  return {
1168
- valid: distance.lt(tol) && sufficientSeparation && t1InBounds && t2InBounds && properlyOrdered,
1337
+ valid:
1338
+ distance.lt(tol) &&
1339
+ sufficientSeparation &&
1340
+ t1InBounds &&
1341
+ t2InBounds &&
1342
+ properlyOrdered,
1169
1343
  distance,
1170
1344
  separation,
1171
1345
  sufficientSeparation,
@@ -1174,7 +1348,7 @@ export function verifySelfIntersection(bezier, intersection, tolerance = '1e-30'
1174
1348
  properlyOrdered,
1175
1349
  stableIntersection,
1176
1350
  point1: [x1, y1],
1177
- point2: [x2, y2]
1351
+ point2: [x2, y2],
1178
1352
  };
1179
1353
  }
1180
1354
 
@@ -1187,17 +1361,24 @@ export function verifySelfIntersection(bezier, intersection, tolerance = '1e-30'
1187
1361
  * @param {string} [tolerance='1e-30'] - Verification tolerance
1188
1362
  * @returns {{valid: boolean, results: Array, invalidCount: number}}
1189
1363
  */
1190
- export function verifyPathPathIntersection(path1, path2, intersections, tolerance = '1e-30') {
1364
+ export function verifyPathPathIntersection(
1365
+ path1,
1366
+ path2,
1367
+ intersections,
1368
+ tolerance = "1e-30",
1369
+ ) {
1191
1370
  // INPUT VALIDATION
1192
1371
  // WHY: Validate all inputs before processing to ensure meaningful error messages.
1193
1372
  if (!path1 || !Array.isArray(path1)) {
1194
- throw new Error('verifyPathPathIntersection: path1 must be an array');
1373
+ throw new Error("verifyPathPathIntersection: path1 must be an array");
1195
1374
  }
1196
1375
  if (!path2 || !Array.isArray(path2)) {
1197
- throw new Error('verifyPathPathIntersection: path2 must be an array');
1376
+ throw new Error("verifyPathPathIntersection: path2 must be an array");
1198
1377
  }
1199
1378
  if (!intersections || !Array.isArray(intersections)) {
1200
- throw new Error('verifyPathPathIntersection: intersections must be an array');
1379
+ throw new Error(
1380
+ "verifyPathPathIntersection: intersections must be an array",
1381
+ );
1201
1382
  }
1202
1383
 
1203
1384
  const results = [];
@@ -1208,12 +1389,17 @@ export function verifyPathPathIntersection(path1, path2, intersections, toleranc
1208
1389
  const seg2 = path2[isect.segment2];
1209
1390
 
1210
1391
  if (!seg1 || !seg2) {
1211
- results.push({ valid: false, reason: 'Invalid segment index' });
1392
+ results.push({ valid: false, reason: "Invalid segment index" });
1212
1393
  invalidCount++;
1213
1394
  continue;
1214
1395
  }
1215
1396
 
1216
- const verification = verifyBezierBezierIntersection(seg1, seg2, isect, tolerance);
1397
+ const verification = verifyBezierBezierIntersection(
1398
+ seg1,
1399
+ seg2,
1400
+ isect,
1401
+ tolerance,
1402
+ );
1217
1403
  results.push(verification);
1218
1404
 
1219
1405
  if (!verification.valid) {
@@ -1225,7 +1411,7 @@ export function verifyPathPathIntersection(path1, path2, intersections, toleranc
1225
1411
  valid: invalidCount === 0,
1226
1412
  results,
1227
1413
  invalidCount,
1228
- totalIntersections: intersections.length
1414
+ totalIntersections: intersections.length,
1229
1415
  };
1230
1416
  }
1231
1417
 
@@ -1236,71 +1422,131 @@ export function verifyPathPathIntersection(path1, path2, intersections, toleranc
1236
1422
  * @param {string} [tolerance='1e-30'] - Verification tolerance
1237
1423
  * @returns {{allPassed: boolean, results: Object}}
1238
1424
  */
1239
- export function verifyAllIntersectionFunctions(tolerance = '1e-30') {
1425
+ export function verifyAllIntersectionFunctions(tolerance = "1e-30") {
1240
1426
  const results = {};
1241
1427
  let allPassed = true;
1242
1428
 
1243
1429
  // Test 1: Line-line intersection
1244
- const line1 = [[0, 0], [2, 2]];
1245
- const line2 = [[0, 2], [2, 0]];
1430
+ const line1 = [
1431
+ [0, 0],
1432
+ [2, 2],
1433
+ ];
1434
+ const line2 = [
1435
+ [0, 2],
1436
+ [2, 0],
1437
+ ];
1246
1438
  const lineIsects = lineLineIntersection(line1, line2);
1247
1439
 
1248
1440
  if (lineIsects.length > 0) {
1249
- const lineVerify = verifyLineLineIntersection(line1, line2, lineIsects[0], tolerance);
1441
+ const lineVerify = verifyLineLineIntersection(
1442
+ line1,
1443
+ line2,
1444
+ lineIsects[0],
1445
+ tolerance,
1446
+ );
1250
1447
  results.lineLine = lineVerify;
1251
1448
  if (!lineVerify.valid) allPassed = false;
1252
1449
  } else {
1253
1450
  // WHY: These specific test lines (diagonal from [0,0] to [2,2] and [0,2] to [2,0])
1254
1451
  // geometrically MUST intersect at [1,1]. No intersection indicates a bug.
1255
- results.lineLine = { valid: false, reason: 'No intersection found for lines that geometrically must intersect at [1,1]' };
1452
+ results.lineLine = {
1453
+ valid: false,
1454
+ reason:
1455
+ "No intersection found for lines that geometrically must intersect at [1,1]",
1456
+ };
1256
1457
  allPassed = false;
1257
1458
  }
1258
1459
 
1259
1460
  // Test 2: Bezier-line intersection
1260
- const cubic = [[0, 0], [0.5, 2], [1.5, 2], [2, 0]];
1261
- const horizLine = [[0, 1], [2, 1]];
1461
+ const cubic = [
1462
+ [0, 0],
1463
+ [0.5, 2],
1464
+ [1.5, 2],
1465
+ [2, 0],
1466
+ ];
1467
+ const horizLine = [
1468
+ [0, 1],
1469
+ [2, 1],
1470
+ ];
1262
1471
  const bezLineIsects = bezierLineIntersection(cubic, horizLine);
1263
1472
 
1264
1473
  if (bezLineIsects.length > 0) {
1265
1474
  let allValid = true;
1266
1475
  const verifications = [];
1267
1476
  for (const isect of bezLineIsects) {
1268
- const v = verifyBezierLineIntersection(cubic, horizLine, isect, tolerance);
1477
+ const v = verifyBezierLineIntersection(
1478
+ cubic,
1479
+ horizLine,
1480
+ isect,
1481
+ tolerance,
1482
+ );
1269
1483
  verifications.push(v);
1270
1484
  if (!v.valid) allValid = false;
1271
1485
  }
1272
- results.bezierLine = { valid: allValid, intersectionCount: bezLineIsects.length, verifications };
1486
+ results.bezierLine = {
1487
+ valid: allValid,
1488
+ intersectionCount: bezLineIsects.length,
1489
+ verifications,
1490
+ };
1273
1491
  if (!allValid) allPassed = false;
1274
1492
  } else {
1275
- results.bezierLine = { valid: false, reason: 'No intersection found' };
1493
+ results.bezierLine = { valid: false, reason: "No intersection found" };
1276
1494
  allPassed = false;
1277
1495
  }
1278
1496
 
1279
1497
  // Test 3: Bezier-bezier intersection
1280
1498
  // WHY: These specific curves may or may not intersect depending on their geometry.
1281
1499
  // An empty result is valid if the curves don't actually cross. This is not a failure condition.
1282
- const cubic1 = [[0, 0], [1, 2], [2, 2], [3, 0]];
1283
- const cubic2 = [[0, 1], [1, -1], [2, 3], [3, 1]];
1500
+ const cubic1 = [
1501
+ [0, 0],
1502
+ [1, 2],
1503
+ [2, 2],
1504
+ [3, 0],
1505
+ ];
1506
+ const cubic2 = [
1507
+ [0, 1],
1508
+ [1, -1],
1509
+ [2, 3],
1510
+ [3, 1],
1511
+ ];
1284
1512
  const bezBezIsects = bezierBezierIntersection(cubic1, cubic2);
1285
1513
 
1286
1514
  if (bezBezIsects.length > 0) {
1287
1515
  let allValid = true;
1288
1516
  const verifications = [];
1289
1517
  for (const isect of bezBezIsects) {
1290
- const v = verifyBezierBezierIntersection(cubic1, cubic2, isect, tolerance);
1518
+ const v = verifyBezierBezierIntersection(
1519
+ cubic1,
1520
+ cubic2,
1521
+ isect,
1522
+ tolerance,
1523
+ );
1291
1524
  verifications.push(v);
1292
1525
  if (!v.valid) allValid = false;
1293
1526
  }
1294
- results.bezierBezier = { valid: allValid, intersectionCount: bezBezIsects.length, verifications };
1527
+ results.bezierBezier = {
1528
+ valid: allValid,
1529
+ intersectionCount: bezBezIsects.length,
1530
+ verifications,
1531
+ };
1295
1532
  if (!allValid) allPassed = false;
1296
1533
  } else {
1297
1534
  // WHY: No intersection is not an error - it's a valid result when curves don't cross.
1298
1535
  // We mark it as valid since the function is working correctly.
1299
- results.bezierBezier = { valid: true, intersectionCount: 0, note: 'No intersections (may be geometrically correct)' };
1536
+ results.bezierBezier = {
1537
+ valid: true,
1538
+ intersectionCount: 0,
1539
+ note: "No intersections (may be geometrically correct)",
1540
+ };
1300
1541
  }
1301
1542
 
1302
1543
  // Test 4: Self-intersection (use a loop curve)
1303
- const loopCurve = [[0, 0], [2, 2], [0, 2], [2, 0]]; // Figure-8 shape
1544
+ const loopCurve = [
1545
+ [0, 0],
1546
+ [2, 2],
1547
+ [0, 2],
1548
+ [2, 0],
1549
+ ]; // Figure-8 shape
1304
1550
  const selfIsects = bezierSelfIntersection(loopCurve);
1305
1551
 
1306
1552
  if (selfIsects.length > 0) {
@@ -1311,11 +1557,19 @@ export function verifyAllIntersectionFunctions(tolerance = '1e-30') {
1311
1557
  verifications.push(v);
1312
1558
  if (!v.valid) allValid = false;
1313
1559
  }
1314
- results.selfIntersection = { valid: allValid, intersectionCount: selfIsects.length, verifications };
1560
+ results.selfIntersection = {
1561
+ valid: allValid,
1562
+ intersectionCount: selfIsects.length,
1563
+ verifications,
1564
+ };
1315
1565
  if (!allValid) allPassed = false;
1316
1566
  } else {
1317
1567
  // Self-intersection expected for this curve
1318
- results.selfIntersection = { valid: true, intersectionCount: 0, note: 'No self-intersections found' };
1568
+ results.selfIntersection = {
1569
+ valid: true,
1570
+ intersectionCount: 0,
1571
+ note: "No self-intersections found",
1572
+ };
1319
1573
  }
1320
1574
 
1321
1575
  // Test 5: Path-path intersection
@@ -1324,16 +1578,25 @@ export function verifyAllIntersectionFunctions(tolerance = '1e-30') {
1324
1578
  const pathIsects = pathPathIntersection(path1, path2);
1325
1579
 
1326
1580
  if (pathIsects.length > 0) {
1327
- const pathVerify = verifyPathPathIntersection(path1, path2, pathIsects, tolerance);
1581
+ const pathVerify = verifyPathPathIntersection(
1582
+ path1,
1583
+ path2,
1584
+ pathIsects,
1585
+ tolerance,
1586
+ );
1328
1587
  results.pathPath = pathVerify;
1329
1588
  if (!pathVerify.valid) allPassed = false;
1330
1589
  } else {
1331
- results.pathPath = { valid: true, intersectionCount: 0, note: 'No path intersections' };
1590
+ results.pathPath = {
1591
+ valid: true,
1592
+ intersectionCount: 0,
1593
+ note: "No path intersections",
1594
+ };
1332
1595
  }
1333
1596
 
1334
1597
  return {
1335
1598
  allPassed,
1336
- results
1599
+ results,
1337
1600
  };
1338
1601
  }
1339
1602
 
@@ -1365,5 +1628,5 @@ export default {
1365
1628
  verifyBezierBezierIntersection,
1366
1629
  verifySelfIntersection,
1367
1630
  verifyPathPathIntersection,
1368
- verifyAllIntersectionFunctions
1631
+ verifyAllIntersectionFunctions,
1369
1632
  };