@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
@@ -52,8 +52,11 @@ const MAX_ITERATIONS = 100;
52
52
  * @param {number|string|Decimal} x - X coordinate
53
53
  * @param {number|string|Decimal} y - Y coordinate
54
54
  * @returns {{x: Decimal, y: Decimal}} Point object
55
+ * @throws {TypeError} If x or y is null or undefined
55
56
  */
56
57
  export function point(x, y) {
58
+ if (x == null) throw new TypeError('point: x coordinate cannot be null or undefined');
59
+ if (y == null) throw new TypeError('point: y coordinate cannot be null or undefined');
57
60
  return { x: D(x), y: D(y) };
58
61
  }
59
62
 
@@ -62,8 +65,11 @@ export function point(x, y) {
62
65
  * @param {{x: Decimal, y: Decimal}} a - First vector
63
66
  * @param {{x: Decimal, y: Decimal}} b - Second vector
64
67
  * @returns {{x: Decimal, y: Decimal}} Sum vector
68
+ * @throws {TypeError} If a or b is invalid or missing x/y properties
65
69
  */
66
70
  export function vectorAdd(a, b) {
71
+ if (!a || a.x == null || a.y == null) throw new TypeError('vectorAdd: first vector must have x and y properties');
72
+ if (!b || b.x == null || b.y == null) throw new TypeError('vectorAdd: second vector must have x and y properties');
67
73
  return { x: a.x.plus(b.x), y: a.y.plus(b.y) };
68
74
  }
69
75
 
@@ -72,8 +78,11 @@ export function vectorAdd(a, b) {
72
78
  * @param {{x: Decimal, y: Decimal}} a - First vector
73
79
  * @param {{x: Decimal, y: Decimal}} b - Second vector
74
80
  * @returns {{x: Decimal, y: Decimal}} Difference vector (a - b)
81
+ * @throws {TypeError} If a or b is invalid or missing x/y properties
75
82
  */
76
83
  export function vectorSub(a, b) {
84
+ if (!a || a.x == null || a.y == null) throw new TypeError('vectorSub: first vector must have x and y properties');
85
+ if (!b || b.x == null || b.y == null) throw new TypeError('vectorSub: second vector must have x and y properties');
77
86
  return { x: a.x.minus(b.x), y: a.y.minus(b.y) };
78
87
  }
79
88
 
@@ -81,8 +90,10 @@ export function vectorSub(a, b) {
81
90
  * Negate a vector.
82
91
  * @param {{x: Decimal, y: Decimal}} v - Vector to negate
83
92
  * @returns {{x: Decimal, y: Decimal}} Negated vector
93
+ * @throws {TypeError} If v is invalid or missing x/y properties
84
94
  */
85
95
  export function vectorNeg(v) {
96
+ if (!v || v.x == null || v.y == null) throw new TypeError('vectorNeg: vector must have x and y properties');
86
97
  return { x: v.x.neg(), y: v.y.neg() };
87
98
  }
88
99
 
@@ -91,8 +102,11 @@ export function vectorNeg(v) {
91
102
  * @param {{x: Decimal, y: Decimal}} v - Vector to scale
92
103
  * @param {number|string|Decimal} s - Scale factor
93
104
  * @returns {{x: Decimal, y: Decimal}} Scaled vector
105
+ * @throws {TypeError} If v is invalid, missing x/y properties, or s is null/undefined
94
106
  */
95
107
  export function vectorScale(v, s) {
108
+ if (!v || v.x == null || v.y == null) throw new TypeError('vectorScale: vector must have x and y properties');
109
+ if (s == null) throw new TypeError('vectorScale: scale factor cannot be null or undefined');
96
110
  const sd = D(s);
97
111
  return { x: v.x.mul(sd), y: v.y.mul(sd) };
98
112
  }
@@ -102,8 +116,11 @@ export function vectorScale(v, s) {
102
116
  * @param {{x: Decimal, y: Decimal}} a - First vector
103
117
  * @param {{x: Decimal, y: Decimal}} b - Second vector
104
118
  * @returns {Decimal} Dot product
119
+ * @throws {TypeError} If a or b is invalid or missing x/y properties
105
120
  */
106
121
  export function dot(a, b) {
122
+ if (!a || a.x == null || a.y == null) throw new TypeError('dot: first vector must have x and y properties');
123
+ if (!b || b.x == null || b.y == null) throw new TypeError('dot: second vector must have x and y properties');
107
124
  return a.x.mul(b.x).plus(a.y.mul(b.y));
108
125
  }
109
126
 
@@ -112,8 +129,11 @@ export function dot(a, b) {
112
129
  * @param {{x: Decimal, y: Decimal}} a - First vector
113
130
  * @param {{x: Decimal, y: Decimal}} b - Second vector
114
131
  * @returns {Decimal} Cross product (a.x * b.y - a.y * b.x)
132
+ * @throws {TypeError} If a or b is invalid or missing x/y properties
115
133
  */
116
134
  export function cross(a, b) {
135
+ if (!a || a.x == null || a.y == null) throw new TypeError('cross: first vector must have x and y properties');
136
+ if (!b || b.x == null || b.y == null) throw new TypeError('cross: second vector must have x and y properties');
117
137
  return a.x.mul(b.y).minus(a.y.mul(b.x));
118
138
  }
119
139
 
@@ -121,8 +141,10 @@ export function cross(a, b) {
121
141
  * Squared magnitude of a vector.
122
142
  * @param {{x: Decimal, y: Decimal}} v - Vector
123
143
  * @returns {Decimal} Squared magnitude
144
+ * @throws {TypeError} If v is invalid or missing x/y properties
124
145
  */
125
146
  export function magnitudeSquared(v) {
147
+ if (!v || v.x == null || v.y == null) throw new TypeError('magnitudeSquared: vector must have x and y properties');
126
148
  return v.x.mul(v.x).plus(v.y.mul(v.y));
127
149
  }
128
150
 
@@ -130,8 +152,10 @@ export function magnitudeSquared(v) {
130
152
  * Magnitude of a vector.
131
153
  * @param {{x: Decimal, y: Decimal}} v - Vector
132
154
  * @returns {Decimal} Magnitude
155
+ * @throws {TypeError} If v is invalid or missing x/y properties
133
156
  */
134
157
  export function magnitude(v) {
158
+ if (!v || v.x == null || v.y == null) throw new TypeError('magnitude: vector must have x and y properties');
135
159
  return magnitudeSquared(v).sqrt();
136
160
  }
137
161
 
@@ -139,8 +163,10 @@ export function magnitude(v) {
139
163
  * Normalize a vector to unit length.
140
164
  * @param {{x: Decimal, y: Decimal}} v - Vector to normalize
141
165
  * @returns {{x: Decimal, y: Decimal}} Unit vector
166
+ * @throws {TypeError} If v is invalid or missing x/y properties
142
167
  */
143
168
  export function normalize(v) {
169
+ if (!v || v.x == null || v.y == null) throw new TypeError('normalize: vector must have x and y properties');
144
170
  const mag = magnitude(v);
145
171
  if (mag.lessThan(EPSILON)) {
146
172
  return { x: D(0), y: D(0) };
@@ -152,8 +178,10 @@ export function normalize(v) {
152
178
  * Get perpendicular vector (90° counter-clockwise rotation).
153
179
  * @param {{x: Decimal, y: Decimal}} v - Vector
154
180
  * @returns {{x: Decimal, y: Decimal}} Perpendicular vector
181
+ * @throws {TypeError} If v is invalid or missing x/y properties
155
182
  */
156
183
  export function perpendicular(v) {
184
+ if (!v || v.x == null || v.y == null) throw new TypeError('perpendicular: vector must have x and y properties');
157
185
  return { x: v.y.neg(), y: v.x };
158
186
  }
159
187
 
@@ -164,8 +192,12 @@ export function perpendicular(v) {
164
192
  * @param {{x: Decimal, y: Decimal}} b - Second vector
165
193
  * @param {{x: Decimal, y: Decimal}} c - Third vector
166
194
  * @returns {{x: Decimal, y: Decimal}} Triple product result
195
+ * @throws {TypeError} If any vector is invalid or missing x/y properties
167
196
  */
168
197
  export function tripleProduct(a, b, c) {
198
+ if (!a || a.x == null || a.y == null) throw new TypeError('tripleProduct: first vector must have x and y properties');
199
+ if (!b || b.x == null || b.y == null) throw new TypeError('tripleProduct: second vector must have x and y properties');
200
+ if (!c || c.x == null || c.y == null) throw new TypeError('tripleProduct: third vector must have x and y properties');
169
201
  // In 2D: (A × B) × C = B(A·C) - A(B·C)
170
202
  const ac = dot(a, c);
171
203
  const bc = dot(b, c);
@@ -185,16 +217,27 @@ export function tripleProduct(a, b, c) {
185
217
  * @param {Array<{x: Decimal, y: Decimal}>} polygon - Convex polygon vertices
186
218
  * @param {{x: Decimal, y: Decimal}} direction - Direction to search
187
219
  * @returns {{x: Decimal, y: Decimal}} Farthest point
220
+ * @throws {TypeError} If polygon is not an array or direction is invalid
188
221
  */
189
222
  export function supportPoint(polygon, direction) {
223
+ if (!Array.isArray(polygon)) throw new TypeError('supportPoint: polygon must be an array');
224
+ if (!direction || direction.x == null || direction.y == null) throw new TypeError('supportPoint: direction must have x and y properties');
190
225
  if (polygon.length === 0) {
191
226
  return point(0, 0);
192
227
  }
193
228
 
229
+ // Validate first point
230
+ if (!polygon[0] || polygon[0].x == null || polygon[0].y == null) {
231
+ throw new TypeError('supportPoint: polygon[0] must have x and y properties');
232
+ }
233
+
194
234
  let maxDot = dot(polygon[0], direction);
195
235
  let maxPoint = polygon[0];
196
236
 
197
237
  for (let i = 1; i < polygon.length; i++) {
238
+ if (!polygon[i] || polygon[i].x == null || polygon[i].y == null) {
239
+ throw new TypeError(`supportPoint: polygon[${i}] must have x and y properties`);
240
+ }
198
241
  const d = dot(polygon[i], direction);
199
242
  if (d.greaterThan(maxDot)) {
200
243
  maxDot = d;
@@ -215,8 +258,12 @@ export function supportPoint(polygon, direction) {
215
258
  * @param {Array<{x: Decimal, y: Decimal}>} polygonB - Second convex polygon
216
259
  * @param {{x: Decimal, y: Decimal}} direction - Direction to search
217
260
  * @returns {{x: Decimal, y: Decimal}} Support point on Minkowski difference
261
+ * @throws {TypeError} If polygons are not arrays or direction is invalid
218
262
  */
219
263
  export function minkowskiSupport(polygonA, polygonB, direction) {
264
+ if (!Array.isArray(polygonA)) throw new TypeError('minkowskiSupport: polygonA must be an array');
265
+ if (!Array.isArray(polygonB)) throw new TypeError('minkowskiSupport: polygonB must be an array');
266
+ if (!direction || direction.x == null || direction.y == null) throw new TypeError('minkowskiSupport: direction must have x and y properties');
220
267
  const pointA = supportPoint(polygonA, direction);
221
268
  const pointB = supportPoint(polygonB, vectorNeg(direction));
222
269
  return vectorSub(pointA, pointB);
@@ -236,8 +283,14 @@ export function minkowskiSupport(polygonA, polygonB, direction) {
236
283
  * @param {Array<{x: Decimal, y: Decimal}>} simplex - Current simplex (modified in place)
237
284
  * @param {{x: Decimal, y: Decimal}} direction - Current search direction (modified)
238
285
  * @returns {{contains: boolean, newDirection: {x: Decimal, y: Decimal}}}
286
+ * @throws {TypeError} If simplex is not an array, has wrong length, or direction is invalid
239
287
  */
240
288
  export function processLineSimplex(simplex, direction) {
289
+ if (!Array.isArray(simplex)) throw new TypeError('processLineSimplex: simplex must be an array');
290
+ if (simplex.length !== 2) throw new TypeError('processLineSimplex: simplex must have exactly 2 points');
291
+ if (!direction || direction.x == null || direction.y == null) throw new TypeError('processLineSimplex: direction must have x and y properties');
292
+ if (!simplex[0] || simplex[0].x == null || simplex[0].y == null) throw new TypeError('processLineSimplex: simplex[0] must have x and y properties');
293
+ if (!simplex[1] || simplex[1].x == null || simplex[1].y == null) throw new TypeError('processLineSimplex: simplex[1] must have x and y properties');
241
294
  const A = simplex[0]; // Newest point
242
295
  const B = simplex[1];
243
296
 
@@ -276,8 +329,15 @@ export function processLineSimplex(simplex, direction) {
276
329
  * @param {Array<{x: Decimal, y: Decimal}>} simplex - Current simplex (modified in place)
277
330
  * @param {{x: Decimal, y: Decimal}} direction - Current search direction
278
331
  * @returns {{contains: boolean, newDirection: {x: Decimal, y: Decimal}, newSimplex: Array}}
332
+ * @throws {TypeError} If simplex is not an array, has wrong length, or direction is invalid
279
333
  */
280
334
  export function processTriangleSimplex(simplex, direction) {
335
+ if (!Array.isArray(simplex)) throw new TypeError('processTriangleSimplex: simplex must be an array');
336
+ if (simplex.length !== 3) throw new TypeError('processTriangleSimplex: simplex must have exactly 3 points');
337
+ if (!direction || direction.x == null || direction.y == null) throw new TypeError('processTriangleSimplex: direction must have x and y properties');
338
+ if (!simplex[0] || simplex[0].x == null || simplex[0].y == null) throw new TypeError('processTriangleSimplex: simplex[0] must have x and y properties');
339
+ if (!simplex[1] || simplex[1].x == null || simplex[1].y == null) throw new TypeError('processTriangleSimplex: simplex[1] must have x and y properties');
340
+ if (!simplex[2] || simplex[2].x == null || simplex[2].y == null) throw new TypeError('processTriangleSimplex: simplex[2] must have x and y properties');
281
341
  const A = simplex[0]; // Newest point
282
342
  const B = simplex[1];
283
343
  const C = simplex[2];
@@ -336,8 +396,11 @@ export function processTriangleSimplex(simplex, direction) {
336
396
  * @param {Array<{x: Decimal, y: Decimal}>} polygonA - First convex polygon
337
397
  * @param {Array<{x: Decimal, y: Decimal}>} polygonB - Second convex polygon
338
398
  * @returns {{intersects: boolean, iterations: number, simplex: Array, verified: boolean}}
399
+ * @throws {TypeError} If polygons are not arrays
339
400
  */
340
401
  export function gjkIntersects(polygonA, polygonB) {
402
+ if (!Array.isArray(polygonA)) throw new TypeError('gjkIntersects: polygonA must be an array');
403
+ if (!Array.isArray(polygonB)) throw new TypeError('gjkIntersects: polygonB must be an array');
341
404
  // Handle empty polygons
342
405
  if (polygonA.length === 0 || polygonB.length === 0) {
343
406
  return { intersects: false, iterations: 0, simplex: [], verified: true };
@@ -345,6 +408,12 @@ export function gjkIntersects(polygonA, polygonB) {
345
408
 
346
409
  // Handle single points
347
410
  if (polygonA.length === 1 && polygonB.length === 1) {
411
+ if (!polygonA[0] || polygonA[0].x == null || polygonA[0].y == null) {
412
+ throw new TypeError('gjkIntersects: polygonA[0] must have x and y properties');
413
+ }
414
+ if (!polygonB[0] || polygonB[0].x == null || polygonB[0].y == null) {
415
+ throw new TypeError('gjkIntersects: polygonB[0] must have x and y properties');
416
+ }
348
417
  const dist = magnitude(vectorSub(polygonA[0], polygonB[0]));
349
418
  return {
350
419
  intersects: dist.lessThan(EPSILON),
@@ -460,8 +529,10 @@ export function gjkIntersects(polygonA, polygonB) {
460
529
  * Calculate centroid of a polygon.
461
530
  * @param {Array<{x: Decimal, y: Decimal}>} polygon - Polygon vertices
462
531
  * @returns {{x: Decimal, y: Decimal}} Centroid point
532
+ * @throws {TypeError} If polygon is not an array
463
533
  */
464
534
  export function centroid(polygon) {
535
+ if (!Array.isArray(polygon)) throw new TypeError('centroid: polygon must be an array');
465
536
  if (polygon.length === 0) {
466
537
  return point(0, 0);
467
538
  }
@@ -469,9 +540,12 @@ export function centroid(polygon) {
469
540
  let sumX = D(0);
470
541
  let sumY = D(0);
471
542
 
472
- for (const p of polygon) {
473
- sumX = sumX.plus(p.x);
474
- sumY = sumY.plus(p.y);
543
+ for (let i = 0; i < polygon.length; i++) {
544
+ if (!polygon[i] || polygon[i].x == null || polygon[i].y == null) {
545
+ throw new TypeError(`centroid: polygon[${i}] must have x and y properties`);
546
+ }
547
+ sumX = sumX.plus(polygon[i].x);
548
+ sumY = sumY.plus(polygon[i].y);
475
549
  }
476
550
 
477
551
  const n = D(polygon.length);
@@ -487,8 +561,11 @@ export function centroid(polygon) {
487
561
  * @param {{x: Decimal, y: Decimal}} pt - Point to test
488
562
  * @param {Array<{x: Decimal, y: Decimal}>} polygon - Convex polygon
489
563
  * @returns {boolean} True if point is inside (including boundary)
564
+ * @throws {TypeError} If pt is invalid or polygon is not an array
490
565
  */
491
566
  export function pointInConvexPolygon(pt, polygon) {
567
+ if (!pt || pt.x == null || pt.y == null) throw new TypeError('pointInConvexPolygon: point must have x and y properties');
568
+ if (!Array.isArray(polygon)) throw new TypeError('pointInConvexPolygon: polygon must be an array');
492
569
  if (polygon.length < 3) {
493
570
  return false;
494
571
  }
@@ -499,6 +576,13 @@ export function pointInConvexPolygon(pt, polygon) {
499
576
  const p1 = polygon[i];
500
577
  const p2 = polygon[(i + 1) % polygon.length];
501
578
 
579
+ if (!p1 || p1.x == null || p1.y == null) {
580
+ throw new TypeError(`pointInConvexPolygon: polygon[${i}] must have x and y properties`);
581
+ }
582
+ if (!p2 || p2.x == null || p2.y == null) {
583
+ throw new TypeError(`pointInConvexPolygon: polygon[${(i + 1) % polygon.length}] must have x and y properties`);
584
+ }
585
+
502
586
  const edge = vectorSub(p2, p1);
503
587
  const toPoint = vectorSub(pt, p1);
504
588
  const crossVal = cross(edge, toPoint);
@@ -530,18 +614,27 @@ export function pointInConvexPolygon(pt, polygon) {
530
614
  * @param {Array<{x: Decimal, y: Decimal}>} polygonA - First polygon
531
615
  * @param {Array<{x: Decimal, y: Decimal}>} polygonB - Second polygon
532
616
  * @returns {boolean} True if intersection is verified
617
+ * @throws {TypeError} If polygons are not arrays
533
618
  */
534
619
  export function verifyIntersection(polygonA, polygonB) {
620
+ if (!Array.isArray(polygonA)) throw new TypeError('verifyIntersection: polygonA must be an array');
621
+ if (!Array.isArray(polygonB)) throw new TypeError('verifyIntersection: polygonB must be an array');
535
622
  // Check if any vertex of A is inside B
536
- for (const p of polygonA) {
537
- if (pointInConvexPolygon(p, polygonB)) {
623
+ for (let i = 0; i < polygonA.length; i++) {
624
+ if (!polygonA[i] || polygonA[i].x == null || polygonA[i].y == null) {
625
+ throw new TypeError(`verifyIntersection: polygonA[${i}] must have x and y properties`);
626
+ }
627
+ if (pointInConvexPolygon(polygonA[i], polygonB)) {
538
628
  return true;
539
629
  }
540
630
  }
541
631
 
542
632
  // Check if any vertex of B is inside A
543
- for (const p of polygonB) {
544
- if (pointInConvexPolygon(p, polygonA)) {
633
+ for (let i = 0; i < polygonB.length; i++) {
634
+ if (!polygonB[i] || polygonB[i].x == null || polygonB[i].y == null) {
635
+ throw new TypeError(`verifyIntersection: polygonB[${i}] must have x and y properties`);
636
+ }
637
+ if (pointInConvexPolygon(polygonB[i], polygonA)) {
545
638
  return true;
546
639
  }
547
640
  }
@@ -551,10 +644,24 @@ export function verifyIntersection(polygonA, polygonB) {
551
644
  const a1 = polygonA[i];
552
645
  const a2 = polygonA[(i + 1) % polygonA.length];
553
646
 
647
+ if (!a1 || a1.x == null || a1.y == null) {
648
+ throw new TypeError(`verifyIntersection: polygonA[${i}] must have x and y properties`);
649
+ }
650
+ if (!a2 || a2.x == null || a2.y == null) {
651
+ throw new TypeError(`verifyIntersection: polygonA[${(i + 1) % polygonA.length}] must have x and y properties`);
652
+ }
653
+
554
654
  for (let j = 0; j < polygonB.length; j++) {
555
655
  const b1 = polygonB[j];
556
656
  const b2 = polygonB[(j + 1) % polygonB.length];
557
657
 
658
+ if (!b1 || b1.x == null || b1.y == null) {
659
+ throw new TypeError(`verifyIntersection: polygonB[${j}] must have x and y properties`);
660
+ }
661
+ if (!b2 || b2.x == null || b2.y == null) {
662
+ throw new TypeError(`verifyIntersection: polygonB[${(j + 1) % polygonB.length}] must have x and y properties`);
663
+ }
664
+
558
665
  if (segmentsIntersect(a1, a2, b1, b2)) {
559
666
  return true;
560
667
  }
@@ -572,8 +679,14 @@ export function verifyIntersection(polygonA, polygonB) {
572
679
  * @param {{x: Decimal, y: Decimal}} b1 - Second segment start
573
680
  * @param {{x: Decimal, y: Decimal}} b2 - Second segment end
574
681
  * @returns {boolean} True if segments intersect
682
+ * @throws {TypeError} If any point is invalid or missing x/y properties
575
683
  */
576
684
  export function segmentsIntersect(a1, a2, b1, b2) {
685
+ if (!a1 || a1.x == null || a1.y == null) throw new TypeError('segmentsIntersect: a1 must have x and y properties');
686
+ if (!a2 || a2.x == null || a2.y == null) throw new TypeError('segmentsIntersect: a2 must have x and y properties');
687
+ if (!b1 || b1.x == null || b1.y == null) throw new TypeError('segmentsIntersect: b1 must have x and y properties');
688
+ if (!b2 || b2.x == null || b2.y == null) throw new TypeError('segmentsIntersect: b2 must have x and y properties');
689
+
577
690
  const d1 = vectorSub(a2, a1);
578
691
  const d2 = vectorSub(b2, b1);
579
692
 
@@ -585,9 +698,22 @@ export function segmentsIntersect(a1, a2, b1, b2) {
585
698
  const d3 = vectorSub(b1, a1);
586
699
  if (cross(d1, d3).abs().lessThan(EPSILON)) {
587
700
  // Collinear - check overlap
588
- const t0 = dot(d3, d1).div(dot(d1, d1));
701
+ const d1LengthSq = dot(d1, d1);
702
+ // Handle degenerate first segment (a1 == a2)
703
+ if (d1LengthSq.lessThan(EPSILON)) {
704
+ // First segment is a point - check if it lies on second segment
705
+ const d2LengthSq = dot(d2, d2);
706
+ if (d2LengthSq.lessThan(EPSILON)) {
707
+ // Both segments are points - check if same point
708
+ return magnitude(d3).lessThan(EPSILON);
709
+ }
710
+ // Check if point a1 is on segment b1-b2
711
+ const t = dot(d3, d2).div(d2LengthSq);
712
+ return t.greaterThanOrEqualTo(0) && t.lessThanOrEqualTo(1);
713
+ }
714
+ const t0 = dot(d3, d1).div(d1LengthSq);
589
715
  const d4 = vectorSub(b2, a1);
590
- const t1 = dot(d4, d1).div(dot(d1, d1));
716
+ const t1 = dot(d4, d1).div(d1LengthSq);
591
717
 
592
718
  const tMin = Decimal.min(t0, t1);
593
719
  const tMax = Decimal.max(t0, t1);
@@ -620,8 +746,14 @@ export function segmentsIntersect(a1, a2, b1, b2) {
620
746
  * @param {Array<{x: Decimal, y: Decimal}>} polygonA - First convex polygon
621
747
  * @param {Array<{x: Decimal, y: Decimal}>} polygonB - Second convex polygon
622
748
  * @returns {{distance: Decimal, closestA: {x: Decimal, y: Decimal}, closestB: {x: Decimal, y: Decimal}, verified: boolean}}
749
+ * @throws {TypeError} If polygons are not arrays or are empty
623
750
  */
624
751
  export function gjkDistance(polygonA, polygonB) {
752
+ if (!Array.isArray(polygonA)) throw new TypeError('gjkDistance: polygonA must be an array');
753
+ if (!Array.isArray(polygonB)) throw new TypeError('gjkDistance: polygonB must be an array');
754
+ if (polygonA.length === 0) throw new TypeError('gjkDistance: polygonA cannot be empty');
755
+ if (polygonB.length === 0) throw new TypeError('gjkDistance: polygonB cannot be empty');
756
+
625
757
  // First check if they intersect
626
758
  const intersection = gjkIntersects(polygonA, polygonB);
627
759
 
@@ -640,47 +772,79 @@ export function gjkDistance(polygonA, polygonB) {
640
772
  // For non-intersecting shapes, find the closest points by
641
773
  // examining all vertex-edge pairs
642
774
 
775
+ // Validate first points before using them as initial values
776
+ if (!polygonA[0] || polygonA[0].x == null || polygonA[0].y == null) {
777
+ throw new TypeError('gjkDistance: polygonA[0] must have x and y properties');
778
+ }
779
+ if (!polygonB[0] || polygonB[0].x == null || polygonB[0].y == null) {
780
+ throw new TypeError('gjkDistance: polygonB[0] must have x and y properties');
781
+ }
782
+
643
783
  let minDist = D(Infinity);
644
784
  let closestA = polygonA[0];
645
785
  let closestB = polygonB[0];
646
786
 
647
787
  // Check all pairs of vertices
648
- for (const pA of polygonA) {
649
- for (const pB of polygonB) {
650
- const dist = magnitude(vectorSub(pA, pB));
788
+ for (let i = 0; i < polygonA.length; i++) {
789
+ if (!polygonA[i] || polygonA[i].x == null || polygonA[i].y == null) {
790
+ throw new TypeError(`gjkDistance: polygonA[${i}] must have x and y properties`);
791
+ }
792
+ for (let j = 0; j < polygonB.length; j++) {
793
+ if (!polygonB[j] || polygonB[j].x == null || polygonB[j].y == null) {
794
+ throw new TypeError(`gjkDistance: polygonB[${j}] must have x and y properties`);
795
+ }
796
+ const dist = magnitude(vectorSub(polygonA[i], polygonB[j]));
651
797
  if (dist.lessThan(minDist)) {
652
798
  minDist = dist;
653
- closestA = pA;
654
- closestB = pB;
799
+ closestA = polygonA[i];
800
+ closestB = polygonB[j];
655
801
  }
656
802
  }
657
803
  }
658
804
 
659
805
  // Check vertex-to-edge distances
660
- for (const pA of polygonA) {
661
- for (let i = 0; i < polygonB.length; i++) {
662
- const e1 = polygonB[i];
663
- const e2 = polygonB[(i + 1) % polygonB.length];
664
- const closest = closestPointOnSegment(pA, e1, e2);
665
- const dist = magnitude(vectorSub(pA, closest));
806
+ for (let i = 0; i < polygonA.length; i++) {
807
+ if (!polygonA[i] || polygonA[i].x == null || polygonA[i].y == null) {
808
+ throw new TypeError(`gjkDistance: polygonA[${i}] must have x and y properties`);
809
+ }
810
+ for (let j = 0; j < polygonB.length; j++) {
811
+ const e1 = polygonB[j];
812
+ const e2 = polygonB[(j + 1) % polygonB.length];
813
+ if (!e1 || e1.x == null || e1.y == null) {
814
+ throw new TypeError(`gjkDistance: polygonB[${j}] must have x and y properties`);
815
+ }
816
+ if (!e2 || e2.x == null || e2.y == null) {
817
+ throw new TypeError(`gjkDistance: polygonB[${(j + 1) % polygonB.length}] must have x and y properties`);
818
+ }
819
+ const closest = closestPointOnSegment(polygonA[i], e1, e2);
820
+ const dist = magnitude(vectorSub(polygonA[i], closest));
666
821
  if (dist.lessThan(minDist)) {
667
822
  minDist = dist;
668
- closestA = pA;
823
+ closestA = polygonA[i];
669
824
  closestB = closest;
670
825
  }
671
826
  }
672
827
  }
673
828
 
674
- for (const pB of polygonB) {
675
- for (let i = 0; i < polygonA.length; i++) {
676
- const e1 = polygonA[i];
677
- const e2 = polygonA[(i + 1) % polygonA.length];
678
- const closest = closestPointOnSegment(pB, e1, e2);
679
- const dist = magnitude(vectorSub(pB, closest));
829
+ for (let i = 0; i < polygonB.length; i++) {
830
+ if (!polygonB[i] || polygonB[i].x == null || polygonB[i].y == null) {
831
+ throw new TypeError(`gjkDistance: polygonB[${i}] must have x and y properties`);
832
+ }
833
+ for (let j = 0; j < polygonA.length; j++) {
834
+ const e1 = polygonA[j];
835
+ const e2 = polygonA[(j + 1) % polygonA.length];
836
+ if (!e1 || e1.x == null || e1.y == null) {
837
+ throw new TypeError(`gjkDistance: polygonA[${j}] must have x and y properties`);
838
+ }
839
+ if (!e2 || e2.x == null || e2.y == null) {
840
+ throw new TypeError(`gjkDistance: polygonA[${(j + 1) % polygonA.length}] must have x and y properties`);
841
+ }
842
+ const closest = closestPointOnSegment(polygonB[i], e1, e2);
843
+ const dist = magnitude(vectorSub(polygonB[i], closest));
680
844
  if (dist.lessThan(minDist)) {
681
845
  minDist = dist;
682
846
  closestA = closest;
683
- closestB = pB;
847
+ closestB = polygonB[i];
684
848
  }
685
849
  }
686
850
  }
@@ -704,8 +868,13 @@ export function gjkDistance(polygonA, polygonB) {
704
868
  * @param {{x: Decimal, y: Decimal}} a - Segment start
705
869
  * @param {{x: Decimal, y: Decimal}} b - Segment end
706
870
  * @returns {{x: Decimal, y: Decimal}} Closest point on segment
871
+ * @throws {TypeError} If any point is invalid or missing x/y properties
707
872
  */
708
873
  export function closestPointOnSegment(pt, a, b) {
874
+ if (!pt || pt.x == null || pt.y == null) throw new TypeError('closestPointOnSegment: pt must have x and y properties');
875
+ if (!a || a.x == null || a.y == null) throw new TypeError('closestPointOnSegment: a must have x and y properties');
876
+ if (!b || b.x == null || b.y == null) throw new TypeError('closestPointOnSegment: b must have x and y properties');
877
+
709
878
  const ab = vectorSub(b, a);
710
879
  const ap = vectorSub(pt, a);
711
880
 
@@ -735,11 +904,24 @@ export function closestPointOnSegment(pt, a, b) {
735
904
  * @param {Array<{x: number|Decimal, y: number|Decimal}>} polygonA - First polygon
736
905
  * @param {Array<{x: number|Decimal, y: number|Decimal}>} polygonB - Second polygon
737
906
  * @returns {{overlaps: boolean, verified: boolean}}
907
+ * @throws {TypeError} If polygons are not arrays
738
908
  */
739
909
  export function polygonsOverlap(polygonA, polygonB) {
910
+ if (!Array.isArray(polygonA)) throw new TypeError('polygonsOverlap: polygonA must be an array');
911
+ if (!Array.isArray(polygonB)) throw new TypeError('polygonsOverlap: polygonB must be an array');
740
912
  // Normalize input to Decimal
741
- const normA = polygonA.map(p => point(p.x, p.y));
742
- const normB = polygonB.map(p => point(p.x, p.y));
913
+ const normA = polygonA.map((p, i) => {
914
+ if (!p || p.x == null || p.y == null) {
915
+ throw new TypeError(`polygonsOverlap: polygonA[${i}] must have x and y properties`);
916
+ }
917
+ return point(p.x, p.y);
918
+ });
919
+ const normB = polygonB.map((p, i) => {
920
+ if (!p || p.x == null || p.y == null) {
921
+ throw new TypeError(`polygonsOverlap: polygonB[${i}] must have x and y properties`);
922
+ }
923
+ return point(p.x, p.y);
924
+ });
743
925
 
744
926
  const result = gjkIntersects(normA, normB);
745
927
 
@@ -755,11 +937,24 @@ export function polygonsOverlap(polygonA, polygonB) {
755
937
  * @param {Array<{x: number|Decimal, y: number|Decimal}>} polygonA - First polygon
756
938
  * @param {Array<{x: number|Decimal, y: number|Decimal}>} polygonB - Second polygon
757
939
  * @returns {{distance: Decimal, verified: boolean}}
940
+ * @throws {TypeError} If polygons are not arrays
758
941
  */
759
942
  export function polygonsDistance(polygonA, polygonB) {
943
+ if (!Array.isArray(polygonA)) throw new TypeError('polygonsDistance: polygonA must be an array');
944
+ if (!Array.isArray(polygonB)) throw new TypeError('polygonsDistance: polygonB must be an array');
760
945
  // Normalize input to Decimal
761
- const normA = polygonA.map(p => point(p.x, p.y));
762
- const normB = polygonB.map(p => point(p.x, p.y));
946
+ const normA = polygonA.map((p, i) => {
947
+ if (!p || p.x == null || p.y == null) {
948
+ throw new TypeError(`polygonsDistance: polygonA[${i}] must have x and y properties`);
949
+ }
950
+ return point(p.x, p.y);
951
+ });
952
+ const normB = polygonB.map((p, i) => {
953
+ if (!p || p.x == null || p.y == null) {
954
+ throw new TypeError(`polygonsDistance: polygonB[${i}] must have x and y properties`);
955
+ }
956
+ return point(p.x, p.y);
957
+ });
763
958
 
764
959
  const result = gjkDistance(normA, normB);
765
960
 
@@ -777,10 +972,18 @@ export function polygonsDistance(polygonA, polygonB) {
777
972
  * @param {{x: number|Decimal, y: number|Decimal}} pt - Point to test
778
973
  * @param {Array<{x: number|Decimal, y: number|Decimal}>} polygon - Polygon
779
974
  * @returns {boolean} True if inside
975
+ * @throws {TypeError} If pt is invalid or polygon is not an array
780
976
  */
781
977
  export function isPointInPolygon(pt, polygon) {
978
+ if (!pt || pt.x == null || pt.y == null) throw new TypeError('isPointInPolygon: pt must have x and y properties');
979
+ if (!Array.isArray(polygon)) throw new TypeError('isPointInPolygon: polygon must be an array');
782
980
  const normPt = point(pt.x, pt.y);
783
- const normPoly = polygon.map(p => point(p.x, p.y));
981
+ const normPoly = polygon.map((p, i) => {
982
+ if (!p || p.x == null || p.y == null) {
983
+ throw new TypeError(`isPointInPolygon: polygon[${i}] must have x and y properties`);
984
+ }
985
+ return point(p.x, p.y);
986
+ });
784
987
 
785
988
  return pointInConvexPolygon(normPt, normPoly);
786
989
  }