@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.
- package/bin/svg-matrix.js +310 -61
- package/bin/svglinter.cjs +102 -3
- package/bin/svgm.js +236 -27
- package/package.json +1 -1
- package/src/animation-optimization.js +137 -17
- package/src/animation-references.js +123 -6
- package/src/arc-length.js +213 -4
- package/src/bezier-analysis.js +217 -21
- package/src/bezier-intersections.js +275 -12
- package/src/browser-verify.js +237 -4
- package/src/clip-path-resolver.js +168 -0
- package/src/convert-path-data.js +479 -28
- package/src/css-specificity.js +73 -10
- package/src/douglas-peucker.js +219 -2
- package/src/flatten-pipeline.js +284 -26
- package/src/geometry-to-path.js +250 -25
- package/src/gjk-collision.js +236 -33
- package/src/index.js +261 -3
- package/src/inkscape-support.js +86 -28
- package/src/logger.js +48 -3
- package/src/marker-resolver.js +278 -74
- package/src/mask-resolver.js +265 -66
- package/src/matrix.js +44 -5
- package/src/mesh-gradient.js +352 -102
- package/src/off-canvas-detection.js +382 -13
- package/src/path-analysis.js +192 -18
- package/src/path-data-plugins.js +309 -5
- package/src/path-optimization.js +129 -5
- package/src/path-simplification.js +188 -32
- package/src/pattern-resolver.js +454 -106
- package/src/polygon-clip.js +324 -1
- package/src/svg-boolean-ops.js +226 -9
- package/src/svg-collections.js +7 -5
- package/src/svg-flatten.js +386 -62
- package/src/svg-parser.js +179 -8
- package/src/svg-rendering-context.js +235 -6
- package/src/svg-toolbox.js +45 -8
- package/src/svg2-polyfills.js +40 -10
- package/src/transform-decomposition.js +258 -32
- package/src/transform-optimization.js +259 -13
- package/src/transforms2d.js +82 -9
- package/src/transforms3d.js +62 -10
- package/src/use-symbol-resolver.js +286 -42
- package/src/vector.js +64 -8
- package/src/verification.js +392 -1
package/src/polygon-clip.js
CHANGED
|
@@ -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 (
|
|
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
|
|