@emasoft/svg-matrix 1.0.28 → 1.0.30
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/README.md +325 -0
- package/bin/svg-matrix.js +985 -378
- package/bin/svglinter.cjs +4172 -433
- package/bin/svgm.js +723 -180
- package/package.json +16 -4
- package/src/animation-references.js +71 -52
- package/src/arc-length.js +160 -96
- package/src/bezier-analysis.js +257 -117
- package/src/bezier-intersections.js +411 -148
- package/src/browser-verify.js +240 -100
- package/src/clip-path-resolver.js +350 -142
- package/src/convert-path-data.js +279 -134
- package/src/css-specificity.js +78 -70
- package/src/flatten-pipeline.js +751 -263
- package/src/geometry-to-path.js +511 -182
- package/src/index.js +191 -46
- package/src/inkscape-support.js +18 -7
- package/src/marker-resolver.js +278 -164
- package/src/mask-resolver.js +209 -98
- package/src/matrix.js +147 -67
- package/src/mesh-gradient.js +187 -96
- package/src/off-canvas-detection.js +201 -104
- package/src/path-analysis.js +187 -107
- package/src/path-data-plugins.js +628 -167
- package/src/path-simplification.js +0 -1
- package/src/pattern-resolver.js +125 -88
- package/src/polygon-clip.js +111 -66
- package/src/svg-boolean-ops.js +194 -118
- package/src/svg-collections.js +22 -18
- package/src/svg-flatten.js +282 -164
- package/src/svg-parser.js +427 -200
- package/src/svg-rendering-context.js +147 -104
- package/src/svg-toolbox.js +16381 -3370
- package/src/svg2-polyfills.js +93 -224
- package/src/transform-decomposition.js +46 -41
- package/src/transform-optimization.js +89 -68
- package/src/transforms2d.js +49 -16
- package/src/transforms3d.js +58 -22
- package/src/use-symbol-resolver.js +150 -110
- package/src/vector.js +67 -15
- package/src/vendor/README.md +110 -0
- package/src/vendor/inkscape-hatch-polyfill.js +401 -0
- package/src/vendor/inkscape-hatch-polyfill.min.js +8 -0
- package/src/vendor/inkscape-mesh-polyfill.js +843 -0
- package/src/vendor/inkscape-mesh-polyfill.min.js +8 -0
- package/src/verification.js +288 -124
package/src/polygon-clip.js
CHANGED
|
@@ -88,16 +88,16 @@
|
|
|
88
88
|
* @module polygon-clip
|
|
89
89
|
*/
|
|
90
90
|
|
|
91
|
-
import Decimal from
|
|
91
|
+
import Decimal from "decimal.js";
|
|
92
92
|
|
|
93
93
|
// Set high precision for all calculations
|
|
94
94
|
Decimal.set({ precision: 80 });
|
|
95
95
|
|
|
96
96
|
// Helper to convert to Decimal
|
|
97
|
-
const D = x => (x instanceof Decimal ? x : new Decimal(x));
|
|
97
|
+
const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
|
|
98
98
|
|
|
99
99
|
// Near-zero threshold for comparisons
|
|
100
|
-
const EPSILON = new Decimal(
|
|
100
|
+
const EPSILON = new Decimal("1e-40");
|
|
101
101
|
|
|
102
102
|
// ============================================================================
|
|
103
103
|
// Point and Vector Primitives
|
|
@@ -153,8 +153,9 @@ export function point(x, y) {
|
|
|
153
153
|
* pointsEqual(p1, p2); // false
|
|
154
154
|
*/
|
|
155
155
|
export function pointsEqual(p1, p2, tolerance = EPSILON) {
|
|
156
|
-
return
|
|
157
|
-
|
|
156
|
+
return (
|
|
157
|
+
p1.x.minus(p2.x).abs().lt(tolerance) && p1.y.minus(p2.y).abs().lt(tolerance)
|
|
158
|
+
);
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
/**
|
|
@@ -355,7 +356,7 @@ export function segmentIntersection(a, b, c, d) {
|
|
|
355
356
|
x: a.x.plus(dx1.mul(t)),
|
|
356
357
|
y: a.y.plus(dy1.mul(t)),
|
|
357
358
|
t: t,
|
|
358
|
-
s: s
|
|
359
|
+
s: s,
|
|
359
360
|
};
|
|
360
361
|
}
|
|
361
362
|
|
|
@@ -421,7 +422,7 @@ export function lineSegmentIntersection(lineA, lineB, segA, segB) {
|
|
|
421
422
|
return {
|
|
422
423
|
x: segA.x.plus(dx2.mul(s)),
|
|
423
424
|
y: segA.y.plus(dy2.mul(s)),
|
|
424
|
-
s: s
|
|
425
|
+
s: s,
|
|
425
426
|
};
|
|
426
427
|
}
|
|
427
428
|
|
|
@@ -557,8 +558,12 @@ export function pointOnSegment(pt, a, b) {
|
|
|
557
558
|
const minY = Decimal.min(a.y, b.y);
|
|
558
559
|
const maxY = Decimal.max(a.y, b.y);
|
|
559
560
|
|
|
560
|
-
return
|
|
561
|
-
|
|
561
|
+
return (
|
|
562
|
+
pt.x.gte(minX.minus(EPSILON)) &&
|
|
563
|
+
pt.x.lte(maxX.plus(EPSILON)) &&
|
|
564
|
+
pt.y.gte(minY.minus(EPSILON)) &&
|
|
565
|
+
pt.y.lte(maxY.plus(EPSILON))
|
|
566
|
+
);
|
|
562
567
|
}
|
|
563
568
|
|
|
564
569
|
// ============================================================================
|
|
@@ -614,8 +619,8 @@ export function clipPolygonSH(subject, clip) {
|
|
|
614
619
|
}
|
|
615
620
|
|
|
616
621
|
// Convert all points to Decimal
|
|
617
|
-
let output = subject.map(p => point(p.x, p.y));
|
|
618
|
-
const clipPoly = clip.map(p => point(p.x, p.y));
|
|
622
|
+
let output = subject.map((p) => point(p.x, p.y));
|
|
623
|
+
const clipPoly = clip.map((p) => point(p.x, p.y));
|
|
619
624
|
|
|
620
625
|
// Clip against each edge of the clipping polygon
|
|
621
626
|
for (let i = 0; i < clipPoly.length; i++) {
|
|
@@ -639,7 +644,12 @@ export function clipPolygonSH(subject, clip) {
|
|
|
639
644
|
if (currentInside) {
|
|
640
645
|
if (!prevInside) {
|
|
641
646
|
// Entering: add intersection point
|
|
642
|
-
const intersection = lineIntersection(
|
|
647
|
+
const intersection = lineIntersection(
|
|
648
|
+
prev,
|
|
649
|
+
current,
|
|
650
|
+
clipEdgeStart,
|
|
651
|
+
clipEdgeEnd,
|
|
652
|
+
);
|
|
643
653
|
if (intersection) {
|
|
644
654
|
output.push(intersection);
|
|
645
655
|
}
|
|
@@ -647,7 +657,12 @@ export function clipPolygonSH(subject, clip) {
|
|
|
647
657
|
output.push(current);
|
|
648
658
|
} else if (prevInside) {
|
|
649
659
|
// Leaving: add intersection point
|
|
650
|
-
const intersection = lineIntersection(
|
|
660
|
+
const intersection = lineIntersection(
|
|
661
|
+
prev,
|
|
662
|
+
current,
|
|
663
|
+
clipEdgeStart,
|
|
664
|
+
clipEdgeEnd,
|
|
665
|
+
);
|
|
651
666
|
if (intersection) {
|
|
652
667
|
output.push(intersection);
|
|
653
668
|
}
|
|
@@ -707,7 +722,7 @@ function lineIntersection(a, b, c, d) {
|
|
|
707
722
|
|
|
708
723
|
return {
|
|
709
724
|
x: a.x.plus(dx1.mul(t)),
|
|
710
|
-
y: a.y.plus(dy1.mul(t))
|
|
725
|
+
y: a.y.plus(dy1.mul(t)),
|
|
711
726
|
};
|
|
712
727
|
}
|
|
713
728
|
|
|
@@ -876,17 +891,19 @@ export function ensureCCW(polygon) {
|
|
|
876
891
|
*/
|
|
877
892
|
export function convexHull(points) {
|
|
878
893
|
if (points.length < 3) {
|
|
879
|
-
return points.map(p => point(p.x, p.y));
|
|
894
|
+
return points.map((p) => point(p.x, p.y));
|
|
880
895
|
}
|
|
881
896
|
|
|
882
897
|
// Convert to Decimal points
|
|
883
|
-
const pts = points.map(p => point(p.x, p.y));
|
|
898
|
+
const pts = points.map((p) => point(p.x, p.y));
|
|
884
899
|
|
|
885
900
|
// Find the lowest point (and leftmost if tie)
|
|
886
901
|
let lowest = 0;
|
|
887
902
|
for (let i = 1; i < pts.length; i++) {
|
|
888
|
-
if (
|
|
889
|
-
|
|
903
|
+
if (
|
|
904
|
+
pts[i].y.lt(pts[lowest].y) ||
|
|
905
|
+
(pts[i].y.eq(pts[lowest].y) && pts[i].x.lt(pts[lowest].x))
|
|
906
|
+
) {
|
|
890
907
|
lowest = i;
|
|
891
908
|
}
|
|
892
909
|
}
|
|
@@ -911,7 +928,10 @@ export function convexHull(points) {
|
|
|
911
928
|
const hull = [pivot];
|
|
912
929
|
|
|
913
930
|
for (const pt of sorted) {
|
|
914
|
-
while (
|
|
931
|
+
while (
|
|
932
|
+
hull.length > 1 &&
|
|
933
|
+
cross(hull[hull.length - 2], hull[hull.length - 1], pt).lte(0)
|
|
934
|
+
) {
|
|
915
935
|
hull.pop();
|
|
916
936
|
}
|
|
917
937
|
hull.push(pt);
|
|
@@ -996,8 +1016,12 @@ export function boundingBox(polygon) {
|
|
|
996
1016
|
*/
|
|
997
1017
|
export function bboxIntersects(bb1, bb2) {
|
|
998
1018
|
if (!bb1 || !bb2) return false;
|
|
999
|
-
return !(
|
|
1000
|
-
|
|
1019
|
+
return !(
|
|
1020
|
+
bb1.maxX.lt(bb2.minX) ||
|
|
1021
|
+
bb2.maxX.lt(bb1.minX) ||
|
|
1022
|
+
bb1.maxY.lt(bb2.minY) ||
|
|
1023
|
+
bb2.maxY.lt(bb1.minY)
|
|
1024
|
+
);
|
|
1001
1025
|
}
|
|
1002
1026
|
|
|
1003
1027
|
// ============================================================================
|
|
@@ -1044,8 +1068,8 @@ export function bboxIntersects(bb1, bb2) {
|
|
|
1044
1068
|
*/
|
|
1045
1069
|
export function polygonIntersection(subject, clip) {
|
|
1046
1070
|
// Convert to Decimal points
|
|
1047
|
-
const subjectPoly = subject.map(p => point(p.x, p.y));
|
|
1048
|
-
const clipPoly = clip.map(p => point(p.x, p.y));
|
|
1071
|
+
const subjectPoly = subject.map((p) => point(p.x, p.y));
|
|
1072
|
+
const clipPoly = clip.map((p) => point(p.x, p.y));
|
|
1049
1073
|
|
|
1050
1074
|
// Quick bounding box check
|
|
1051
1075
|
const bb1 = boundingBox(subjectPoly);
|
|
@@ -1100,7 +1124,7 @@ export function isConvex(polygon) {
|
|
|
1100
1124
|
const n = polygon.length;
|
|
1101
1125
|
if (n < 3) return false;
|
|
1102
1126
|
|
|
1103
|
-
let
|
|
1127
|
+
let crossSign = 0;
|
|
1104
1128
|
|
|
1105
1129
|
for (let i = 0; i < n; i++) {
|
|
1106
1130
|
const p0 = polygon[i];
|
|
@@ -1108,12 +1132,12 @@ export function isConvex(polygon) {
|
|
|
1108
1132
|
const p2 = polygon[(i + 2) % n];
|
|
1109
1133
|
|
|
1110
1134
|
const crossVal = cross(p0, p1, p2);
|
|
1111
|
-
const currentSign = crossVal.gt(0) ? 1 :
|
|
1135
|
+
const currentSign = crossVal.gt(0) ? 1 : crossVal.lt(0) ? -1 : 0;
|
|
1112
1136
|
|
|
1113
1137
|
if (currentSign !== 0) {
|
|
1114
|
-
if (
|
|
1115
|
-
|
|
1116
|
-
} else if (
|
|
1138
|
+
if (crossSign === 0) {
|
|
1139
|
+
crossSign = currentSign;
|
|
1140
|
+
} else if (crossSign !== currentSign) {
|
|
1117
1141
|
return false;
|
|
1118
1142
|
}
|
|
1119
1143
|
}
|
|
@@ -1162,10 +1186,10 @@ function generalPolygonIntersection(subject, clip) {
|
|
|
1162
1186
|
}
|
|
1163
1187
|
|
|
1164
1188
|
// Find subject vertices inside clip
|
|
1165
|
-
const subjectInside = subject.filter(p => pointInPolygon(p, clip) >= 0);
|
|
1189
|
+
const subjectInside = subject.filter((p) => pointInPolygon(p, clip) >= 0);
|
|
1166
1190
|
|
|
1167
1191
|
// Find clip vertices inside subject
|
|
1168
|
-
const clipInside = clip.filter(p => pointInPolygon(p, subject) >= 0);
|
|
1192
|
+
const clipInside = clip.filter((p) => pointInPolygon(p, subject) >= 0);
|
|
1169
1193
|
|
|
1170
1194
|
// Collect all points
|
|
1171
1195
|
const allPoints = [...intersectionPoints, ...subjectInside, ...clipInside];
|
|
@@ -1253,8 +1277,8 @@ function removeDuplicatePoints(points) {
|
|
|
1253
1277
|
* polygonUnion(square1, square2); // [square1, square2]
|
|
1254
1278
|
*/
|
|
1255
1279
|
export function polygonUnion(polygon1, polygon2) {
|
|
1256
|
-
const poly1 = polygon1.map(p => point(p.x, p.y));
|
|
1257
|
-
const poly2 = polygon2.map(p => point(p.x, p.y));
|
|
1280
|
+
const poly1 = polygon1.map((p) => point(p.x, p.y));
|
|
1281
|
+
const poly2 = polygon2.map((p) => point(p.x, p.y));
|
|
1258
1282
|
|
|
1259
1283
|
const bb1 = boundingBox(poly1);
|
|
1260
1284
|
const bb2 = boundingBox(poly2);
|
|
@@ -1304,19 +1328,25 @@ function traceBoundaryUnion(poly1, poly2) {
|
|
|
1304
1328
|
}
|
|
1305
1329
|
|
|
1306
1330
|
// Build augmented polygons with intersection points inserted
|
|
1307
|
-
const aug1 = augmentPolygon(
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1331
|
+
const aug1 = augmentPolygon(
|
|
1332
|
+
poly1,
|
|
1333
|
+
intersections.map((i) => ({
|
|
1334
|
+
edgeIndex: i.edge1,
|
|
1335
|
+
t: i.t1,
|
|
1336
|
+
point: i.point,
|
|
1337
|
+
intersectionId: i.id,
|
|
1338
|
+
})),
|
|
1339
|
+
);
|
|
1340
|
+
|
|
1341
|
+
const aug2 = augmentPolygon(
|
|
1342
|
+
poly2,
|
|
1343
|
+
intersections.map((i) => ({
|
|
1344
|
+
edgeIndex: i.edge2,
|
|
1345
|
+
t: i.t2,
|
|
1346
|
+
point: i.point,
|
|
1347
|
+
intersectionId: i.id,
|
|
1348
|
+
})),
|
|
1349
|
+
);
|
|
1320
1350
|
|
|
1321
1351
|
// Build lookup from intersection ID to indices in both polygons
|
|
1322
1352
|
const intersectionMap = new Map();
|
|
@@ -1387,7 +1417,10 @@ function traceBoundaryUnion(poly1, poly2) {
|
|
|
1387
1417
|
const vertex = aug[currentIdx];
|
|
1388
1418
|
|
|
1389
1419
|
// Add vertex to result (avoid duplicates)
|
|
1390
|
-
if (
|
|
1420
|
+
if (
|
|
1421
|
+
result.length === 0 ||
|
|
1422
|
+
!pointsEqual(result[result.length - 1], vertex)
|
|
1423
|
+
) {
|
|
1391
1424
|
result.push(point(vertex.x, vertex.y));
|
|
1392
1425
|
}
|
|
1393
1426
|
|
|
@@ -1396,7 +1429,10 @@ function traceBoundaryUnion(poly1, poly2) {
|
|
|
1396
1429
|
const nextVertex = aug[nextIdx];
|
|
1397
1430
|
|
|
1398
1431
|
// If next vertex is an intersection we haven't used, switch polygons
|
|
1399
|
-
if (
|
|
1432
|
+
if (
|
|
1433
|
+
nextVertex.intersectionId !== undefined &&
|
|
1434
|
+
!usedIntersections.has(nextVertex.intersectionId)
|
|
1435
|
+
) {
|
|
1400
1436
|
// Add the intersection point
|
|
1401
1437
|
result.push(point(nextVertex.x, nextVertex.y));
|
|
1402
1438
|
usedIntersections.add(nextVertex.intersectionId);
|
|
@@ -1453,7 +1489,7 @@ function findAllIntersections(poly1, poly2) {
|
|
|
1453
1489
|
edge1: i,
|
|
1454
1490
|
edge2: j,
|
|
1455
1491
|
t1: intersection.t,
|
|
1456
|
-
t2: intersection.s
|
|
1492
|
+
t2: intersection.s,
|
|
1457
1493
|
});
|
|
1458
1494
|
}
|
|
1459
1495
|
}
|
|
@@ -1497,7 +1533,7 @@ function augmentPolygon(polygon, insertions) {
|
|
|
1497
1533
|
result.push({
|
|
1498
1534
|
x: ins.point.x,
|
|
1499
1535
|
y: ins.point.y,
|
|
1500
|
-
intersectionId: ins.intersectionId
|
|
1536
|
+
intersectionId: ins.intersectionId,
|
|
1501
1537
|
});
|
|
1502
1538
|
}
|
|
1503
1539
|
}
|
|
@@ -1547,8 +1583,8 @@ function augmentPolygon(polygon, insertions) {
|
|
|
1547
1583
|
* polygonDifference(small, large); // []
|
|
1548
1584
|
*/
|
|
1549
1585
|
export function polygonDifference(polygon1, polygon2) {
|
|
1550
|
-
const poly1 = polygon1.map(p => point(p.x, p.y));
|
|
1551
|
-
const poly2 = polygon2.map(p => point(p.x, p.y));
|
|
1586
|
+
const poly1 = polygon1.map((p) => point(p.x, p.y));
|
|
1587
|
+
const poly2 = polygon2.map((p) => point(p.x, p.y));
|
|
1552
1588
|
|
|
1553
1589
|
const bb1 = boundingBox(poly1);
|
|
1554
1590
|
const bb2 = boundingBox(poly2);
|
|
@@ -1592,19 +1628,25 @@ function traceBoundaryDifference(poly1, poly2) {
|
|
|
1592
1628
|
}
|
|
1593
1629
|
|
|
1594
1630
|
// Build augmented polygons with intersection points inserted
|
|
1595
|
-
const aug1 = augmentPolygon(
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1631
|
+
const aug1 = augmentPolygon(
|
|
1632
|
+
poly1,
|
|
1633
|
+
intersections.map((i) => ({
|
|
1634
|
+
edgeIndex: i.edge1,
|
|
1635
|
+
t: i.t1,
|
|
1636
|
+
point: i.point,
|
|
1637
|
+
intersectionId: i.id,
|
|
1638
|
+
})),
|
|
1639
|
+
);
|
|
1640
|
+
|
|
1641
|
+
const aug2 = augmentPolygon(
|
|
1642
|
+
poly2,
|
|
1643
|
+
intersections.map((i) => ({
|
|
1644
|
+
edgeIndex: i.edge2,
|
|
1645
|
+
t: i.t2,
|
|
1646
|
+
point: i.point,
|
|
1647
|
+
intersectionId: i.id,
|
|
1648
|
+
})),
|
|
1649
|
+
);
|
|
1608
1650
|
|
|
1609
1651
|
// Build lookup from intersection ID to indices in both polygons
|
|
1610
1652
|
const intersectionMap = new Map();
|
|
@@ -1655,7 +1697,10 @@ function traceBoundaryDifference(poly1, poly2) {
|
|
|
1655
1697
|
const vertex = poly[currentIdx];
|
|
1656
1698
|
|
|
1657
1699
|
// Add vertex to result (avoid duplicates)
|
|
1658
|
-
if (
|
|
1700
|
+
if (
|
|
1701
|
+
result.length === 0 ||
|
|
1702
|
+
!pointsEqual(result[result.length - 1], vertex)
|
|
1703
|
+
) {
|
|
1659
1704
|
result.push(point(vertex.x, vertex.y));
|
|
1660
1705
|
}
|
|
1661
1706
|
|
|
@@ -1764,5 +1809,5 @@ export default {
|
|
|
1764
1809
|
polygonDifference,
|
|
1765
1810
|
|
|
1766
1811
|
// Constants
|
|
1767
|
-
EPSILON
|
|
1812
|
+
EPSILON,
|
|
1768
1813
|
};
|