@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
@@ -127,6 +127,13 @@ const EPSILON = new Decimal("1e-40");
127
127
  * const p3 = point(new Decimal('1.5'), new Decimal('2.5'));
128
128
  */
129
129
  export function point(x, y) {
130
+ // Validate parameters exist
131
+ if (x === null || x === undefined) {
132
+ throw new Error("point: x coordinate is required");
133
+ }
134
+ if (y === null || y === undefined) {
135
+ throw new Error("point: y coordinate is required");
136
+ }
130
137
  return { x: D(x), y: D(y) };
131
138
  }
132
139
 
@@ -153,6 +160,16 @@ export function point(x, y) {
153
160
  * pointsEqual(p1, p2); // false
154
161
  */
155
162
  export function pointsEqual(p1, p2, tolerance = EPSILON) {
163
+ // Validate parameters - check for null/undefined explicitly since Decimal objects are always truthy
164
+ if (!p1 || typeof p1 !== "object" || p1.x === null || p1.x === undefined || p1.y === null || p1.y === undefined) {
165
+ throw new Error("pointsEqual: p1 must be a point with x and y properties");
166
+ }
167
+ if (!p2 || typeof p2 !== "object" || p2.x === null || p2.x === undefined || p2.y === null || p2.y === undefined) {
168
+ throw new Error("pointsEqual: p2 must be a point with x and y properties");
169
+ }
170
+ if (!tolerance || typeof tolerance.abs !== "function") {
171
+ throw new Error("pointsEqual: tolerance must be a Decimal instance");
172
+ }
156
173
  return (
157
174
  p1.x.minus(p2.x).abs().lt(tolerance) && p1.y.minus(p2.y).abs().lt(tolerance)
158
175
  );
@@ -198,6 +215,16 @@ export function pointsEqual(p1, p2, tolerance = EPSILON) {
198
215
  * cross(o, a, b); // ≈ 0
199
216
  */
200
217
  export function cross(o, a, b) {
218
+ // Validate parameters - check for null/undefined explicitly since Decimal objects are always truthy
219
+ if (!o || o.x === null || o.x === undefined || o.y === null || o.y === undefined) {
220
+ throw new Error("cross: origin point o must have x and y properties");
221
+ }
222
+ if (!a || a.x === null || a.x === undefined || a.y === null || a.y === undefined) {
223
+ throw new Error("cross: point a must have x and y properties");
224
+ }
225
+ if (!b || b.x === null || b.x === undefined || b.y === null || b.y === undefined) {
226
+ throw new Error("cross: point b must have x and y properties");
227
+ }
201
228
  const ax = a.x.minus(o.x);
202
229
  const ay = a.y.minus(o.y);
203
230
  const bx = b.x.minus(o.x);
@@ -246,6 +273,16 @@ export function cross(o, a, b) {
246
273
  * dot(o, a, b); // < 0
247
274
  */
248
275
  export function dot(o, a, b) {
276
+ // Validate parameters - check for null/undefined explicitly since Decimal objects are always truthy
277
+ if (!o || o.x === null || o.x === undefined || o.y === null || o.y === undefined) {
278
+ throw new Error("dot: origin point o must have x and y properties");
279
+ }
280
+ if (!a || a.x === null || a.x === undefined || a.y === null || a.y === undefined) {
281
+ throw new Error("dot: point a must have x and y properties");
282
+ }
283
+ if (!b || b.x === null || b.x === undefined || b.y === null || b.y === undefined) {
284
+ throw new Error("dot: point b must have x and y properties");
285
+ }
249
286
  const ax = a.x.minus(o.x);
250
287
  const ay = a.y.minus(o.y);
251
288
  const bx = b.x.minus(o.x);
@@ -271,6 +308,10 @@ export function dot(o, a, b) {
271
308
  * sign(new Decimal('5')); // 1
272
309
  */
273
310
  export function sign(val) {
311
+ // Validate parameter is a Decimal - check null/undefined first
312
+ if (val === null || val === undefined || typeof val.abs !== "function") {
313
+ throw new Error("sign: val must be a Decimal instance");
314
+ }
274
315
  if (val.abs().lt(EPSILON)) return 0;
275
316
  return val.lt(0) ? -1 : 1;
276
317
  }
@@ -325,6 +366,20 @@ export function sign(val) {
325
366
  * segmentIntersection(a, b, c, d); // null
326
367
  */
327
368
  export function segmentIntersection(a, b, c, d) {
369
+ // Validate all segment endpoints - check for null/undefined explicitly
370
+ if (!a || a.x === null || a.x === undefined || a.y === null || a.y === undefined) {
371
+ throw new Error("segmentIntersection: point a must have x and y properties");
372
+ }
373
+ if (!b || b.x === null || b.x === undefined || b.y === null || b.y === undefined) {
374
+ throw new Error("segmentIntersection: point b must have x and y properties");
375
+ }
376
+ if (!c || c.x === null || c.x === undefined || c.y === null || c.y === undefined) {
377
+ throw new Error("segmentIntersection: point c must have x and y properties");
378
+ }
379
+ if (!d || d.x === null || d.x === undefined || d.y === null || d.y === undefined) {
380
+ throw new Error("segmentIntersection: point d must have x and y properties");
381
+ }
382
+
328
383
  // Direction vectors
329
384
  const dx1 = b.x.minus(a.x);
330
385
  const dy1 = b.y.minus(a.y);
@@ -402,6 +457,20 @@ export function segmentIntersection(a, b, c, d) {
402
457
  * lineSegmentIntersection(lineA, lineB, segA, segB); // null
403
458
  */
404
459
  export function lineSegmentIntersection(lineA, lineB, segA, segB) {
460
+ // Validate all points - check for null/undefined explicitly
461
+ if (!lineA || lineA.x === null || lineA.x === undefined || lineA.y === null || lineA.y === undefined) {
462
+ throw new Error("lineSegmentIntersection: lineA must have x and y properties");
463
+ }
464
+ if (!lineB || lineB.x === null || lineB.x === undefined || lineB.y === null || lineB.y === undefined) {
465
+ throw new Error("lineSegmentIntersection: lineB must have x and y properties");
466
+ }
467
+ if (!segA || segA.x === null || segA.x === undefined || segA.y === null || segA.y === undefined) {
468
+ throw new Error("lineSegmentIntersection: segA must have x and y properties");
469
+ }
470
+ if (!segB || segB.x === null || segB.x === undefined || segB.y === null || segB.y === undefined) {
471
+ throw new Error("lineSegmentIntersection: segB must have x and y properties");
472
+ }
473
+
405
474
  const dx1 = lineB.x.minus(lineA.x);
406
475
  const dy1 = lineB.y.minus(lineA.y);
407
476
  const dx2 = segB.x.minus(segA.x);
@@ -476,9 +545,25 @@ export function lineSegmentIntersection(lineA, lineB, segA, segB) {
476
545
  * pointInPolygon(point(3, 2), concave); // 1 (inside concave region)
477
546
  */
478
547
  export function pointInPolygon(pt, polygon) {
548
+ // Validate point parameter - check for null/undefined explicitly
549
+ if (!pt || pt.x === null || pt.x === undefined || pt.y === null || pt.y === undefined) {
550
+ throw new Error("pointInPolygon: pt must have x and y properties");
551
+ }
552
+ // Validate polygon is an array
553
+ if (!Array.isArray(polygon)) {
554
+ throw new Error("pointInPolygon: polygon must be an array");
555
+ }
556
+
479
557
  const n = polygon.length;
480
558
  if (n < 3) return -1;
481
559
 
560
+ // Validate polygon elements have x and y properties
561
+ for (let i = 0; i < n; i++) {
562
+ if (!polygon[i] || polygon[i].x === null || polygon[i].x === undefined || polygon[i].y === null || polygon[i].y === undefined) {
563
+ throw new Error(`pointInPolygon: polygon[${i}] must have x and y properties`);
564
+ }
565
+ }
566
+
482
567
  let winding = 0;
483
568
 
484
569
  for (let i = 0; i < n; i++) {
@@ -546,6 +631,17 @@ export function pointInPolygon(pt, polygon) {
546
631
  * pointOnSegment(pt3, a, b); // false (not collinear)
547
632
  */
548
633
  export function pointOnSegment(pt, a, b) {
634
+ // Validate all points - check for null/undefined explicitly
635
+ if (!pt || pt.x === null || pt.x === undefined || pt.y === null || pt.y === undefined) {
636
+ throw new Error("pointOnSegment: pt must have x and y properties");
637
+ }
638
+ if (!a || a.x === null || a.x === undefined || a.y === null || a.y === undefined) {
639
+ throw new Error("pointOnSegment: segment point a must have x and y properties");
640
+ }
641
+ if (!b || b.x === null || b.x === undefined || b.y === null || b.y === undefined) {
642
+ throw new Error("pointOnSegment: segment point b must have x and y properties");
643
+ }
644
+
549
645
  // Check collinearity
550
646
  const crossVal = cross(a, b, pt);
551
647
  if (crossVal.abs().gt(EPSILON)) {
@@ -614,10 +710,32 @@ export function pointOnSegment(pt, a, b) {
614
710
  * clipPolygonSH(subject, clip); // []
615
711
  */
616
712
  export function clipPolygonSH(subject, clip) {
713
+ // Validate inputs are arrays
714
+ if (!Array.isArray(subject)) {
715
+ throw new Error("clipPolygonSH: subject must be an array");
716
+ }
717
+ if (!Array.isArray(clip)) {
718
+ throw new Error("clipPolygonSH: clip must be an array");
719
+ }
720
+
617
721
  if (subject.length < 3 || clip.length < 3) {
618
722
  return [];
619
723
  }
620
724
 
725
+ // Validate all subject points have x and y properties
726
+ for (let i = 0; i < subject.length; i++) {
727
+ if (!subject[i] || subject[i].x === null || subject[i].x === undefined || subject[i].y === null || subject[i].y === undefined) {
728
+ throw new Error(`clipPolygonSH: subject[${i}] must have x and y properties`);
729
+ }
730
+ }
731
+
732
+ // Validate all clip points have x and y properties
733
+ for (let i = 0; i < clip.length; i++) {
734
+ if (!clip[i] || clip[i].x === null || clip[i].x === undefined || clip[i].y === null || clip[i].y === undefined) {
735
+ throw new Error(`clipPolygonSH: clip[${i}] must have x and y properties`);
736
+ }
737
+ }
738
+
621
739
  // Convert all points to Decimal
622
740
  let output = subject.map((p) => point(p.x, p.y));
623
741
  const clipPoly = clip.map((p) => point(p.x, p.y));
@@ -686,6 +804,17 @@ export function clipPolygonSH(subject, clip) {
686
804
  * @returns {boolean} True if point is on left side or on the edge
687
805
  */
688
806
  function isInsideEdge(pt, edgeStart, edgeEnd) {
807
+ // Validate inputs (defensive check for internal function) - check for null/undefined explicitly
808
+ if (!pt || pt.x === null || pt.x === undefined || pt.y === null || pt.y === undefined) {
809
+ throw new Error("isInsideEdge: pt must have x and y properties");
810
+ }
811
+ if (!edgeStart || edgeStart.x === null || edgeStart.x === undefined || edgeStart.y === null || edgeStart.y === undefined) {
812
+ throw new Error("isInsideEdge: edgeStart must have x and y properties");
813
+ }
814
+ if (!edgeEnd || edgeEnd.x === null || edgeEnd.x === undefined || edgeEnd.y === null || edgeEnd.y === undefined) {
815
+ throw new Error("isInsideEdge: edgeEnd must have x and y properties");
816
+ }
817
+
689
818
  // Point is "inside" if it's on the left side of the edge (CCW polygon)
690
819
  return cross(edgeStart, edgeEnd, pt).gte(0);
691
820
  }
@@ -704,6 +833,20 @@ function isInsideEdge(pt, edgeStart, edgeEnd) {
704
833
  * @returns {Object|null} Intersection point {x, y} or null if parallel
705
834
  */
706
835
  function lineIntersection(a, b, c, d) {
836
+ // Validate inputs (defensive check for internal function) - check for null/undefined explicitly
837
+ if (!a || a.x === null || a.x === undefined || a.y === null || a.y === undefined) {
838
+ throw new Error("lineIntersection: point a must have x and y properties");
839
+ }
840
+ if (!b || b.x === null || b.x === undefined || b.y === null || b.y === undefined) {
841
+ throw new Error("lineIntersection: point b must have x and y properties");
842
+ }
843
+ if (!c || c.x === null || c.x === undefined || c.y === null || c.y === undefined) {
844
+ throw new Error("lineIntersection: point c must have x and y properties");
845
+ }
846
+ if (!d || d.x === null || d.x === undefined || d.y === null || d.y === undefined) {
847
+ throw new Error("lineIntersection: point d must have x and y properties");
848
+ }
849
+
707
850
  const dx1 = b.x.minus(a.x);
708
851
  const dy1 = b.y.minus(a.y);
709
852
  const dx2 = d.x.minus(c.x);
@@ -766,9 +909,21 @@ function lineIntersection(a, b, c, d) {
766
909
  * polygonArea(triangle); // 6
767
910
  */
768
911
  export function polygonArea(polygon) {
912
+ // Validate polygon is an array
913
+ if (!Array.isArray(polygon)) {
914
+ throw new Error("polygonArea: polygon must be an array");
915
+ }
916
+
769
917
  const n = polygon.length;
770
918
  if (n < 3) return D(0);
771
919
 
920
+ // Validate all polygon points have x and y properties
921
+ for (let i = 0; i < n; i++) {
922
+ if (!polygon[i] || polygon[i].x === null || polygon[i].x === undefined || polygon[i].y === null || polygon[i].y === undefined) {
923
+ throw new Error(`polygonArea: polygon[${i}] must have x and y properties`);
924
+ }
925
+ }
926
+
772
927
  let area = D(0);
773
928
  for (let i = 0; i < n; i++) {
774
929
  const p1 = polygon[i];
@@ -798,6 +953,10 @@ export function polygonArea(polygon) {
798
953
  * isCounterClockwise(cw); // false
799
954
  */
800
955
  export function isCounterClockwise(polygon) {
956
+ // Validate polygon is an array
957
+ if (!Array.isArray(polygon)) {
958
+ throw new Error("isCounterClockwise: polygon must be an array");
959
+ }
801
960
  return polygonArea(polygon).gt(0);
802
961
  }
803
962
 
@@ -816,6 +975,10 @@ export function isCounterClockwise(polygon) {
816
975
  * // reversed = [point(1,1), point(1,0), point(0,0)]
817
976
  */
818
977
  export function reversePolygon(polygon) {
978
+ // Validate polygon is an array
979
+ if (!Array.isArray(polygon)) {
980
+ throw new Error("reversePolygon: polygon must be an array");
981
+ }
819
982
  return [...polygon].reverse();
820
983
  }
821
984
 
@@ -834,6 +997,10 @@ export function reversePolygon(polygon) {
834
997
  * isCounterClockwise(ccw); // true
835
998
  */
836
999
  export function ensureCCW(polygon) {
1000
+ // Validate polygon is an array
1001
+ if (!Array.isArray(polygon)) {
1002
+ throw new Error("ensureCCW: polygon must be an array");
1003
+ }
837
1004
  if (!isCounterClockwise(polygon)) {
838
1005
  return reversePolygon(polygon);
839
1006
  }
@@ -890,10 +1057,28 @@ export function ensureCCW(polygon) {
890
1057
  * // Returns vertices of convex boundary in CCW order
891
1058
  */
892
1059
  export function convexHull(points) {
1060
+ // Validate points is an array
1061
+ if (!Array.isArray(points)) {
1062
+ throw new Error("convexHull: points must be an array");
1063
+ }
1064
+
893
1065
  if (points.length < 3) {
1066
+ // Validate and convert points
1067
+ for (let i = 0; i < points.length; i++) {
1068
+ if (!points[i] || points[i].x === null || points[i].x === undefined || points[i].y === null || points[i].y === undefined) {
1069
+ throw new Error(`convexHull: points[${i}] must have x and y properties`);
1070
+ }
1071
+ }
894
1072
  return points.map((p) => point(p.x, p.y));
895
1073
  }
896
1074
 
1075
+ // Validate all points have x and y properties before converting
1076
+ for (let i = 0; i < points.length; i++) {
1077
+ if (!points[i] || points[i].x === null || points[i].x === undefined || points[i].y === null || points[i].y === undefined) {
1078
+ throw new Error(`convexHull: points[${i}] must have x and y properties`);
1079
+ }
1080
+ }
1081
+
897
1082
  // Convert to Decimal points
898
1083
  const pts = points.map((p) => point(p.x, p.y));
899
1084
 
@@ -965,16 +1150,31 @@ export function convexHull(points) {
965
1150
  * boundingBox(empty); // null
966
1151
  */
967
1152
  export function boundingBox(polygon) {
1153
+ // Validate polygon is an array
1154
+ if (!Array.isArray(polygon)) {
1155
+ throw new Error("boundingBox: polygon must be an array");
1156
+ }
1157
+
968
1158
  if (polygon.length === 0) {
969
1159
  return null;
970
1160
  }
971
1161
 
1162
+ // Validate first point has x and y properties
1163
+ if (!polygon[0] || polygon[0].x === null || polygon[0].x === undefined || polygon[0].y === null || polygon[0].y === undefined) {
1164
+ throw new Error("boundingBox: polygon[0] must have x and y properties");
1165
+ }
1166
+
972
1167
  let minX = D(polygon[0].x);
973
1168
  let minY = D(polygon[0].y);
974
1169
  let maxX = D(polygon[0].x);
975
1170
  let maxY = D(polygon[0].y);
976
1171
 
977
- for (const p of polygon) {
1172
+ for (let i = 0; i < polygon.length; i++) {
1173
+ const p = polygon[i];
1174
+ // Validate each point has x and y properties
1175
+ if (!p || p.x === null || p.x === undefined || p.y === null || p.y === undefined) {
1176
+ throw new Error(`boundingBox: polygon[${i}] must have x and y properties`);
1177
+ }
978
1178
  const x = D(p.x);
979
1179
  const y = D(p.y);
980
1180
  if (x.lt(minX)) minX = x;
@@ -1015,7 +1215,19 @@ export function boundingBox(polygon) {
1015
1215
  * bboxIntersects(bb1, bb3); // false (separate)
1016
1216
  */
1017
1217
  export function bboxIntersects(bb1, bb2) {
1218
+ // Check if bounding boxes are null
1018
1219
  if (!bb1 || !bb2) return false;
1220
+
1221
+ // Validate bounding boxes have required properties - check for null/undefined explicitly
1222
+ if (bb1.minX === null || bb1.minX === undefined || bb1.minY === null || bb1.minY === undefined ||
1223
+ bb1.maxX === null || bb1.maxX === undefined || bb1.maxY === null || bb1.maxY === undefined) {
1224
+ throw new Error("bboxIntersects: bb1 must have minX, minY, maxX, maxY properties");
1225
+ }
1226
+ if (bb2.minX === null || bb2.minX === undefined || bb2.minY === null || bb2.minY === undefined ||
1227
+ bb2.maxX === null || bb2.maxX === undefined || bb2.maxY === null || bb2.maxY === undefined) {
1228
+ throw new Error("bboxIntersects: bb2 must have minX, minY, maxX, maxY properties");
1229
+ }
1230
+
1019
1231
  return !(
1020
1232
  bb1.maxX.lt(bb2.minX) ||
1021
1233
  bb2.maxX.lt(bb1.minX) ||
@@ -1067,6 +1279,28 @@ export function bboxIntersects(bb1, bb2) {
1067
1279
  * polygonIntersection(square1, square2); // []
1068
1280
  */
1069
1281
  export function polygonIntersection(subject, clip) {
1282
+ // Validate inputs are arrays
1283
+ if (!Array.isArray(subject)) {
1284
+ throw new Error("polygonIntersection: subject must be an array");
1285
+ }
1286
+ if (!Array.isArray(clip)) {
1287
+ throw new Error("polygonIntersection: clip must be an array");
1288
+ }
1289
+
1290
+ // Validate all subject points have x and y properties
1291
+ for (let i = 0; i < subject.length; i++) {
1292
+ if (!subject[i] || subject[i].x === null || subject[i].x === undefined || subject[i].y === null || subject[i].y === undefined) {
1293
+ throw new Error(`polygonIntersection: subject[${i}] must have x and y properties`);
1294
+ }
1295
+ }
1296
+
1297
+ // Validate all clip points have x and y properties
1298
+ for (let i = 0; i < clip.length; i++) {
1299
+ if (!clip[i] || clip[i].x === null || clip[i].x === undefined || clip[i].y === null || clip[i].y === undefined) {
1300
+ throw new Error(`polygonIntersection: clip[${i}] must have x and y properties`);
1301
+ }
1302
+ }
1303
+
1070
1304
  // Convert to Decimal points
1071
1305
  const subjectPoly = subject.map((p) => point(p.x, p.y));
1072
1306
  const clipPoly = clip.map((p) => point(p.x, p.y));
@@ -1121,9 +1355,21 @@ export function polygonIntersection(subject, clip) {
1121
1355
  * isConvex(triangle); // true
1122
1356
  */
1123
1357
  export function isConvex(polygon) {
1358
+ // Validate polygon is an array
1359
+ if (!Array.isArray(polygon)) {
1360
+ throw new Error("isConvex: polygon must be an array");
1361
+ }
1362
+
1124
1363
  const n = polygon.length;
1125
1364
  if (n < 3) return false;
1126
1365
 
1366
+ // Validate all polygon points have x and y properties
1367
+ for (let i = 0; i < n; i++) {
1368
+ if (!polygon[i] || polygon[i].x === null || polygon[i].x === undefined || polygon[i].y === null || polygon[i].y === undefined) {
1369
+ throw new Error(`isConvex: polygon[${i}] must have x and y properties`);
1370
+ }
1371
+ }
1372
+
1127
1373
  let crossSign = 0;
1128
1374
 
1129
1375
  for (let i = 0; i < n; i++) {
@@ -1167,6 +1413,11 @@ export function isConvex(polygon) {
1167
1413
  * @returns {Array} Single result polygon or empty array
1168
1414
  */
1169
1415
  function generalPolygonIntersection(subject, clip) {
1416
+ // Validate inputs are arrays (defensive check for internal function)
1417
+ if (!Array.isArray(subject) || !Array.isArray(clip)) {
1418
+ throw new Error("generalPolygonIntersection: both arguments must be arrays");
1419
+ }
1420
+
1170
1421
  const intersectionPoints = [];
1171
1422
 
1172
1423
  // Find all edge intersection points
@@ -1222,6 +1473,11 @@ function generalPolygonIntersection(subject, clip) {
1222
1473
  * @returns {Array} Array with duplicates removed
1223
1474
  */
1224
1475
  function removeDuplicatePoints(points) {
1476
+ // Validate points is an array (defensive check for internal function)
1477
+ if (!Array.isArray(points)) {
1478
+ throw new Error("removeDuplicatePoints: points must be an array");
1479
+ }
1480
+
1225
1481
  const result = [];
1226
1482
 
1227
1483
  for (const p of points) {
@@ -1277,6 +1533,28 @@ function removeDuplicatePoints(points) {
1277
1533
  * polygonUnion(square1, square2); // [square1, square2]
1278
1534
  */
1279
1535
  export function polygonUnion(polygon1, polygon2) {
1536
+ // Validate inputs are arrays
1537
+ if (!Array.isArray(polygon1)) {
1538
+ throw new Error("polygonUnion: polygon1 must be an array");
1539
+ }
1540
+ if (!Array.isArray(polygon2)) {
1541
+ throw new Error("polygonUnion: polygon2 must be an array");
1542
+ }
1543
+
1544
+ // Validate all polygon1 points have x and y properties
1545
+ for (let i = 0; i < polygon1.length; i++) {
1546
+ if (!polygon1[i] || polygon1[i].x === null || polygon1[i].x === undefined || polygon1[i].y === null || polygon1[i].y === undefined) {
1547
+ throw new Error(`polygonUnion: polygon1[${i}] must have x and y properties`);
1548
+ }
1549
+ }
1550
+
1551
+ // Validate all polygon2 points have x and y properties
1552
+ for (let i = 0; i < polygon2.length; i++) {
1553
+ if (!polygon2[i] || polygon2[i].x === null || polygon2[i].x === undefined || polygon2[i].y === null || polygon2[i].y === undefined) {
1554
+ throw new Error(`polygonUnion: polygon2[${i}] must have x and y properties`);
1555
+ }
1556
+ }
1557
+
1280
1558
  const poly1 = polygon1.map((p) => point(p.x, p.y));
1281
1559
  const poly2 = polygon2.map((p) => point(p.x, p.y));
1282
1560
 
@@ -1305,6 +1583,11 @@ export function polygonUnion(polygon1, polygon2) {
1305
1583
  * @returns {Array} Array containing result polygon(s)
1306
1584
  */
1307
1585
  function traceBoundaryUnion(poly1, poly2) {
1586
+ // Validate inputs (defensive check for internal function)
1587
+ if (!Array.isArray(poly1) || !Array.isArray(poly2)) {
1588
+ throw new Error("traceBoundaryUnion: both arguments must be arrays");
1589
+ }
1590
+
1308
1591
  // Find all intersection points with edge indices
1309
1592
  const intersections = findAllIntersections(poly1, poly2);
1310
1593
 
@@ -1470,6 +1753,11 @@ function traceBoundaryUnion(poly1, poly2) {
1470
1753
  * @returns {Array} Array of intersection objects with edge indices and parameters
1471
1754
  */
1472
1755
  function findAllIntersections(poly1, poly2) {
1756
+ // Validate inputs are arrays (defensive check for internal function)
1757
+ if (!Array.isArray(poly1) || !Array.isArray(poly2)) {
1758
+ throw new Error("findAllIntersections: both arguments must be arrays");
1759
+ }
1760
+
1473
1761
  const intersections = [];
1474
1762
  let id = 0;
1475
1763
 
@@ -1507,6 +1795,14 @@ function findAllIntersections(poly1, poly2) {
1507
1795
  * @returns {Array} Augmented polygon with intersection points inserted
1508
1796
  */
1509
1797
  function augmentPolygon(polygon, insertions) {
1798
+ // Validate inputs (defensive check for internal function)
1799
+ if (!Array.isArray(polygon)) {
1800
+ throw new Error("augmentPolygon: polygon must be an array");
1801
+ }
1802
+ if (!Array.isArray(insertions)) {
1803
+ throw new Error("augmentPolygon: insertions must be an array");
1804
+ }
1805
+
1510
1806
  // Group insertions by edge
1511
1807
  const byEdge = new Map();
1512
1808
  for (const ins of insertions) {
@@ -1583,6 +1879,28 @@ function augmentPolygon(polygon, insertions) {
1583
1879
  * polygonDifference(small, large); // []
1584
1880
  */
1585
1881
  export function polygonDifference(polygon1, polygon2) {
1882
+ // Validate inputs are arrays
1883
+ if (!Array.isArray(polygon1)) {
1884
+ throw new Error("polygonDifference: polygon1 must be an array");
1885
+ }
1886
+ if (!Array.isArray(polygon2)) {
1887
+ throw new Error("polygonDifference: polygon2 must be an array");
1888
+ }
1889
+
1890
+ // Validate all polygon1 points have x and y properties
1891
+ for (let i = 0; i < polygon1.length; i++) {
1892
+ if (!polygon1[i] || polygon1[i].x === null || polygon1[i].x === undefined || polygon1[i].y === null || polygon1[i].y === undefined) {
1893
+ throw new Error(`polygonDifference: polygon1[${i}] must have x and y properties`);
1894
+ }
1895
+ }
1896
+
1897
+ // Validate all polygon2 points have x and y properties
1898
+ for (let i = 0; i < polygon2.length; i++) {
1899
+ if (!polygon2[i] || polygon2[i].x === null || polygon2[i].x === undefined || polygon2[i].y === null || polygon2[i].y === undefined) {
1900
+ throw new Error(`polygonDifference: polygon2[${i}] must have x and y properties`);
1901
+ }
1902
+ }
1903
+
1586
1904
  const poly1 = polygon1.map((p) => point(p.x, p.y));
1587
1905
  const poly2 = polygon2.map((p) => point(p.x, p.y));
1588
1906
 
@@ -1610,6 +1928,11 @@ export function polygonDifference(polygon1, polygon2) {
1610
1928
  * @returns {Array} Array containing result polygon(s)
1611
1929
  */
1612
1930
  function traceBoundaryDifference(poly1, poly2) {
1931
+ // Validate inputs (defensive check for internal function)
1932
+ if (!Array.isArray(poly1) || !Array.isArray(poly2)) {
1933
+ throw new Error("traceBoundaryDifference: both arguments must be arrays");
1934
+ }
1935
+
1613
1936
  // Find all intersection points with edge indices
1614
1937
  const intersections = findAllIntersections(poly1, poly2);
1615
1938