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.cjs
CHANGED
|
@@ -189,11 +189,9 @@ class Vector2 {
|
|
|
189
189
|
return Math.sqrt(this.lengthSquared());
|
|
190
190
|
}
|
|
191
191
|
scale(sx, sy = sx, origin = { x: 0, y: 0 }) {
|
|
192
|
-
const x = sx < 0 ? origin.x - this._x + origin.x : this._x;
|
|
193
|
-
const y = sy < 0 ? origin.y - this._y + origin.y : this._y;
|
|
194
192
|
return this.set(
|
|
195
|
-
x *
|
|
196
|
-
y *
|
|
193
|
+
origin.x + (this._x - origin.x) * sx,
|
|
194
|
+
origin.y + (this._y - origin.y) * sy
|
|
197
195
|
);
|
|
198
196
|
}
|
|
199
197
|
skew(ax, ay = 0, origin = { x: 0, y: 0 }) {
|
|
@@ -590,7 +588,7 @@ function getDirectedArea(vertices) {
|
|
|
590
588
|
for (let i = 0; i < n; i += 2) {
|
|
591
589
|
const x0 = vertices[i];
|
|
592
590
|
const y0 = vertices[i + 1];
|
|
593
|
-
const x1 = vertices[(i + 2) %
|
|
591
|
+
const x1 = vertices[(i + 2) % n];
|
|
594
592
|
const y1 = vertices[(i + 3) % n];
|
|
595
593
|
area += x0 * y1 - x1 * y0;
|
|
596
594
|
}
|
|
@@ -600,7 +598,7 @@ function getDirectedArea(vertices) {
|
|
|
600
598
|
function cross(ax, ay, bx, by, cx, cy) {
|
|
601
599
|
return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
|
|
602
600
|
}
|
|
603
|
-
function windingNumber(px, py, polygon) {
|
|
601
|
+
function windingNumber$1(px, py, polygon) {
|
|
604
602
|
const polygonLen = polygon.length;
|
|
605
603
|
let wn = 0;
|
|
606
604
|
for (let i = 0, j = polygonLen - 2; i < polygonLen; j = i, i += 2) {
|
|
@@ -705,7 +703,7 @@ function nonzeroFillRule(paths) {
|
|
|
705
703
|
const wnList = [];
|
|
706
704
|
for (let p = 0, pLen = testPoints.length; p < pLen; p++) {
|
|
707
705
|
const [x, y] = testPoints[p];
|
|
708
|
-
const winding = windingNumber(x, y, paths[j]);
|
|
706
|
+
const winding = windingNumber$1(x, y, paths[j]);
|
|
709
707
|
wnMap[winding] = (wnMap[winding] ?? 0) + 1;
|
|
710
708
|
wnList.push(winding);
|
|
711
709
|
}
|
|
@@ -728,6 +726,120 @@ function nonzeroFillRule(paths) {
|
|
|
728
726
|
return results;
|
|
729
727
|
}
|
|
730
728
|
|
|
729
|
+
function isLeft(ax, ay, bx, by, px, py) {
|
|
730
|
+
return (bx - ax) * (py - ay) - (px - ax) * (by - ay);
|
|
731
|
+
}
|
|
732
|
+
function windingNumber(px, py, vertices) {
|
|
733
|
+
const len = vertices.length;
|
|
734
|
+
let wn = 0;
|
|
735
|
+
for (let i = 0; i < len; i += 2) {
|
|
736
|
+
const ax = vertices[i];
|
|
737
|
+
const ay = vertices[i + 1];
|
|
738
|
+
const k = (i + 2) % len;
|
|
739
|
+
const bx = vertices[k];
|
|
740
|
+
const by = vertices[k + 1];
|
|
741
|
+
if (ay <= py) {
|
|
742
|
+
if (by > py && isLeft(ax, ay, bx, by, px, py) > 0) {
|
|
743
|
+
wn++;
|
|
744
|
+
}
|
|
745
|
+
} else {
|
|
746
|
+
if (by <= py && isLeft(ax, ay, bx, by, px, py) < 0) {
|
|
747
|
+
wn--;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return wn;
|
|
752
|
+
}
|
|
753
|
+
function crossingNumber(px, py, vertices) {
|
|
754
|
+
const len = vertices.length;
|
|
755
|
+
let cn = 0;
|
|
756
|
+
for (let i = 0; i < len; i += 2) {
|
|
757
|
+
const ax = vertices[i];
|
|
758
|
+
const ay = vertices[i + 1];
|
|
759
|
+
const k = (i + 2) % len;
|
|
760
|
+
const bx = vertices[k];
|
|
761
|
+
const by = vertices[k + 1];
|
|
762
|
+
if (ay <= py && by > py || ay > py && by <= py) {
|
|
763
|
+
const t = (py - ay) / (by - ay);
|
|
764
|
+
if (px < ax + t * (bx - ax)) {
|
|
765
|
+
cn++;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return cn;
|
|
770
|
+
}
|
|
771
|
+
function segmentDistance(px, py, ax, ay, bx, by) {
|
|
772
|
+
const dx = bx - ax;
|
|
773
|
+
const dy = by - ay;
|
|
774
|
+
const lenSq = dx * dx + dy * dy;
|
|
775
|
+
let t = lenSq === 0 ? 0 : ((px - ax) * dx + (py - ay) * dy) / lenSq;
|
|
776
|
+
if (t < 0) {
|
|
777
|
+
t = 0;
|
|
778
|
+
} else if (t > 1) {
|
|
779
|
+
t = 1;
|
|
780
|
+
}
|
|
781
|
+
const cx = ax + t * dx;
|
|
782
|
+
const cy = ay + t * dy;
|
|
783
|
+
return Math.hypot(px - cx, py - cy);
|
|
784
|
+
}
|
|
785
|
+
function pointInPolygon(point, vertices, fillRule = "nonzero") {
|
|
786
|
+
if (vertices.length < 6) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
if (fillRule === "evenodd") {
|
|
790
|
+
return (crossingNumber(point.x, point.y, vertices) & 1) === 1;
|
|
791
|
+
}
|
|
792
|
+
return windingNumber(point.x, point.y, vertices) !== 0;
|
|
793
|
+
}
|
|
794
|
+
function pointInPolygons(point, polygons, fillRule = "nonzero") {
|
|
795
|
+
const { x, y } = point;
|
|
796
|
+
if (fillRule === "evenodd") {
|
|
797
|
+
let cn = 0;
|
|
798
|
+
for (let i = 0, len = polygons.length; i < len; i++) {
|
|
799
|
+
const ring = polygons[i];
|
|
800
|
+
if (ring.length >= 6) {
|
|
801
|
+
cn += crossingNumber(x, y, ring);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return (cn & 1) === 1;
|
|
805
|
+
}
|
|
806
|
+
let wn = 0;
|
|
807
|
+
for (let i = 0, len = polygons.length; i < len; i++) {
|
|
808
|
+
const ring = polygons[i];
|
|
809
|
+
if (ring.length >= 6) {
|
|
810
|
+
wn += windingNumber(x, y, ring);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return wn !== 0;
|
|
814
|
+
}
|
|
815
|
+
function pointToSegmentDistance(point, a, b) {
|
|
816
|
+
return segmentDistance(point.x, point.y, a.x, a.y, b.x, b.y);
|
|
817
|
+
}
|
|
818
|
+
function pointToPolylineDistance(point, vertices, closed = false) {
|
|
819
|
+
const len = vertices.length;
|
|
820
|
+
if (len < 2) {
|
|
821
|
+
return Infinity;
|
|
822
|
+
}
|
|
823
|
+
const { x: px, y: py } = point;
|
|
824
|
+
if (len === 2) {
|
|
825
|
+
return Math.hypot(px - vertices[0], py - vertices[1]);
|
|
826
|
+
}
|
|
827
|
+
let min = Infinity;
|
|
828
|
+
for (let i = 0; i < len - 2; i += 2) {
|
|
829
|
+
const d = segmentDistance(px, py, vertices[i], vertices[i + 1], vertices[i + 2], vertices[i + 3]);
|
|
830
|
+
if (d < min) {
|
|
831
|
+
min = d;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (closed && len >= 6) {
|
|
835
|
+
const d = segmentDistance(px, py, vertices[len - 2], vertices[len - 1], vertices[0], vertices[1]);
|
|
836
|
+
if (d < min) {
|
|
837
|
+
min = d;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return min;
|
|
841
|
+
}
|
|
842
|
+
|
|
731
843
|
function quadraticBezierP0(t, p) {
|
|
732
844
|
const k = 1 - t;
|
|
733
845
|
return k * k * p;
|
|
@@ -1741,8 +1853,14 @@ function getReflection(a, b) {
|
|
|
1741
1853
|
function svgPathCommandsAddToPath2D(commands, path) {
|
|
1742
1854
|
const current = new Vector2();
|
|
1743
1855
|
const control = new Vector2();
|
|
1856
|
+
let prevType = "";
|
|
1744
1857
|
for (let i = 0, l = commands.length; i < l; i++) {
|
|
1745
1858
|
const cmd = commands[i];
|
|
1859
|
+
if ((cmd.type === "s" || cmd.type === "S") && !"CcSs".includes(prevType)) {
|
|
1860
|
+
control.copyFrom(current);
|
|
1861
|
+
} else if ((cmd.type === "t" || cmd.type === "T") && !"QqTt".includes(prevType)) {
|
|
1862
|
+
control.copyFrom(current);
|
|
1863
|
+
}
|
|
1746
1864
|
if (cmd.type === "m" || cmd.type === "M") {
|
|
1747
1865
|
if (cmd.type === "m") {
|
|
1748
1866
|
current.add(cmd);
|
|
@@ -1901,6 +2019,7 @@ function svgPathCommandsAddToPath2D(commands, path) {
|
|
|
1901
2019
|
} else {
|
|
1902
2020
|
console.warn("Unsupported commands", cmd);
|
|
1903
2021
|
}
|
|
2022
|
+
prevType = cmd.type;
|
|
1904
2023
|
}
|
|
1905
2024
|
}
|
|
1906
2025
|
|
|
@@ -2325,10 +2444,14 @@ function parsePolylineNode(node) {
|
|
|
2325
2444
|
function parseRectNode(node) {
|
|
2326
2445
|
const x = parseFloatWithUnits(node.getAttribute("x") || 0);
|
|
2327
2446
|
const y = parseFloatWithUnits(node.getAttribute("y") || 0);
|
|
2328
|
-
const
|
|
2329
|
-
const
|
|
2447
|
+
const rxAttr = node.getAttribute("rx");
|
|
2448
|
+
const ryAttr = node.getAttribute("ry");
|
|
2449
|
+
let rx = parseFloatWithUnits(rxAttr ?? ryAttr ?? 0);
|
|
2450
|
+
let ry = parseFloatWithUnits(ryAttr ?? rxAttr ?? 0);
|
|
2330
2451
|
const w = parseFloatWithUnits(node.getAttribute("width"));
|
|
2331
2452
|
const h = parseFloatWithUnits(node.getAttribute("height"));
|
|
2453
|
+
rx = Math.max(0, Math.min(rx, w / 2));
|
|
2454
|
+
ry = Math.max(0, Math.min(ry, h / 2));
|
|
2332
2455
|
const bci = 1 - 0.551915024494;
|
|
2333
2456
|
const path = new Path2D();
|
|
2334
2457
|
path.moveTo(x + rx, y);
|
|
@@ -2704,6 +2827,40 @@ class Curve {
|
|
|
2704
2827
|
const { min, max } = this.getMinMax();
|
|
2705
2828
|
return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
2706
2829
|
}
|
|
2830
|
+
/**
|
|
2831
|
+
* Test whether a point lies inside the area enclosed by this curve.
|
|
2832
|
+
*
|
|
2833
|
+
* The curve is sampled via {@link getAdaptiveVertices} into a single implicitly closed
|
|
2834
|
+
* ring. This is purely geometric (it ignores any `fill`/`stroke` style), mirroring
|
|
2835
|
+
* `CanvasRenderingContext2D.isPointInPath`.
|
|
2836
|
+
*
|
|
2837
|
+
* Composites that hold multiple sub-paths (e.g. {@link Path2D}) override this so holes
|
|
2838
|
+
* are honored — a single `Curve` is always one ring.
|
|
2839
|
+
*/
|
|
2840
|
+
isPointInFill(point, options = {}) {
|
|
2841
|
+
return pointInPolygon(point, this.getAdaptiveVertices(), options.fillRule);
|
|
2842
|
+
}
|
|
2843
|
+
/**
|
|
2844
|
+
* Test whether a point lies on this curve's stroke, i.e. within `strokeWidth / 2 + tolerance`
|
|
2845
|
+
* of the sampled outline. The point must be in the same coordinate space as the curve.
|
|
2846
|
+
*
|
|
2847
|
+
* Options: `strokeWidth` (path units, default `1`), `tolerance` (extra hit slack in path
|
|
2848
|
+
* units, default `0` — useful for thin strokes; no coordinate scaling is assumed, so convert
|
|
2849
|
+
* pixel tolerance to path units upstream if your path is normalized), and `closed` (whether
|
|
2850
|
+
* to include the closing edge from the last vertex back to the first).
|
|
2851
|
+
*/
|
|
2852
|
+
isPointInStroke(point, options = {}) {
|
|
2853
|
+
const { strokeWidth = 1, tolerance = 0, closed = false } = options;
|
|
2854
|
+
const distance = pointToPolylineDistance(point, this.getAdaptiveVertices(), closed);
|
|
2855
|
+
return distance <= strokeWidth / 2 + tolerance;
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Concise PathKit-style fill containment test: `contains(x, y)` is shorthand for
|
|
2859
|
+
* {@link isPointInFill} with a `{ x, y }` point.
|
|
2860
|
+
*/
|
|
2861
|
+
contains(x, y, options = {}) {
|
|
2862
|
+
return this.isPointInFill({ x, y }, options);
|
|
2863
|
+
}
|
|
2707
2864
|
getFillVertices(_options) {
|
|
2708
2865
|
return this.getAdaptiveVertices();
|
|
2709
2866
|
}
|
|
@@ -3114,6 +3271,8 @@ function eigenDecomposition(A, B, C) {
|
|
|
3114
3271
|
rt2 = A * t * C - B * t * B;
|
|
3115
3272
|
} else if (sm < 0) {
|
|
3116
3273
|
rt2 = 0.5 * (sm - rt);
|
|
3274
|
+
t = 1 / rt2;
|
|
3275
|
+
rt1 = A * t * C - B * t * B;
|
|
3117
3276
|
} else {
|
|
3118
3277
|
rt1 = 0.5 * rt;
|
|
3119
3278
|
rt2 = -0.5 * rt;
|
|
@@ -3279,20 +3438,26 @@ class CompositeCurve extends Curve {
|
|
|
3279
3438
|
getPoint(t, output = new Vector2()) {
|
|
3280
3439
|
const d = t * this.getLength();
|
|
3281
3440
|
const lengths = this.getLengths();
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3441
|
+
const n = lengths.length;
|
|
3442
|
+
if (n === 0)
|
|
3443
|
+
return output;
|
|
3444
|
+
let lo = 0;
|
|
3445
|
+
let hi = n - 1;
|
|
3446
|
+
while (lo < hi) {
|
|
3447
|
+
const mid = lo + hi >>> 1;
|
|
3448
|
+
if (lengths[mid] < d) {
|
|
3449
|
+
lo = mid + 1;
|
|
3450
|
+
} else {
|
|
3451
|
+
hi = mid;
|
|
3292
3452
|
}
|
|
3293
|
-
i++;
|
|
3294
3453
|
}
|
|
3295
|
-
|
|
3454
|
+
const diff = lengths[lo] - d;
|
|
3455
|
+
const curve = this.curves[lo];
|
|
3456
|
+
const length = curve.getLength();
|
|
3457
|
+
return curve.getPointAt(
|
|
3458
|
+
length === 0 ? 0 : 1 - diff / length,
|
|
3459
|
+
output
|
|
3460
|
+
);
|
|
3296
3461
|
}
|
|
3297
3462
|
getLengths() {
|
|
3298
3463
|
if (this._lengths.length !== this.curves.length) {
|
|
@@ -3441,6 +3606,12 @@ class CubicBezierCurve extends Curve {
|
|
|
3441
3606
|
return [this.p1, this.cp1, this.cp2, this.p2];
|
|
3442
3607
|
}
|
|
3443
3608
|
_solveQuadratic(a, b, c) {
|
|
3609
|
+
if (Math.abs(a) < 1e-12) {
|
|
3610
|
+
if (Math.abs(b) < 1e-12)
|
|
3611
|
+
return [];
|
|
3612
|
+
const t = -c / b;
|
|
3613
|
+
return t >= 0 && t <= 1 ? [t] : [];
|
|
3614
|
+
}
|
|
3444
3615
|
const discriminant = b * b - 4 * a * c;
|
|
3445
3616
|
if (discriminant < 0)
|
|
3446
3617
|
return [];
|
|
@@ -3452,30 +3623,23 @@ class CubicBezierCurve extends Curve {
|
|
|
3452
3623
|
getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
|
|
3453
3624
|
const { p1, cp1, cp2, p2 } = this;
|
|
3454
3625
|
const dxRoots = this._solveQuadratic(
|
|
3455
|
-
3 * (cp1.x -
|
|
3456
|
-
6 * (
|
|
3457
|
-
3 * (
|
|
3626
|
+
3 * (-p1.x + 3 * cp1.x - 3 * cp2.x + p2.x),
|
|
3627
|
+
6 * (p1.x - 2 * cp1.x + cp2.x),
|
|
3628
|
+
3 * (cp1.x - p1.x)
|
|
3458
3629
|
);
|
|
3459
3630
|
const dyRoots = this._solveQuadratic(
|
|
3460
|
-
3 * (cp1.y -
|
|
3461
|
-
6 * (
|
|
3462
|
-
3 * (
|
|
3631
|
+
3 * (-p1.y + 3 * cp1.y - 3 * cp2.y + p2.y),
|
|
3632
|
+
6 * (p1.y - 2 * cp1.y + cp2.y),
|
|
3633
|
+
3 * (cp1.y - p1.y)
|
|
3463
3634
|
);
|
|
3464
3635
|
const tValues = [0, 1, ...dxRoots, ...dyRoots];
|
|
3465
|
-
const
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
min.y = Math.min(min.y, point.y);
|
|
3473
|
-
max.x = Math.max(max.x, point.x);
|
|
3474
|
-
max.y = Math.max(max.y, point.y);
|
|
3475
|
-
}
|
|
3476
|
-
}
|
|
3477
|
-
};
|
|
3478
|
-
samplePoints(tValues, 10);
|
|
3636
|
+
for (const t of tValues) {
|
|
3637
|
+
const point = this.getPoint(t);
|
|
3638
|
+
min.x = Math.min(min.x, point.x);
|
|
3639
|
+
min.y = Math.min(min.y, point.y);
|
|
3640
|
+
max.x = Math.max(max.x, point.x);
|
|
3641
|
+
max.y = Math.max(max.y, point.y);
|
|
3642
|
+
}
|
|
3479
3643
|
return { min: min.finite(), max: max.finite() };
|
|
3480
3644
|
}
|
|
3481
3645
|
toCommands() {
|
|
@@ -3528,11 +3692,11 @@ class EllipseCurve extends RoundCurve {
|
|
|
3528
3692
|
}
|
|
3529
3693
|
}
|
|
3530
3694
|
|
|
3531
|
-
class
|
|
3695
|
+
class PolygonCurve extends CompositeCurve {
|
|
3532
3696
|
//
|
|
3533
3697
|
}
|
|
3534
3698
|
|
|
3535
|
-
class
|
|
3699
|
+
class EquilateralPolygonCurve extends PolygonCurve {
|
|
3536
3700
|
constructor(cx = 0, cy = 0, radius = 1, sideCount = 3) {
|
|
3537
3701
|
super();
|
|
3538
3702
|
this.cx = cx;
|
|
@@ -3615,14 +3779,25 @@ class QuadraticBezierCurve extends Curve {
|
|
|
3615
3779
|
}
|
|
3616
3780
|
getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
|
|
3617
3781
|
const { p1, cp, p2 } = this;
|
|
3618
|
-
const
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3782
|
+
const extrema = (a, b, c) => {
|
|
3783
|
+
const denom = a - 2 * b + c;
|
|
3784
|
+
if (Math.abs(denom) < 1e-12)
|
|
3785
|
+
return null;
|
|
3786
|
+
const t = (a - b) / denom;
|
|
3787
|
+
return t > 0 && t < 1 ? t : null;
|
|
3788
|
+
};
|
|
3789
|
+
const tx = extrema(p1.x, cp.x, p2.x);
|
|
3790
|
+
const ty = extrema(p1.y, cp.y, p2.y);
|
|
3791
|
+
const xs = [p1.x, p2.x];
|
|
3792
|
+
const ys = [p1.y, p2.y];
|
|
3793
|
+
if (tx !== null)
|
|
3794
|
+
xs.push(quadraticBezier(tx, p1.x, cp.x, p2.x));
|
|
3795
|
+
if (ty !== null)
|
|
3796
|
+
ys.push(quadraticBezier(ty, p1.y, cp.y, p2.y));
|
|
3797
|
+
min.x = Math.min(min.x, ...xs);
|
|
3798
|
+
min.y = Math.min(min.y, ...ys);
|
|
3799
|
+
max.x = Math.max(max.x, ...xs);
|
|
3800
|
+
max.y = Math.max(max.y, ...ys);
|
|
3626
3801
|
return { min: min.finite(), max: max.finite() };
|
|
3627
3802
|
}
|
|
3628
3803
|
toCommands() {
|
|
@@ -3647,7 +3822,7 @@ class QuadraticBezierCurve extends Curve {
|
|
|
3647
3822
|
}
|
|
3648
3823
|
}
|
|
3649
3824
|
|
|
3650
|
-
class RectangleCurve extends
|
|
3825
|
+
class RectangleCurve extends PolygonCurve {
|
|
3651
3826
|
constructor(x = 0, y = 0, width = 0, height = 0) {
|
|
3652
3827
|
super();
|
|
3653
3828
|
this.x = x;
|
|
@@ -3825,6 +4000,17 @@ class CurvePath extends CompositeCurve {
|
|
|
3825
4000
|
super.getFillVertices(options)
|
|
3826
4001
|
);
|
|
3827
4002
|
}
|
|
4003
|
+
/**
|
|
4004
|
+
* Same as {@link Curve.isPointInStroke}, but `closed` defaults to this sub-path's actual
|
|
4005
|
+
* closed-ness: explicitly `autoClose`, or geometrically closed (first vertex === last).
|
|
4006
|
+
*/
|
|
4007
|
+
isPointInStroke(point, options = {}) {
|
|
4008
|
+
const { strokeWidth = 1, tolerance = 0 } = options;
|
|
4009
|
+
const vertices = this.getAdaptiveVertices();
|
|
4010
|
+
const len = vertices.length;
|
|
4011
|
+
const closed = options.closed ?? (this.autoClose || len >= 6 && vertices[0] === vertices[len - 2] && vertices[1] === vertices[len - 1]);
|
|
4012
|
+
return pointToPolylineDistance(point, vertices, closed) <= strokeWidth / 2 + tolerance;
|
|
4013
|
+
}
|
|
3828
4014
|
_setCurrentPoint(point) {
|
|
3829
4015
|
this.currentPoint = new Vector2(point.x, point.y);
|
|
3830
4016
|
if (!this.startPoint) {
|
|
@@ -4055,13 +4241,11 @@ class Path2D extends CompositeCurve {
|
|
|
4055
4241
|
return this;
|
|
4056
4242
|
}
|
|
4057
4243
|
moveTo(x, y) {
|
|
4058
|
-
if (
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
this.curves.push(this.currentCurve);
|
|
4062
|
-
}
|
|
4063
|
-
this.currentCurve.moveTo(x, y);
|
|
4244
|
+
if (this.currentCurve.curves.length) {
|
|
4245
|
+
this.currentCurve = new CurvePath();
|
|
4246
|
+
this.curves.push(this.currentCurve);
|
|
4064
4247
|
}
|
|
4248
|
+
this.currentCurve.moveTo(x, y);
|
|
4065
4249
|
return this;
|
|
4066
4250
|
}
|
|
4067
4251
|
lineTo(x, y) {
|
|
@@ -4190,6 +4374,40 @@ class Path2D extends CompositeCurve {
|
|
|
4190
4374
|
});
|
|
4191
4375
|
return this;
|
|
4192
4376
|
}
|
|
4377
|
+
/**
|
|
4378
|
+
* Test whether a point lies inside the filled area of this path.
|
|
4379
|
+
*
|
|
4380
|
+
* Each sub-path ({@link CurvePath}) is sampled into its own ring and all rings are
|
|
4381
|
+
* evaluated together via {@link pointInPolygons}, so holes (donut / hollow shapes) are
|
|
4382
|
+
* honored. This is purely geometric and ignores `style.fill` — for the `fill: 'none'`
|
|
4383
|
+
* fallback, gate the call upstream (see {@link Path2DSet.hitTest}).
|
|
4384
|
+
*
|
|
4385
|
+
* Defaults `fillRule` to `style.fillRule`, then `'nonzero'` (matching SVG/Canvas).
|
|
4386
|
+
*/
|
|
4387
|
+
isPointInFill(point, options = {}) {
|
|
4388
|
+
const fillRule = options.fillRule ?? this.style.fillRule ?? "nonzero";
|
|
4389
|
+
return pointInPolygons(
|
|
4390
|
+
point,
|
|
4391
|
+
this.curves.map((curve) => curve.getAdaptiveVertices()),
|
|
4392
|
+
fillRule
|
|
4393
|
+
);
|
|
4394
|
+
}
|
|
4395
|
+
/**
|
|
4396
|
+
* Test whether a point lies on this path's stroke. A hit on any sub-path counts.
|
|
4397
|
+
*
|
|
4398
|
+
* Defaults `strokeWidth` to this path's own {@link strokeWidth} (which is `0` when
|
|
4399
|
+
* `style.stroke` is `'none'`). Each sub-path infers its own closed-ness unless `closed`
|
|
4400
|
+
* is given explicitly.
|
|
4401
|
+
*/
|
|
4402
|
+
isPointInStroke(point, options = {}) {
|
|
4403
|
+
const strokeWidth = options.strokeWidth ?? this.strokeWidth;
|
|
4404
|
+
const { tolerance = 0, closed } = options;
|
|
4405
|
+
return this.curves.some((curve) => curve.isPointInStroke(point, {
|
|
4406
|
+
strokeWidth,
|
|
4407
|
+
tolerance,
|
|
4408
|
+
closed
|
|
4409
|
+
}));
|
|
4410
|
+
}
|
|
4193
4411
|
getMinMax(min = Vector2.MAX, max = Vector2.MIN, withStyle = true) {
|
|
4194
4412
|
const strokeWidth = this.strokeWidth;
|
|
4195
4413
|
this.curves.forEach((curve) => {
|
|
@@ -4367,6 +4585,44 @@ class Path2DSet {
|
|
|
4367
4585
|
this.paths = paths;
|
|
4368
4586
|
this.viewBox = viewBox;
|
|
4369
4587
|
}
|
|
4588
|
+
/**
|
|
4589
|
+
* Test whether a point lies inside the filled area of any path in this set.
|
|
4590
|
+
* Purely geometric (ignores `fill: 'none'`); use {@link hitTest} for style-aware hits.
|
|
4591
|
+
*/
|
|
4592
|
+
isPointInFill(point, options = {}) {
|
|
4593
|
+
return this.paths.some((path) => path.isPointInFill(point, options));
|
|
4594
|
+
}
|
|
4595
|
+
/**
|
|
4596
|
+
* Concise PathKit-style fill containment test across the whole set; shorthand for
|
|
4597
|
+
* {@link isPointInFill} with a `{ x, y }` point.
|
|
4598
|
+
*/
|
|
4599
|
+
contains(x, y, options = {}) {
|
|
4600
|
+
return this.isPointInFill({ x, y }, options);
|
|
4601
|
+
}
|
|
4602
|
+
/**
|
|
4603
|
+
* Find the topmost path hit by a point, or `undefined` if none.
|
|
4604
|
+
*
|
|
4605
|
+
* Paths are tested top-to-bottom (last drawn first). For each path a fill hit is checked
|
|
4606
|
+
* first (skipped when `style.fill` is `'none'`), then — if `stroke` is enabled — a stroke
|
|
4607
|
+
* hit (skipped when `style.stroke` is `'none'`). This honors the "fill: none falls back to
|
|
4608
|
+
* stroke" rule; the coordinate space of `point` must match the paths (no scaling assumed).
|
|
4609
|
+
*
|
|
4610
|
+
* Options: `stroke` (also test strokes, default `true`), `tolerance` (extra stroke hit slack
|
|
4611
|
+
* in path units, default `0`), and `fillRule` (overrides each path's own fill rule).
|
|
4612
|
+
*/
|
|
4613
|
+
hitTest(point, options = {}) {
|
|
4614
|
+
const { stroke = true, tolerance, fillRule } = options;
|
|
4615
|
+
for (let i = this.paths.length - 1; i >= 0; i--) {
|
|
4616
|
+
const path = this.paths[i];
|
|
4617
|
+
if ((path.style.fill ?? "#000") !== "none" && path.isPointInFill(point, { fillRule })) {
|
|
4618
|
+
return path;
|
|
4619
|
+
}
|
|
4620
|
+
if (stroke && (path.style.stroke ?? "none") !== "none" && path.isPointInStroke(point, { tolerance })) {
|
|
4621
|
+
return path;
|
|
4622
|
+
}
|
|
4623
|
+
}
|
|
4624
|
+
return void 0;
|
|
4625
|
+
}
|
|
4370
4626
|
getBoundingBox(withStyle = true) {
|
|
4371
4627
|
if (!this.paths.length) {
|
|
4372
4628
|
return void 0;
|
|
@@ -4529,14 +4785,14 @@ exports.CubicBezierCurve = CubicBezierCurve;
|
|
|
4529
4785
|
exports.Curve = Curve;
|
|
4530
4786
|
exports.CurvePath = CurvePath;
|
|
4531
4787
|
exports.EllipseCurve = EllipseCurve;
|
|
4532
|
-
exports.
|
|
4788
|
+
exports.EquilateralPolygonCurve = EquilateralPolygonCurve;
|
|
4533
4789
|
exports.FFDControlGrid = FFDControlGrid;
|
|
4534
4790
|
exports.LineCurve = LineCurve;
|
|
4535
4791
|
exports.PI = PI;
|
|
4536
4792
|
exports.PI_2 = PI_2;
|
|
4537
4793
|
exports.Path2D = Path2D;
|
|
4538
4794
|
exports.Path2DSet = Path2DSet;
|
|
4539
|
-
exports.
|
|
4795
|
+
exports.PolygonCurve = PolygonCurve;
|
|
4540
4796
|
exports.QuadraticBezierCurve = QuadraticBezierCurve;
|
|
4541
4797
|
exports.RectangleCurve = RectangleCurve;
|
|
4542
4798
|
exports.RoundRectangleCurve = RoundRectangleCurve;
|
|
@@ -4558,6 +4814,10 @@ exports.parseCssArg = parseCssArg;
|
|
|
4558
4814
|
exports.parseCssArgs = parseCssArgs;
|
|
4559
4815
|
exports.parseCssFunctions = parseCssFunctions;
|
|
4560
4816
|
exports.parsePathDataArgs = parsePathDataArgs;
|
|
4817
|
+
exports.pointInPolygon = pointInPolygon;
|
|
4818
|
+
exports.pointInPolygons = pointInPolygons;
|
|
4819
|
+
exports.pointToPolylineDistance = pointToPolylineDistance;
|
|
4820
|
+
exports.pointToSegmentDistance = pointToSegmentDistance;
|
|
4561
4821
|
exports.quadraticBezier = quadraticBezier;
|
|
4562
4822
|
exports.setCanvasContext = setCanvasContext;
|
|
4563
4823
|
exports.strokeTriangulate = strokeTriangulate;
|