modern-path2d 1.5.6 → 1.6.1
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/dist/index.cjs +320 -60
- package/dist/index.d.cts +133 -6
- package/dist/index.d.mts +133 -6
- package/dist/index.d.ts +133 -6
- package/dist/index.js +2 -2
- package/dist/index.mjs +315 -59
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -183,11 +183,9 @@ class Vector2 {
|
|
|
183
183
|
return Math.sqrt(this.lengthSquared());
|
|
184
184
|
}
|
|
185
185
|
scale(sx, sy = sx, origin = { x: 0, y: 0 }) {
|
|
186
|
-
const x = sx < 0 ? origin.x - this._x + origin.x : this._x;
|
|
187
|
-
const y = sy < 0 ? origin.y - this._y + origin.y : this._y;
|
|
188
186
|
return this.set(
|
|
189
|
-
x *
|
|
190
|
-
y *
|
|
187
|
+
origin.x + (this._x - origin.x) * sx,
|
|
188
|
+
origin.y + (this._y - origin.y) * sy
|
|
191
189
|
);
|
|
192
190
|
}
|
|
193
191
|
skew(ax, ay = 0, origin = { x: 0, y: 0 }) {
|
|
@@ -584,7 +582,7 @@ function getDirectedArea(vertices) {
|
|
|
584
582
|
for (let i = 0; i < n; i += 2) {
|
|
585
583
|
const x0 = vertices[i];
|
|
586
584
|
const y0 = vertices[i + 1];
|
|
587
|
-
const x1 = vertices[(i + 2) %
|
|
585
|
+
const x1 = vertices[(i + 2) % n];
|
|
588
586
|
const y1 = vertices[(i + 3) % n];
|
|
589
587
|
area += x0 * y1 - x1 * y0;
|
|
590
588
|
}
|
|
@@ -594,7 +592,7 @@ function getDirectedArea(vertices) {
|
|
|
594
592
|
function cross(ax, ay, bx, by, cx, cy) {
|
|
595
593
|
return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
|
|
596
594
|
}
|
|
597
|
-
function windingNumber(px, py, polygon) {
|
|
595
|
+
function windingNumber$1(px, py, polygon) {
|
|
598
596
|
const polygonLen = polygon.length;
|
|
599
597
|
let wn = 0;
|
|
600
598
|
for (let i = 0, j = polygonLen - 2; i < polygonLen; j = i, i += 2) {
|
|
@@ -699,7 +697,7 @@ function nonzeroFillRule(paths) {
|
|
|
699
697
|
const wnList = [];
|
|
700
698
|
for (let p = 0, pLen = testPoints.length; p < pLen; p++) {
|
|
701
699
|
const [x, y] = testPoints[p];
|
|
702
|
-
const winding = windingNumber(x, y, paths[j]);
|
|
700
|
+
const winding = windingNumber$1(x, y, paths[j]);
|
|
703
701
|
wnMap[winding] = (wnMap[winding] ?? 0) + 1;
|
|
704
702
|
wnList.push(winding);
|
|
705
703
|
}
|
|
@@ -722,6 +720,120 @@ function nonzeroFillRule(paths) {
|
|
|
722
720
|
return results;
|
|
723
721
|
}
|
|
724
722
|
|
|
723
|
+
function isLeft(ax, ay, bx, by, px, py) {
|
|
724
|
+
return (bx - ax) * (py - ay) - (px - ax) * (by - ay);
|
|
725
|
+
}
|
|
726
|
+
function windingNumber(px, py, vertices) {
|
|
727
|
+
const len = vertices.length;
|
|
728
|
+
let wn = 0;
|
|
729
|
+
for (let i = 0; i < len; i += 2) {
|
|
730
|
+
const ax = vertices[i];
|
|
731
|
+
const ay = vertices[i + 1];
|
|
732
|
+
const k = (i + 2) % len;
|
|
733
|
+
const bx = vertices[k];
|
|
734
|
+
const by = vertices[k + 1];
|
|
735
|
+
if (ay <= py) {
|
|
736
|
+
if (by > py && isLeft(ax, ay, bx, by, px, py) > 0) {
|
|
737
|
+
wn++;
|
|
738
|
+
}
|
|
739
|
+
} else {
|
|
740
|
+
if (by <= py && isLeft(ax, ay, bx, by, px, py) < 0) {
|
|
741
|
+
wn--;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return wn;
|
|
746
|
+
}
|
|
747
|
+
function crossingNumber(px, py, vertices) {
|
|
748
|
+
const len = vertices.length;
|
|
749
|
+
let cn = 0;
|
|
750
|
+
for (let i = 0; i < len; i += 2) {
|
|
751
|
+
const ax = vertices[i];
|
|
752
|
+
const ay = vertices[i + 1];
|
|
753
|
+
const k = (i + 2) % len;
|
|
754
|
+
const bx = vertices[k];
|
|
755
|
+
const by = vertices[k + 1];
|
|
756
|
+
if (ay <= py && by > py || ay > py && by <= py) {
|
|
757
|
+
const t = (py - ay) / (by - ay);
|
|
758
|
+
if (px < ax + t * (bx - ax)) {
|
|
759
|
+
cn++;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return cn;
|
|
764
|
+
}
|
|
765
|
+
function segmentDistance(px, py, ax, ay, bx, by) {
|
|
766
|
+
const dx = bx - ax;
|
|
767
|
+
const dy = by - ay;
|
|
768
|
+
const lenSq = dx * dx + dy * dy;
|
|
769
|
+
let t = lenSq === 0 ? 0 : ((px - ax) * dx + (py - ay) * dy) / lenSq;
|
|
770
|
+
if (t < 0) {
|
|
771
|
+
t = 0;
|
|
772
|
+
} else if (t > 1) {
|
|
773
|
+
t = 1;
|
|
774
|
+
}
|
|
775
|
+
const cx = ax + t * dx;
|
|
776
|
+
const cy = ay + t * dy;
|
|
777
|
+
return Math.hypot(px - cx, py - cy);
|
|
778
|
+
}
|
|
779
|
+
function pointInPolygon(point, vertices, fillRule = "nonzero") {
|
|
780
|
+
if (vertices.length < 6) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
if (fillRule === "evenodd") {
|
|
784
|
+
return (crossingNumber(point.x, point.y, vertices) & 1) === 1;
|
|
785
|
+
}
|
|
786
|
+
return windingNumber(point.x, point.y, vertices) !== 0;
|
|
787
|
+
}
|
|
788
|
+
function pointInPolygons(point, polygons, fillRule = "nonzero") {
|
|
789
|
+
const { x, y } = point;
|
|
790
|
+
if (fillRule === "evenodd") {
|
|
791
|
+
let cn = 0;
|
|
792
|
+
for (let i = 0, len = polygons.length; i < len; i++) {
|
|
793
|
+
const ring = polygons[i];
|
|
794
|
+
if (ring.length >= 6) {
|
|
795
|
+
cn += crossingNumber(x, y, ring);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return (cn & 1) === 1;
|
|
799
|
+
}
|
|
800
|
+
let wn = 0;
|
|
801
|
+
for (let i = 0, len = polygons.length; i < len; i++) {
|
|
802
|
+
const ring = polygons[i];
|
|
803
|
+
if (ring.length >= 6) {
|
|
804
|
+
wn += windingNumber(x, y, ring);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return wn !== 0;
|
|
808
|
+
}
|
|
809
|
+
function pointToSegmentDistance(point, a, b) {
|
|
810
|
+
return segmentDistance(point.x, point.y, a.x, a.y, b.x, b.y);
|
|
811
|
+
}
|
|
812
|
+
function pointToPolylineDistance(point, vertices, closed = false) {
|
|
813
|
+
const len = vertices.length;
|
|
814
|
+
if (len < 2) {
|
|
815
|
+
return Infinity;
|
|
816
|
+
}
|
|
817
|
+
const { x: px, y: py } = point;
|
|
818
|
+
if (len === 2) {
|
|
819
|
+
return Math.hypot(px - vertices[0], py - vertices[1]);
|
|
820
|
+
}
|
|
821
|
+
let min = Infinity;
|
|
822
|
+
for (let i = 0; i < len - 2; i += 2) {
|
|
823
|
+
const d = segmentDistance(px, py, vertices[i], vertices[i + 1], vertices[i + 2], vertices[i + 3]);
|
|
824
|
+
if (d < min) {
|
|
825
|
+
min = d;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (closed && len >= 6) {
|
|
829
|
+
const d = segmentDistance(px, py, vertices[len - 2], vertices[len - 1], vertices[0], vertices[1]);
|
|
830
|
+
if (d < min) {
|
|
831
|
+
min = d;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return min;
|
|
835
|
+
}
|
|
836
|
+
|
|
725
837
|
function quadraticBezierP0(t, p) {
|
|
726
838
|
const k = 1 - t;
|
|
727
839
|
return k * k * p;
|
|
@@ -1735,8 +1847,14 @@ function getReflection(a, b) {
|
|
|
1735
1847
|
function svgPathCommandsAddToPath2D(commands, path) {
|
|
1736
1848
|
const current = new Vector2();
|
|
1737
1849
|
const control = new Vector2();
|
|
1850
|
+
let prevType = "";
|
|
1738
1851
|
for (let i = 0, l = commands.length; i < l; i++) {
|
|
1739
1852
|
const cmd = commands[i];
|
|
1853
|
+
if ((cmd.type === "s" || cmd.type === "S") && !"CcSs".includes(prevType)) {
|
|
1854
|
+
control.copyFrom(current);
|
|
1855
|
+
} else if ((cmd.type === "t" || cmd.type === "T") && !"QqTt".includes(prevType)) {
|
|
1856
|
+
control.copyFrom(current);
|
|
1857
|
+
}
|
|
1740
1858
|
if (cmd.type === "m" || cmd.type === "M") {
|
|
1741
1859
|
if (cmd.type === "m") {
|
|
1742
1860
|
current.add(cmd);
|
|
@@ -1895,6 +2013,7 @@ function svgPathCommandsAddToPath2D(commands, path) {
|
|
|
1895
2013
|
} else {
|
|
1896
2014
|
console.warn("Unsupported commands", cmd);
|
|
1897
2015
|
}
|
|
2016
|
+
prevType = cmd.type;
|
|
1898
2017
|
}
|
|
1899
2018
|
}
|
|
1900
2019
|
|
|
@@ -2319,10 +2438,14 @@ function parsePolylineNode(node) {
|
|
|
2319
2438
|
function parseRectNode(node) {
|
|
2320
2439
|
const x = parseFloatWithUnits(node.getAttribute("x") || 0);
|
|
2321
2440
|
const y = parseFloatWithUnits(node.getAttribute("y") || 0);
|
|
2322
|
-
const
|
|
2323
|
-
const
|
|
2441
|
+
const rxAttr = node.getAttribute("rx");
|
|
2442
|
+
const ryAttr = node.getAttribute("ry");
|
|
2443
|
+
let rx = parseFloatWithUnits(rxAttr ?? ryAttr ?? 0);
|
|
2444
|
+
let ry = parseFloatWithUnits(ryAttr ?? rxAttr ?? 0);
|
|
2324
2445
|
const w = parseFloatWithUnits(node.getAttribute("width"));
|
|
2325
2446
|
const h = parseFloatWithUnits(node.getAttribute("height"));
|
|
2447
|
+
rx = Math.max(0, Math.min(rx, w / 2));
|
|
2448
|
+
ry = Math.max(0, Math.min(ry, h / 2));
|
|
2326
2449
|
const bci = 1 - 0.551915024494;
|
|
2327
2450
|
const path = new Path2D();
|
|
2328
2451
|
path.moveTo(x + rx, y);
|
|
@@ -2698,6 +2821,40 @@ class Curve {
|
|
|
2698
2821
|
const { min, max } = this.getMinMax();
|
|
2699
2822
|
return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
2700
2823
|
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Test whether a point lies inside the area enclosed by this curve.
|
|
2826
|
+
*
|
|
2827
|
+
* The curve is sampled via {@link getAdaptiveVertices} into a single implicitly closed
|
|
2828
|
+
* ring. This is purely geometric (it ignores any `fill`/`stroke` style), mirroring
|
|
2829
|
+
* `CanvasRenderingContext2D.isPointInPath`.
|
|
2830
|
+
*
|
|
2831
|
+
* Composites that hold multiple sub-paths (e.g. {@link Path2D}) override this so holes
|
|
2832
|
+
* are honored — a single `Curve` is always one ring.
|
|
2833
|
+
*/
|
|
2834
|
+
isPointInFill(point, options = {}) {
|
|
2835
|
+
return pointInPolygon(point, this.getAdaptiveVertices(), options.fillRule);
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Test whether a point lies on this curve's stroke, i.e. within `strokeWidth / 2 + tolerance`
|
|
2839
|
+
* of the sampled outline. The point must be in the same coordinate space as the curve.
|
|
2840
|
+
*
|
|
2841
|
+
* Options: `strokeWidth` (path units, default `1`), `tolerance` (extra hit slack in path
|
|
2842
|
+
* units, default `0` — useful for thin strokes; no coordinate scaling is assumed, so convert
|
|
2843
|
+
* pixel tolerance to path units upstream if your path is normalized), and `closed` (whether
|
|
2844
|
+
* to include the closing edge from the last vertex back to the first).
|
|
2845
|
+
*/
|
|
2846
|
+
isPointInStroke(point, options = {}) {
|
|
2847
|
+
const { strokeWidth = 1, tolerance = 0, closed = false } = options;
|
|
2848
|
+
const distance = pointToPolylineDistance(point, this.getAdaptiveVertices(), closed);
|
|
2849
|
+
return distance <= strokeWidth / 2 + tolerance;
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Concise PathKit-style fill containment test: `contains(x, y)` is shorthand for
|
|
2853
|
+
* {@link isPointInFill} with a `{ x, y }` point.
|
|
2854
|
+
*/
|
|
2855
|
+
contains(x, y, options = {}) {
|
|
2856
|
+
return this.isPointInFill({ x, y }, options);
|
|
2857
|
+
}
|
|
2701
2858
|
getFillVertices(_options) {
|
|
2702
2859
|
return this.getAdaptiveVertices();
|
|
2703
2860
|
}
|
|
@@ -3108,6 +3265,8 @@ function eigenDecomposition(A, B, C) {
|
|
|
3108
3265
|
rt2 = A * t * C - B * t * B;
|
|
3109
3266
|
} else if (sm < 0) {
|
|
3110
3267
|
rt2 = 0.5 * (sm - rt);
|
|
3268
|
+
t = 1 / rt2;
|
|
3269
|
+
rt1 = A * t * C - B * t * B;
|
|
3111
3270
|
} else {
|
|
3112
3271
|
rt1 = 0.5 * rt;
|
|
3113
3272
|
rt2 = -0.5 * rt;
|
|
@@ -3273,20 +3432,26 @@ class CompositeCurve extends Curve {
|
|
|
3273
3432
|
getPoint(t, output = new Vector2()) {
|
|
3274
3433
|
const d = t * this.getLength();
|
|
3275
3434
|
const lengths = this.getLengths();
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3435
|
+
const n = lengths.length;
|
|
3436
|
+
if (n === 0)
|
|
3437
|
+
return output;
|
|
3438
|
+
let lo = 0;
|
|
3439
|
+
let hi = n - 1;
|
|
3440
|
+
while (lo < hi) {
|
|
3441
|
+
const mid = lo + hi >>> 1;
|
|
3442
|
+
if (lengths[mid] < d) {
|
|
3443
|
+
lo = mid + 1;
|
|
3444
|
+
} else {
|
|
3445
|
+
hi = mid;
|
|
3286
3446
|
}
|
|
3287
|
-
i++;
|
|
3288
3447
|
}
|
|
3289
|
-
|
|
3448
|
+
const diff = lengths[lo] - d;
|
|
3449
|
+
const curve = this.curves[lo];
|
|
3450
|
+
const length = curve.getLength();
|
|
3451
|
+
return curve.getPointAt(
|
|
3452
|
+
length === 0 ? 0 : 1 - diff / length,
|
|
3453
|
+
output
|
|
3454
|
+
);
|
|
3290
3455
|
}
|
|
3291
3456
|
getLengths() {
|
|
3292
3457
|
if (this._lengths.length !== this.curves.length) {
|
|
@@ -3435,6 +3600,12 @@ class CubicBezierCurve extends Curve {
|
|
|
3435
3600
|
return [this.p1, this.cp1, this.cp2, this.p2];
|
|
3436
3601
|
}
|
|
3437
3602
|
_solveQuadratic(a, b, c) {
|
|
3603
|
+
if (Math.abs(a) < 1e-12) {
|
|
3604
|
+
if (Math.abs(b) < 1e-12)
|
|
3605
|
+
return [];
|
|
3606
|
+
const t = -c / b;
|
|
3607
|
+
return t >= 0 && t <= 1 ? [t] : [];
|
|
3608
|
+
}
|
|
3438
3609
|
const discriminant = b * b - 4 * a * c;
|
|
3439
3610
|
if (discriminant < 0)
|
|
3440
3611
|
return [];
|
|
@@ -3446,30 +3617,23 @@ class CubicBezierCurve extends Curve {
|
|
|
3446
3617
|
getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
|
|
3447
3618
|
const { p1, cp1, cp2, p2 } = this;
|
|
3448
3619
|
const dxRoots = this._solveQuadratic(
|
|
3449
|
-
3 * (cp1.x -
|
|
3450
|
-
6 * (
|
|
3451
|
-
3 * (
|
|
3620
|
+
3 * (-p1.x + 3 * cp1.x - 3 * cp2.x + p2.x),
|
|
3621
|
+
6 * (p1.x - 2 * cp1.x + cp2.x),
|
|
3622
|
+
3 * (cp1.x - p1.x)
|
|
3452
3623
|
);
|
|
3453
3624
|
const dyRoots = this._solveQuadratic(
|
|
3454
|
-
3 * (cp1.y -
|
|
3455
|
-
6 * (
|
|
3456
|
-
3 * (
|
|
3625
|
+
3 * (-p1.y + 3 * cp1.y - 3 * cp2.y + p2.y),
|
|
3626
|
+
6 * (p1.y - 2 * cp1.y + cp2.y),
|
|
3627
|
+
3 * (cp1.y - p1.y)
|
|
3457
3628
|
);
|
|
3458
3629
|
const tValues = [0, 1, ...dxRoots, ...dyRoots];
|
|
3459
|
-
const
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
min.y = Math.min(min.y, point.y);
|
|
3467
|
-
max.x = Math.max(max.x, point.x);
|
|
3468
|
-
max.y = Math.max(max.y, point.y);
|
|
3469
|
-
}
|
|
3470
|
-
}
|
|
3471
|
-
};
|
|
3472
|
-
samplePoints(tValues, 10);
|
|
3630
|
+
for (const t of tValues) {
|
|
3631
|
+
const point = this.getPoint(t);
|
|
3632
|
+
min.x = Math.min(min.x, point.x);
|
|
3633
|
+
min.y = Math.min(min.y, point.y);
|
|
3634
|
+
max.x = Math.max(max.x, point.x);
|
|
3635
|
+
max.y = Math.max(max.y, point.y);
|
|
3636
|
+
}
|
|
3473
3637
|
return { min: min.finite(), max: max.finite() };
|
|
3474
3638
|
}
|
|
3475
3639
|
toCommands() {
|
|
@@ -3522,11 +3686,11 @@ class EllipseCurve extends RoundCurve {
|
|
|
3522
3686
|
}
|
|
3523
3687
|
}
|
|
3524
3688
|
|
|
3525
|
-
class
|
|
3689
|
+
class PolygonCurve extends CompositeCurve {
|
|
3526
3690
|
//
|
|
3527
3691
|
}
|
|
3528
3692
|
|
|
3529
|
-
class
|
|
3693
|
+
class EquilateralPolygonCurve extends PolygonCurve {
|
|
3530
3694
|
constructor(cx = 0, cy = 0, radius = 1, sideCount = 3) {
|
|
3531
3695
|
super();
|
|
3532
3696
|
this.cx = cx;
|
|
@@ -3609,14 +3773,25 @@ class QuadraticBezierCurve extends Curve {
|
|
|
3609
3773
|
}
|
|
3610
3774
|
getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
|
|
3611
3775
|
const { p1, cp, p2 } = this;
|
|
3612
|
-
const
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3776
|
+
const extrema = (a, b, c) => {
|
|
3777
|
+
const denom = a - 2 * b + c;
|
|
3778
|
+
if (Math.abs(denom) < 1e-12)
|
|
3779
|
+
return null;
|
|
3780
|
+
const t = (a - b) / denom;
|
|
3781
|
+
return t > 0 && t < 1 ? t : null;
|
|
3782
|
+
};
|
|
3783
|
+
const tx = extrema(p1.x, cp.x, p2.x);
|
|
3784
|
+
const ty = extrema(p1.y, cp.y, p2.y);
|
|
3785
|
+
const xs = [p1.x, p2.x];
|
|
3786
|
+
const ys = [p1.y, p2.y];
|
|
3787
|
+
if (tx !== null)
|
|
3788
|
+
xs.push(quadraticBezier(tx, p1.x, cp.x, p2.x));
|
|
3789
|
+
if (ty !== null)
|
|
3790
|
+
ys.push(quadraticBezier(ty, p1.y, cp.y, p2.y));
|
|
3791
|
+
min.x = Math.min(min.x, ...xs);
|
|
3792
|
+
min.y = Math.min(min.y, ...ys);
|
|
3793
|
+
max.x = Math.max(max.x, ...xs);
|
|
3794
|
+
max.y = Math.max(max.y, ...ys);
|
|
3620
3795
|
return { min: min.finite(), max: max.finite() };
|
|
3621
3796
|
}
|
|
3622
3797
|
toCommands() {
|
|
@@ -3641,7 +3816,7 @@ class QuadraticBezierCurve extends Curve {
|
|
|
3641
3816
|
}
|
|
3642
3817
|
}
|
|
3643
3818
|
|
|
3644
|
-
class RectangleCurve extends
|
|
3819
|
+
class RectangleCurve extends PolygonCurve {
|
|
3645
3820
|
constructor(x = 0, y = 0, width = 0, height = 0) {
|
|
3646
3821
|
super();
|
|
3647
3822
|
this.x = x;
|
|
@@ -3819,6 +3994,17 @@ class CurvePath extends CompositeCurve {
|
|
|
3819
3994
|
super.getFillVertices(options)
|
|
3820
3995
|
);
|
|
3821
3996
|
}
|
|
3997
|
+
/**
|
|
3998
|
+
* Same as {@link Curve.isPointInStroke}, but `closed` defaults to this sub-path's actual
|
|
3999
|
+
* closed-ness: explicitly `autoClose`, or geometrically closed (first vertex === last).
|
|
4000
|
+
*/
|
|
4001
|
+
isPointInStroke(point, options = {}) {
|
|
4002
|
+
const { strokeWidth = 1, tolerance = 0 } = options;
|
|
4003
|
+
const vertices = this.getAdaptiveVertices();
|
|
4004
|
+
const len = vertices.length;
|
|
4005
|
+
const closed = options.closed ?? (this.autoClose || len >= 6 && vertices[0] === vertices[len - 2] && vertices[1] === vertices[len - 1]);
|
|
4006
|
+
return pointToPolylineDistance(point, vertices, closed) <= strokeWidth / 2 + tolerance;
|
|
4007
|
+
}
|
|
3822
4008
|
_setCurrentPoint(point) {
|
|
3823
4009
|
this.currentPoint = new Vector2(point.x, point.y);
|
|
3824
4010
|
if (!this.startPoint) {
|
|
@@ -4049,13 +4235,11 @@ class Path2D extends CompositeCurve {
|
|
|
4049
4235
|
return this;
|
|
4050
4236
|
}
|
|
4051
4237
|
moveTo(x, y) {
|
|
4052
|
-
if (
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
this.curves.push(this.currentCurve);
|
|
4056
|
-
}
|
|
4057
|
-
this.currentCurve.moveTo(x, y);
|
|
4238
|
+
if (this.currentCurve.curves.length) {
|
|
4239
|
+
this.currentCurve = new CurvePath();
|
|
4240
|
+
this.curves.push(this.currentCurve);
|
|
4058
4241
|
}
|
|
4242
|
+
this.currentCurve.moveTo(x, y);
|
|
4059
4243
|
return this;
|
|
4060
4244
|
}
|
|
4061
4245
|
lineTo(x, y) {
|
|
@@ -4184,6 +4368,40 @@ class Path2D extends CompositeCurve {
|
|
|
4184
4368
|
});
|
|
4185
4369
|
return this;
|
|
4186
4370
|
}
|
|
4371
|
+
/**
|
|
4372
|
+
* Test whether a point lies inside the filled area of this path.
|
|
4373
|
+
*
|
|
4374
|
+
* Each sub-path ({@link CurvePath}) is sampled into its own ring and all rings are
|
|
4375
|
+
* evaluated together via {@link pointInPolygons}, so holes (donut / hollow shapes) are
|
|
4376
|
+
* honored. This is purely geometric and ignores `style.fill` — for the `fill: 'none'`
|
|
4377
|
+
* fallback, gate the call upstream (see {@link Path2DSet.hitTest}).
|
|
4378
|
+
*
|
|
4379
|
+
* Defaults `fillRule` to `style.fillRule`, then `'nonzero'` (matching SVG/Canvas).
|
|
4380
|
+
*/
|
|
4381
|
+
isPointInFill(point, options = {}) {
|
|
4382
|
+
const fillRule = options.fillRule ?? this.style.fillRule ?? "nonzero";
|
|
4383
|
+
return pointInPolygons(
|
|
4384
|
+
point,
|
|
4385
|
+
this.curves.map((curve) => curve.getAdaptiveVertices()),
|
|
4386
|
+
fillRule
|
|
4387
|
+
);
|
|
4388
|
+
}
|
|
4389
|
+
/**
|
|
4390
|
+
* Test whether a point lies on this path's stroke. A hit on any sub-path counts.
|
|
4391
|
+
*
|
|
4392
|
+
* Defaults `strokeWidth` to this path's own {@link strokeWidth} (which is `0` when
|
|
4393
|
+
* `style.stroke` is `'none'`). Each sub-path infers its own closed-ness unless `closed`
|
|
4394
|
+
* is given explicitly.
|
|
4395
|
+
*/
|
|
4396
|
+
isPointInStroke(point, options = {}) {
|
|
4397
|
+
const strokeWidth = options.strokeWidth ?? this.strokeWidth;
|
|
4398
|
+
const { tolerance = 0, closed } = options;
|
|
4399
|
+
return this.curves.some((curve) => curve.isPointInStroke(point, {
|
|
4400
|
+
strokeWidth,
|
|
4401
|
+
tolerance,
|
|
4402
|
+
closed
|
|
4403
|
+
}));
|
|
4404
|
+
}
|
|
4187
4405
|
getMinMax(min = Vector2.MAX, max = Vector2.MIN, withStyle = true) {
|
|
4188
4406
|
const strokeWidth = this.strokeWidth;
|
|
4189
4407
|
this.curves.forEach((curve) => {
|
|
@@ -4361,6 +4579,44 @@ class Path2DSet {
|
|
|
4361
4579
|
this.paths = paths;
|
|
4362
4580
|
this.viewBox = viewBox;
|
|
4363
4581
|
}
|
|
4582
|
+
/**
|
|
4583
|
+
* Test whether a point lies inside the filled area of any path in this set.
|
|
4584
|
+
* Purely geometric (ignores `fill: 'none'`); use {@link hitTest} for style-aware hits.
|
|
4585
|
+
*/
|
|
4586
|
+
isPointInFill(point, options = {}) {
|
|
4587
|
+
return this.paths.some((path) => path.isPointInFill(point, options));
|
|
4588
|
+
}
|
|
4589
|
+
/**
|
|
4590
|
+
* Concise PathKit-style fill containment test across the whole set; shorthand for
|
|
4591
|
+
* {@link isPointInFill} with a `{ x, y }` point.
|
|
4592
|
+
*/
|
|
4593
|
+
contains(x, y, options = {}) {
|
|
4594
|
+
return this.isPointInFill({ x, y }, options);
|
|
4595
|
+
}
|
|
4596
|
+
/**
|
|
4597
|
+
* Find the topmost path hit by a point, or `undefined` if none.
|
|
4598
|
+
*
|
|
4599
|
+
* Paths are tested top-to-bottom (last drawn first). For each path a fill hit is checked
|
|
4600
|
+
* first (skipped when `style.fill` is `'none'`), then — if `stroke` is enabled — a stroke
|
|
4601
|
+
* hit (skipped when `style.stroke` is `'none'`). This honors the "fill: none falls back to
|
|
4602
|
+
* stroke" rule; the coordinate space of `point` must match the paths (no scaling assumed).
|
|
4603
|
+
*
|
|
4604
|
+
* Options: `stroke` (also test strokes, default `true`), `tolerance` (extra stroke hit slack
|
|
4605
|
+
* in path units, default `0`), and `fillRule` (overrides each path's own fill rule).
|
|
4606
|
+
*/
|
|
4607
|
+
hitTest(point, options = {}) {
|
|
4608
|
+
const { stroke = true, tolerance, fillRule } = options;
|
|
4609
|
+
for (let i = this.paths.length - 1; i >= 0; i--) {
|
|
4610
|
+
const path = this.paths[i];
|
|
4611
|
+
if ((path.style.fill ?? "#000") !== "none" && path.isPointInFill(point, { fillRule })) {
|
|
4612
|
+
return path;
|
|
4613
|
+
}
|
|
4614
|
+
if (stroke && (path.style.stroke ?? "none") !== "none" && path.isPointInStroke(point, { tolerance })) {
|
|
4615
|
+
return path;
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
return void 0;
|
|
4619
|
+
}
|
|
4364
4620
|
getBoundingBox(withStyle = true) {
|
|
4365
4621
|
if (!this.paths.length) {
|
|
4366
4622
|
return void 0;
|
|
@@ -4516,4 +4772,4 @@ function applyFFD(point, grid, width = grid.width, height = grid.height) {
|
|
|
4516
4772
|
point.set(x, y);
|
|
4517
4773
|
}
|
|
4518
4774
|
|
|
4519
|
-
export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, EllipseCurve,
|
|
4775
|
+
export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, EllipseCurve, EquilateralPolygonCurve, FFDControlGrid, LineCurve, PI, PI_2, Path2D, Path2DSet, PolygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, Transform2D, Vector2, applyFFD, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, getDirectedArea, getIntersectionPoint, nonzeroFillRule, parseArcCommand, parseCssArg, parseCssArgs, parseCssFunctions, parsePathDataArgs, pointInPolygon, pointInPolygons, pointToPolylineDistance, pointToSegmentDistance, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDom, svgToPath2DSet, toKebabCase };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modern-path2d",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.1",
|
|
5
5
|
"packageManager": "pnpm@9.15.1",
|
|
6
6
|
"description": "A Path2D library, fully compatible with Web Path2D, with additional support for triangulate、animation、deformation etc.",
|
|
7
7
|
"author": "wxm",
|