@emasoft/svg-matrix 1.0.30 → 1.0.31

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 (45) hide show
  1. package/bin/svg-matrix.js +310 -61
  2. package/bin/svglinter.cjs +102 -3
  3. package/bin/svgm.js +236 -27
  4. package/package.json +1 -1
  5. package/src/animation-optimization.js +137 -17
  6. package/src/animation-references.js +123 -6
  7. package/src/arc-length.js +213 -4
  8. package/src/bezier-analysis.js +217 -21
  9. package/src/bezier-intersections.js +275 -12
  10. package/src/browser-verify.js +237 -4
  11. package/src/clip-path-resolver.js +168 -0
  12. package/src/convert-path-data.js +479 -28
  13. package/src/css-specificity.js +73 -10
  14. package/src/douglas-peucker.js +219 -2
  15. package/src/flatten-pipeline.js +284 -26
  16. package/src/geometry-to-path.js +250 -25
  17. package/src/gjk-collision.js +236 -33
  18. package/src/index.js +261 -3
  19. package/src/inkscape-support.js +86 -28
  20. package/src/logger.js +48 -3
  21. package/src/marker-resolver.js +278 -74
  22. package/src/mask-resolver.js +265 -66
  23. package/src/matrix.js +44 -5
  24. package/src/mesh-gradient.js +352 -102
  25. package/src/off-canvas-detection.js +382 -13
  26. package/src/path-analysis.js +192 -18
  27. package/src/path-data-plugins.js +309 -5
  28. package/src/path-optimization.js +129 -5
  29. package/src/path-simplification.js +188 -32
  30. package/src/pattern-resolver.js +454 -106
  31. package/src/polygon-clip.js +324 -1
  32. package/src/svg-boolean-ops.js +226 -9
  33. package/src/svg-collections.js +7 -5
  34. package/src/svg-flatten.js +386 -62
  35. package/src/svg-parser.js +179 -8
  36. package/src/svg-rendering-context.js +235 -6
  37. package/src/svg-toolbox.js +45 -8
  38. package/src/svg2-polyfills.js +40 -10
  39. package/src/transform-decomposition.js +258 -32
  40. package/src/transform-optimization.js +259 -13
  41. package/src/transforms2d.js +82 -9
  42. package/src/transforms3d.js +62 -10
  43. package/src/use-symbol-resolver.js +286 -42
  44. package/src/vector.js +64 -8
  45. package/src/verification.js +392 -1
@@ -71,6 +71,28 @@ export function lineLineIntersection(line1, line2) {
71
71
  throw new Error("lineLineIntersection: line2 must be an array of 2 points");
72
72
  }
73
73
 
74
+ // WHY: Validate point structure to prevent undefined access errors
75
+ if (
76
+ !Array.isArray(line1[0]) ||
77
+ line1[0].length < 2 ||
78
+ !Array.isArray(line1[1]) ||
79
+ line1[1].length < 2
80
+ ) {
81
+ throw new Error(
82
+ "lineLineIntersection: line1 points must be arrays with at least 2 elements [x, y]",
83
+ );
84
+ }
85
+ if (
86
+ !Array.isArray(line2[0]) ||
87
+ line2[0].length < 2 ||
88
+ !Array.isArray(line2[1]) ||
89
+ line2[1].length < 2
90
+ ) {
91
+ throw new Error(
92
+ "lineLineIntersection: line2 points must be arrays with at least 2 elements [x, y]",
93
+ );
94
+ }
95
+
74
96
  const [x1, y1] = [D(line1[0][0]), D(line1[0][1])];
75
97
  const [x2, y2] = [D(line1[1][0]), D(line1[1][1])];
76
98
  const [x3, y3] = [D(line2[0][0]), D(line2[0][1])];
@@ -147,6 +169,38 @@ export function bezierLineIntersection(bezier, line, options = {}) {
147
169
  );
148
170
  }
149
171
 
172
+ // WHY: Validate options parameters to prevent invalid behavior
173
+ if (
174
+ typeof samplesPerDegree !== "number" ||
175
+ samplesPerDegree <= 0 ||
176
+ !isFinite(samplesPerDegree)
177
+ ) {
178
+ throw new Error(
179
+ "bezierLineIntersection: samplesPerDegree must be a positive finite number",
180
+ );
181
+ }
182
+
183
+ // WHY: Validate line point structure to prevent undefined access
184
+ if (
185
+ !Array.isArray(line[0]) ||
186
+ line[0].length < 2 ||
187
+ !Array.isArray(line[1]) ||
188
+ line[1].length < 2
189
+ ) {
190
+ throw new Error(
191
+ "bezierLineIntersection: line points must be arrays with at least 2 elements [x, y]",
192
+ );
193
+ }
194
+
195
+ // WHY: Validate bezier control points structure
196
+ for (let i = 0; i < bezier.length; i++) {
197
+ if (!Array.isArray(bezier[i]) || bezier[i].length < 2) {
198
+ throw new Error(
199
+ `bezierLineIntersection: bezier control point ${i} must be an array with at least 2 elements [x, y]`,
200
+ );
201
+ }
202
+ }
203
+
150
204
  const [lx0, ly0] = [D(line[0][0]), D(line[0][1])];
151
205
  const [lx1, ly1] = [D(line[1][0]), D(line[1][1])];
152
206
 
@@ -232,6 +286,25 @@ export function bezierLineIntersection(bezier, line, options = {}) {
232
286
  * @returns {Decimal} Refined parameter value
233
287
  */
234
288
  function refineBezierLineRoot(bezier, line, t0, t1, tol) {
289
+ // WHY: Validate inputs to prevent undefined behavior in internal function
290
+ if (!bezier || !Array.isArray(bezier) || bezier.length < 2) {
291
+ throw new Error(
292
+ "refineBezierLineRoot: bezier must have at least 2 control points",
293
+ );
294
+ }
295
+ if (!line || !Array.isArray(line) || line.length !== 2) {
296
+ throw new Error("refineBezierLineRoot: line must be an array of 2 points");
297
+ }
298
+ if (t0 === undefined || t0 === null) {
299
+ throw new Error("refineBezierLineRoot: t0 is required");
300
+ }
301
+ if (t1 === undefined || t1 === null) {
302
+ throw new Error("refineBezierLineRoot: t1 is required");
303
+ }
304
+ if (tol === undefined || tol === null) {
305
+ throw new Error("refineBezierLineRoot: tol is required");
306
+ }
307
+
235
308
  const [lx0, ly0] = [D(line[0][0]), D(line[0][1])];
236
309
  const [lx1, ly1] = [D(line[1][0]), D(line[1][1])];
237
310
  const dlx = lx1.minus(lx0);
@@ -246,7 +319,6 @@ function refineBezierLineRoot(bezier, line, t0, t1, tol) {
246
319
  };
247
320
 
248
321
  let fLo = evalDist(lo);
249
- let _fHi = evalDist(hi);
250
322
 
251
323
  // WHY: Use named constant instead of magic number for clarity and maintainability
252
324
  for (let i = 0; i < MAX_BISECTION_ITERATIONS; i++) {
@@ -265,7 +337,6 @@ function refineBezierLineRoot(bezier, line, t0, t1, tol) {
265
337
  fLo = fMid;
266
338
  } else {
267
339
  hi = mid;
268
- _fHi = fMid;
269
340
  }
270
341
  }
271
342
 
@@ -308,6 +379,36 @@ export function bezierBezierIntersection(bezier1, bezier2, options = {}) {
308
379
  );
309
380
  }
310
381
 
382
+ // WHY: Validate maxDepth to prevent infinite recursion or invalid behavior
383
+ if (
384
+ typeof maxDepth !== "number" ||
385
+ maxDepth <= 0 ||
386
+ !isFinite(maxDepth) ||
387
+ Math.floor(maxDepth) !== maxDepth
388
+ ) {
389
+ throw new Error(
390
+ "bezierBezierIntersection: maxDepth must be a positive integer",
391
+ );
392
+ }
393
+
394
+ // WHY: Validate control point structure for bezier1
395
+ for (let i = 0; i < bezier1.length; i++) {
396
+ if (!Array.isArray(bezier1[i]) || bezier1[i].length < 2) {
397
+ throw new Error(
398
+ `bezierBezierIntersection: bezier1 control point ${i} must be an array with at least 2 elements [x, y]`,
399
+ );
400
+ }
401
+ }
402
+
403
+ // WHY: Validate control point structure for bezier2
404
+ for (let i = 0; i < bezier2.length; i++) {
405
+ if (!Array.isArray(bezier2[i]) || bezier2[i].length < 2) {
406
+ throw new Error(
407
+ `bezierBezierIntersection: bezier2 control point ${i} must be an array with at least 2 elements [x, y]`,
408
+ );
409
+ }
410
+ }
411
+
311
412
  const tol = D(tolerance);
312
413
  const results = [];
313
414
 
@@ -381,6 +482,27 @@ export function bezierBezierIntersection(bezier1, bezier2, options = {}) {
381
482
  * @returns {Object|null} Refined intersection or null
382
483
  */
383
484
  function refineIntersection(bez1, bez2, t1, t2, tol) {
485
+ // WHY: Validate inputs to prevent undefined behavior in internal function
486
+ if (!bez1 || !Array.isArray(bez1) || bez1.length < 2) {
487
+ throw new Error(
488
+ "refineIntersection: bez1 must have at least 2 control points",
489
+ );
490
+ }
491
+ if (!bez2 || !Array.isArray(bez2) || bez2.length < 2) {
492
+ throw new Error(
493
+ "refineIntersection: bez2 must have at least 2 control points",
494
+ );
495
+ }
496
+ if (t1 === undefined || t1 === null) {
497
+ throw new Error("refineIntersection: t1 is required");
498
+ }
499
+ if (t2 === undefined || t2 === null) {
500
+ throw new Error("refineIntersection: t2 is required");
501
+ }
502
+ if (tol === undefined || tol === null) {
503
+ throw new Error("refineIntersection: tol is required");
504
+ }
505
+
384
506
  let currentT1 = D(t1);
385
507
  let currentT2 = D(t2);
386
508
 
@@ -469,6 +591,39 @@ function bboxOverlap(bbox1, bbox2) {
469
591
  return false; // No overlap if either bbox is missing
470
592
  }
471
593
 
594
+ // WHY: Validate bbox objects have required properties with proper types
595
+ // WHY: Cannot use !bbox1.xmin as it fails for Decimal(0). Must check for undefined/null explicitly.
596
+ if (
597
+ bbox1.xmin === undefined ||
598
+ bbox1.xmin === null ||
599
+ bbox1.xmax === undefined ||
600
+ bbox1.xmax === null ||
601
+ bbox1.ymin === undefined ||
602
+ bbox1.ymin === null ||
603
+ bbox1.ymax === undefined ||
604
+ bbox1.ymax === null ||
605
+ typeof bbox1.xmin.lt !== "function"
606
+ ) {
607
+ throw new Error(
608
+ "bboxOverlap: bbox1 must have xmin, xmax, ymin, ymax Decimal properties",
609
+ );
610
+ }
611
+ if (
612
+ bbox2.xmin === undefined ||
613
+ bbox2.xmin === null ||
614
+ bbox2.xmax === undefined ||
615
+ bbox2.xmax === null ||
616
+ bbox2.ymin === undefined ||
617
+ bbox2.ymin === null ||
618
+ bbox2.ymax === undefined ||
619
+ bbox2.ymax === null ||
620
+ typeof bbox2.xmin.lt !== "function"
621
+ ) {
622
+ throw new Error(
623
+ "bboxOverlap: bbox2 must have xmin, xmax, ymin, ymax Decimal properties",
624
+ );
625
+ }
626
+
472
627
  return !(
473
628
  bbox1.xmax.lt(bbox2.xmin) ||
474
629
  bbox1.xmin.gt(bbox2.xmax) ||
@@ -484,9 +639,43 @@ function bboxOverlap(bbox1, bbox2) {
484
639
  * @returns {Array} Array of unique intersections
485
640
  */
486
641
  function deduplicateIntersections(intersections, tol) {
642
+ // WHY: Validate inputs to prevent cryptic errors from invalid data
643
+ if (!intersections || !Array.isArray(intersections)) {
644
+ throw new Error("deduplicateIntersections: intersections must be an array");
645
+ }
646
+ if (tol === undefined || tol === null) {
647
+ throw new Error("deduplicateIntersections: tol is required");
648
+ }
649
+
487
650
  const result = [];
488
651
 
489
652
  for (const isect of intersections) {
653
+ // WHY: Validate each intersection has required properties
654
+ if (!isect || typeof isect !== "object") {
655
+ throw new Error(
656
+ "deduplicateIntersections: intersection must be an object",
657
+ );
658
+ }
659
+ // WHY: Cannot use !isect.t1 as it fails for Decimal(0). Must check for undefined/null explicitly.
660
+ if (
661
+ isect.t1 === undefined ||
662
+ isect.t1 === null ||
663
+ typeof isect.t1.minus !== "function"
664
+ ) {
665
+ throw new Error(
666
+ "deduplicateIntersections: intersection must have t1 Decimal property",
667
+ );
668
+ }
669
+ if (
670
+ isect.t2 === undefined ||
671
+ isect.t2 === null ||
672
+ typeof isect.t2.minus !== "function"
673
+ ) {
674
+ throw new Error(
675
+ "deduplicateIntersections: intersection must have t2 Decimal property",
676
+ );
677
+ }
678
+
490
679
  let isDuplicate = false;
491
680
 
492
681
  for (const existing of result) {
@@ -538,12 +727,33 @@ export function bezierSelfIntersection(bezier, options = {}) {
538
727
  const minSep = D(minSeparation);
539
728
 
540
729
  // Input validation
541
- if (!bezier || bezier.length < 2) {
730
+ if (!bezier || !Array.isArray(bezier) || bezier.length < 2) {
542
731
  throw new Error(
543
- "bezierSelfIntersection: bezier must have at least 2 control points",
732
+ "bezierSelfIntersection: bezier must be an array with at least 2 control points",
544
733
  );
545
734
  }
546
735
 
736
+ // WHY: Validate maxDepth to prevent infinite recursion or invalid behavior
737
+ if (
738
+ typeof maxDepth !== "number" ||
739
+ maxDepth <= 0 ||
740
+ !isFinite(maxDepth) ||
741
+ Math.floor(maxDepth) !== maxDepth
742
+ ) {
743
+ throw new Error(
744
+ "bezierSelfIntersection: maxDepth must be a positive integer",
745
+ );
746
+ }
747
+
748
+ // WHY: Validate control point structure
749
+ for (let i = 0; i < bezier.length; i++) {
750
+ if (!Array.isArray(bezier[i]) || bezier[i].length < 2) {
751
+ throw new Error(
752
+ `bezierSelfIntersection: control point ${i} must be an array with at least 2 elements [x, y]`,
753
+ );
754
+ }
755
+ }
756
+
547
757
  // Self-intersections only possible for cubic and higher
548
758
  if (bezier.length < 4) {
549
759
  return [];
@@ -648,6 +858,25 @@ export function bezierSelfIntersection(bezier, options = {}) {
648
858
  * @returns {Object|null} Refined intersection or null if failed
649
859
  */
650
860
  function refineSelfIntersection(bezier, t1Init, t2Init, tol, minSep) {
861
+ // WHY: Validate inputs to prevent undefined behavior in internal function
862
+ if (!bezier || !Array.isArray(bezier) || bezier.length < 2) {
863
+ throw new Error(
864
+ "refineSelfIntersection: bezier must have at least 2 control points",
865
+ );
866
+ }
867
+ if (t1Init === undefined || t1Init === null) {
868
+ throw new Error("refineSelfIntersection: t1Init is required");
869
+ }
870
+ if (t2Init === undefined || t2Init === null) {
871
+ throw new Error("refineSelfIntersection: t2Init is required");
872
+ }
873
+ if (tol === undefined || tol === null) {
874
+ throw new Error("refineSelfIntersection: tol is required");
875
+ }
876
+ if (minSep === undefined || minSep === null) {
877
+ throw new Error("refineSelfIntersection: minSep is required");
878
+ }
879
+
651
880
  let t1 = D(t1Init);
652
881
  let t2 = D(t2Init);
653
882
 
@@ -888,6 +1117,13 @@ export function verifyIntersection(
888
1117
  if (!intersection) {
889
1118
  throw new Error("verifyIntersection: intersection object is required");
890
1119
  }
1120
+ // WHY: Validate intersection has required t1 and t2 properties before using them
1121
+ if (intersection.t1 === undefined || intersection.t1 === null) {
1122
+ throw new Error("verifyIntersection: intersection.t1 is required");
1123
+ }
1124
+ if (intersection.t2 === undefined || intersection.t2 === null) {
1125
+ throw new Error("verifyIntersection: intersection.t2 is required");
1126
+ }
891
1127
 
892
1128
  const tol = D(tolerance);
893
1129
 
@@ -942,8 +1178,15 @@ export function verifyLineLineIntersection(
942
1178
 
943
1179
  const tol = D(tolerance);
944
1180
 
945
- if (!intersection.t1) {
946
- return { valid: false, reason: "No intersection provided" };
1181
+ // WHY: Validate all required intersection properties before using them
1182
+ if (intersection.t1 === undefined || intersection.t1 === null) {
1183
+ return { valid: false, reason: "intersection.t1 is missing" };
1184
+ }
1185
+ if (intersection.t2 === undefined || intersection.t2 === null) {
1186
+ return { valid: false, reason: "intersection.t2 is missing" };
1187
+ }
1188
+ if (!intersection.point || !Array.isArray(intersection.point) || intersection.point.length < 2) {
1189
+ return { valid: false, reason: "intersection.point is missing or invalid" };
947
1190
  }
948
1191
 
949
1192
  const [x1, y1] = [D(line1[0][0]), D(line1[0][1])];
@@ -1065,8 +1308,15 @@ export function verifyBezierLineIntersection(
1065
1308
 
1066
1309
  const tol = D(tolerance);
1067
1310
 
1068
- if (intersection.t1 === undefined) {
1069
- return { valid: false, reason: "No intersection provided" };
1311
+ // WHY: Validate all required intersection properties before using them
1312
+ if (intersection.t1 === undefined || intersection.t1 === null) {
1313
+ return { valid: false, reason: "intersection.t1 is missing" };
1314
+ }
1315
+ if (intersection.t2 === undefined || intersection.t2 === null) {
1316
+ return { valid: false, reason: "intersection.t2 is missing" };
1317
+ }
1318
+ if (!intersection.point || !Array.isArray(intersection.point) || intersection.point.length < 2) {
1319
+ return { valid: false, reason: "intersection.point is missing or invalid" };
1070
1320
  }
1071
1321
 
1072
1322
  const t1 = D(intersection.t1);
@@ -1167,8 +1417,12 @@ export function verifyBezierBezierIntersection(
1167
1417
 
1168
1418
  const tol = D(tolerance);
1169
1419
 
1170
- if (intersection.t1 === undefined) {
1171
- return { valid: false, reason: "No intersection provided" };
1420
+ // WHY: Validate all required intersection properties before using them
1421
+ if (intersection.t1 === undefined || intersection.t1 === null) {
1422
+ return { valid: false, reason: "intersection.t1 is missing" };
1423
+ }
1424
+ if (intersection.t2 === undefined || intersection.t2 === null) {
1425
+ return { valid: false, reason: "intersection.t2 is missing" };
1172
1426
  }
1173
1427
 
1174
1428
  const t1 = D(intersection.t1);
@@ -1267,8 +1521,12 @@ export function verifySelfIntersection(
1267
1521
  const tol = D(tolerance);
1268
1522
  const minSep = D(minSeparation);
1269
1523
 
1270
- if (intersection.t1 === undefined) {
1271
- return { valid: false, reason: "No intersection provided" };
1524
+ // WHY: Validate all required intersection properties before using them
1525
+ if (intersection.t1 === undefined || intersection.t1 === null) {
1526
+ return { valid: false, reason: "intersection.t1 is missing" };
1527
+ }
1528
+ if (intersection.t2 === undefined || intersection.t2 === null) {
1529
+ return { valid: false, reason: "intersection.t2 is missing" };
1272
1530
  }
1273
1531
 
1274
1532
  const t1 = D(intersection.t1);
@@ -1423,6 +1681,11 @@ export function verifyPathPathIntersection(
1423
1681
  * @returns {{allPassed: boolean, results: Object}}
1424
1682
  */
1425
1683
  export function verifyAllIntersectionFunctions(tolerance = "1e-30") {
1684
+ // WHY: Validate tolerance parameter to prevent invalid configuration
1685
+ if (tolerance === undefined || tolerance === null) {
1686
+ throw new Error("verifyAllIntersectionFunctions: tolerance cannot be undefined or null");
1687
+ }
1688
+
1426
1689
  const results = {};
1427
1690
  let allPassed = true;
1428
1691