modern-path2d 1.6.1 → 1.7.0
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 +55 -0
- package/dist/index.cjs +249 -63
- package/dist/index.d.cts +56 -2
- package/dist/index.d.mts +56 -2
- package/dist/index.d.ts +56 -2
- package/dist/index.js +2 -2
- package/dist/index.mjs +249 -63
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -120,7 +120,10 @@ class Vector2 {
|
|
|
120
120
|
return this.set(this._x * x, this._y * y);
|
|
121
121
|
}
|
|
122
122
|
divide(x = 0, y = x) {
|
|
123
|
-
return this.set(
|
|
123
|
+
return this.set(
|
|
124
|
+
x === 0 ? this._x : this._x / x,
|
|
125
|
+
y === 0 ? this._y : this._y / y
|
|
126
|
+
);
|
|
124
127
|
}
|
|
125
128
|
cross(p) {
|
|
126
129
|
return this._x * p.y - this._y * p.x;
|
|
@@ -313,17 +316,11 @@ function getIntersectionPoint(p1, p2, q1, q2) {
|
|
|
313
316
|
const q1p1 = q1.clone().sub(p1);
|
|
314
317
|
const crossRS = r.cross(s);
|
|
315
318
|
if (crossRS === 0) {
|
|
316
|
-
return
|
|
317
|
-
(p1.x + q1.x) / 2,
|
|
318
|
-
(p1.y + q1.y) / 2
|
|
319
|
-
);
|
|
319
|
+
return null;
|
|
320
320
|
}
|
|
321
321
|
const t = q1p1.cross(s) / crossRS;
|
|
322
322
|
if (Math.abs(t) > 1) {
|
|
323
|
-
return
|
|
324
|
-
(p1.x + q1.x) / 2,
|
|
325
|
-
(p1.y + q1.y) / 2
|
|
326
|
-
);
|
|
323
|
+
return null;
|
|
327
324
|
}
|
|
328
325
|
return new Vector2(
|
|
329
326
|
p1.x + t * r.x,
|
|
@@ -615,11 +612,16 @@ function distance(p1, p2) {
|
|
|
615
612
|
const dy = p2[1] - p1[1];
|
|
616
613
|
return Math.sqrt(dx * dx + dy * dy);
|
|
617
614
|
}
|
|
615
|
+
function aabbIntersects(a, b) {
|
|
616
|
+
return a.minX <= b.maxX && a.maxX >= b.minX && a.minY <= b.maxY && a.maxY >= b.minY;
|
|
617
|
+
}
|
|
618
618
|
function nonzeroFillRule(paths) {
|
|
619
619
|
const results = paths.map((_, i) => ({ index: i }));
|
|
620
|
-
const
|
|
620
|
+
const bboxes = [];
|
|
621
|
+
const testPointsGroups = paths.map((path, pathIndex) => {
|
|
621
622
|
const len = path.length;
|
|
622
623
|
if (!len) {
|
|
624
|
+
bboxes[pathIndex] = null;
|
|
623
625
|
return [];
|
|
624
626
|
}
|
|
625
627
|
let xMinYAuto = [Number.MAX_SAFE_INTEGER, 0];
|
|
@@ -642,6 +644,12 @@ function nonzeroFillRule(paths) {
|
|
|
642
644
|
xAutoYMax = [x, y];
|
|
643
645
|
}
|
|
644
646
|
}
|
|
647
|
+
bboxes[pathIndex] = {
|
|
648
|
+
minX: xMinYAuto[0],
|
|
649
|
+
minY: xAutoYMin[1],
|
|
650
|
+
maxX: xMaxYAuto[0],
|
|
651
|
+
maxY: xAutoYMax[1]
|
|
652
|
+
};
|
|
645
653
|
const mid = [
|
|
646
654
|
(xMinYAuto[0] + xMaxYAuto[0]) / 2,
|
|
647
655
|
(xAutoYMin[1] + xAutoYMax[1]) / 2
|
|
@@ -690,9 +698,14 @@ function nonzeroFillRule(paths) {
|
|
|
690
698
|
for (let i = 0, len = paths.length; i < len; i++) {
|
|
691
699
|
const _results = [];
|
|
692
700
|
const testPoints = testPointsGroups[i];
|
|
701
|
+
const boxI = bboxes[i];
|
|
693
702
|
for (let j = 0; j < len; j++) {
|
|
694
703
|
if (i === j)
|
|
695
704
|
continue;
|
|
705
|
+
const boxJ = bboxes[j];
|
|
706
|
+
if (!boxI || !boxJ || !aabbIntersects(boxI, boxJ)) {
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
696
709
|
const wnMap = {};
|
|
697
710
|
const wnList = [];
|
|
698
711
|
for (let p = 0, pLen = testPoints.length; p < pLen; p++) {
|
|
@@ -2647,6 +2660,41 @@ function svgToPath2DSet(svg) {
|
|
|
2647
2660
|
class Curve {
|
|
2648
2661
|
arcLengthDivision = 200;
|
|
2649
2662
|
_lengths = [];
|
|
2663
|
+
_adaptiveCache;
|
|
2664
|
+
/**
|
|
2665
|
+
* Parent composite, set lazily when a composite caches its children. Lets
|
|
2666
|
+
* {@link invalidate} propagate up so an ancestor's caches refresh too.
|
|
2667
|
+
*/
|
|
2668
|
+
_owner;
|
|
2669
|
+
_invalidating = false;
|
|
2670
|
+
/**
|
|
2671
|
+
* Drop cached arc lengths and the cached sampled outline used by hit testing, then
|
|
2672
|
+
* bubble up to {@link _owner}. Called automatically by {@link applyTransform} and the
|
|
2673
|
+
* `Path2D` mutators; call it manually after mutating control-point coordinates in place —
|
|
2674
|
+
* the caches cannot observe such mutations.
|
|
2675
|
+
*/
|
|
2676
|
+
invalidate() {
|
|
2677
|
+
if (this._invalidating) {
|
|
2678
|
+
return this;
|
|
2679
|
+
}
|
|
2680
|
+
this._invalidating = true;
|
|
2681
|
+
this._invalidateSelf();
|
|
2682
|
+
this._owner?.invalidate();
|
|
2683
|
+
this._invalidating = false;
|
|
2684
|
+
return this;
|
|
2685
|
+
}
|
|
2686
|
+
/** Clears this curve's own caches. Composites also clear their children (see override). */
|
|
2687
|
+
_invalidateSelf() {
|
|
2688
|
+
this._lengths.length = 0;
|
|
2689
|
+
this._adaptiveCache = void 0;
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* Sampled outline cached for repeated hit tests (read-only — do not mutate the result).
|
|
2693
|
+
* Invalidated by {@link invalidate}.
|
|
2694
|
+
*/
|
|
2695
|
+
_getCachedAdaptiveVertices() {
|
|
2696
|
+
return this._adaptiveCache ??= this.getAdaptiveVertices();
|
|
2697
|
+
}
|
|
2650
2698
|
getPointAt(u, output = new Vector2()) {
|
|
2651
2699
|
return this.getPoint(this.getUToTMapping(u), output);
|
|
2652
2700
|
}
|
|
@@ -2665,6 +2713,7 @@ class Curve {
|
|
|
2665
2713
|
transform.apply(p, p);
|
|
2666
2714
|
}
|
|
2667
2715
|
});
|
|
2716
|
+
this.invalidate();
|
|
2668
2717
|
return this;
|
|
2669
2718
|
}
|
|
2670
2719
|
getUnevenVertices(count = 5, output = []) {
|
|
@@ -2809,12 +2858,25 @@ class Curve {
|
|
|
2809
2858
|
return mid;
|
|
2810
2859
|
}
|
|
2811
2860
|
getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
|
|
2812
|
-
const
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2861
|
+
const vertices = this.getAdaptiveVertices();
|
|
2862
|
+
let minX = min.x;
|
|
2863
|
+
let minY = min.y;
|
|
2864
|
+
let maxX = max.x;
|
|
2865
|
+
let maxY = max.y;
|
|
2866
|
+
for (let i = 0, len = vertices.length; i < len; i += 2) {
|
|
2867
|
+
const x = vertices[i];
|
|
2868
|
+
const y = vertices[i + 1];
|
|
2869
|
+
if (x < minX)
|
|
2870
|
+
minX = x;
|
|
2871
|
+
if (y < minY)
|
|
2872
|
+
minY = y;
|
|
2873
|
+
if (x > maxX)
|
|
2874
|
+
maxX = x;
|
|
2875
|
+
if (y > maxY)
|
|
2876
|
+
maxY = y;
|
|
2817
2877
|
}
|
|
2878
|
+
min.set(minX, minY);
|
|
2879
|
+
max.set(maxX, maxY);
|
|
2818
2880
|
return { min: min.finite(), max: max.finite() };
|
|
2819
2881
|
}
|
|
2820
2882
|
getBoundingBox() {
|
|
@@ -2832,7 +2894,7 @@ class Curve {
|
|
|
2832
2894
|
* are honored — a single `Curve` is always one ring.
|
|
2833
2895
|
*/
|
|
2834
2896
|
isPointInFill(point, options = {}) {
|
|
2835
|
-
return pointInPolygon(point, this.
|
|
2897
|
+
return pointInPolygon(point, this._getCachedAdaptiveVertices(), options.fillRule);
|
|
2836
2898
|
}
|
|
2837
2899
|
/**
|
|
2838
2900
|
* Test whether a point lies on this curve's stroke, i.e. within `strokeWidth / 2 + tolerance`
|
|
@@ -2845,7 +2907,7 @@ class Curve {
|
|
|
2845
2907
|
*/
|
|
2846
2908
|
isPointInStroke(point, options = {}) {
|
|
2847
2909
|
const { strokeWidth = 1, tolerance = 0, closed = false } = options;
|
|
2848
|
-
const distance = pointToPolylineDistance(point, this.
|
|
2910
|
+
const distance = pointToPolylineDistance(point, this._getCachedAdaptiveVertices(), closed);
|
|
2849
2911
|
return distance <= strokeWidth / 2 + tolerance;
|
|
2850
2912
|
}
|
|
2851
2913
|
/**
|
|
@@ -2989,6 +3051,64 @@ class RoundCurve extends Curve {
|
|
|
2989
3051
|
}
|
|
2990
3052
|
return output.set(_x, _y);
|
|
2991
3053
|
}
|
|
3054
|
+
/**
|
|
3055
|
+
* Point on the ellipse at an absolute angle (mirrors {@link getPoint}'s parameterization,
|
|
3056
|
+
* ignoring `_diff`).
|
|
3057
|
+
*/
|
|
3058
|
+
_pointAtAngle(angle, output) {
|
|
3059
|
+
let x = this.cx + this.rx * Math.cos(angle);
|
|
3060
|
+
let y = this.cy + this.ry * Math.sin(angle);
|
|
3061
|
+
if (this.rotate !== 0) {
|
|
3062
|
+
const cos = Math.cos(this.rotate);
|
|
3063
|
+
const sin = Math.sin(this.rotate);
|
|
3064
|
+
const tx = x - this.cx;
|
|
3065
|
+
const ty = y - this.cy;
|
|
3066
|
+
x = tx * cos - ty * sin + this.cx;
|
|
3067
|
+
y = tx * sin + ty * cos + this.cy;
|
|
3068
|
+
}
|
|
3069
|
+
return output.set(x, y);
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Analytical bounds of the (elliptical) arc: the start/end points plus the per-axis
|
|
3073
|
+
* extrema angles that fall within the swept interval. Matches {@link getPoint}, so it is
|
|
3074
|
+
* exact for `ArcCurve`/`EllipseCurve`. The `_diff` offset (used only by the legacy
|
|
3075
|
+
* `_getAdaptiveVerticesByCircle` path) is intentionally ignored here.
|
|
3076
|
+
*/
|
|
3077
|
+
getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
|
|
3078
|
+
const { startAngle, rotate } = this;
|
|
3079
|
+
const delta = this._getDeltaAngle();
|
|
3080
|
+
const cosT = Math.cos(rotate);
|
|
3081
|
+
const sinT = Math.sin(rotate);
|
|
3082
|
+
const p = tempV2;
|
|
3083
|
+
let minX = min.x;
|
|
3084
|
+
let minY = min.y;
|
|
3085
|
+
let maxX = max.x;
|
|
3086
|
+
let maxY = max.y;
|
|
3087
|
+
const consider = (angle) => {
|
|
3088
|
+
this._pointAtAngle(angle, p);
|
|
3089
|
+
if (p.x < minX)
|
|
3090
|
+
minX = p.x;
|
|
3091
|
+
if (p.y < minY)
|
|
3092
|
+
minY = p.y;
|
|
3093
|
+
if (p.x > maxX)
|
|
3094
|
+
maxX = p.x;
|
|
3095
|
+
if (p.y > maxY)
|
|
3096
|
+
maxY = p.y;
|
|
3097
|
+
};
|
|
3098
|
+
consider(startAngle);
|
|
3099
|
+
consider(startAngle + delta);
|
|
3100
|
+
const ax = Math.atan2(-this.ry * sinT, this.rx * cosT);
|
|
3101
|
+
const ay = Math.atan2(this.ry * cosT, this.rx * sinT);
|
|
3102
|
+
const bases = [ax, ax + Math.PI, ay, ay + Math.PI];
|
|
3103
|
+
for (let i = 0; i < 4; i++) {
|
|
3104
|
+
if (angleInSweep(bases[i], startAngle, delta)) {
|
|
3105
|
+
consider(bases[i]);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
min.set(minX, minY);
|
|
3109
|
+
max.set(maxX, maxY);
|
|
3110
|
+
return { min: min.finite(), max: max.finite() };
|
|
3111
|
+
}
|
|
2992
3112
|
toCommands() {
|
|
2993
3113
|
const { cx, cy, rx, ry, startAngle, endAngle, clockwise, rotate } = this;
|
|
2994
3114
|
const startX = cx + rx * Math.cos(startAngle) * Math.cos(rotate) - ry * Math.sin(startAngle) * Math.sin(rotate);
|
|
@@ -3039,6 +3159,7 @@ class RoundCurve extends Curve {
|
|
|
3039
3159
|
} else {
|
|
3040
3160
|
transfEllipseNoSkew(this, transform);
|
|
3041
3161
|
}
|
|
3162
|
+
this.invalidate();
|
|
3042
3163
|
return this;
|
|
3043
3164
|
}
|
|
3044
3165
|
getControlPointRefs() {
|
|
@@ -3179,6 +3300,23 @@ class RoundCurve extends Curve {
|
|
|
3179
3300
|
return this;
|
|
3180
3301
|
}
|
|
3181
3302
|
}
|
|
3303
|
+
function angleInSweep(a, start, delta) {
|
|
3304
|
+
const PI_2 = Math.PI * 2;
|
|
3305
|
+
const eps = 1e-9;
|
|
3306
|
+
if (Math.abs(delta) >= PI_2 - eps) {
|
|
3307
|
+
return true;
|
|
3308
|
+
}
|
|
3309
|
+
let off = (a - start) % PI_2;
|
|
3310
|
+
if (delta >= 0) {
|
|
3311
|
+
if (off < -eps)
|
|
3312
|
+
off += PI_2;
|
|
3313
|
+
return off >= -eps && off <= delta + eps;
|
|
3314
|
+
}
|
|
3315
|
+
if (off > eps) {
|
|
3316
|
+
off -= PI_2;
|
|
3317
|
+
}
|
|
3318
|
+
return off <= eps && off >= delta - eps;
|
|
3319
|
+
}
|
|
3182
3320
|
function transfEllipseGeneric(curve, m) {
|
|
3183
3321
|
const a = curve.rx;
|
|
3184
3322
|
const b = curve.ry;
|
|
@@ -3417,6 +3555,22 @@ class CompositeCurve extends Curve {
|
|
|
3417
3555
|
super();
|
|
3418
3556
|
this.curves = curves;
|
|
3419
3557
|
}
|
|
3558
|
+
_adaptiveCacheLen = -1;
|
|
3559
|
+
_invalidateSelf() {
|
|
3560
|
+
super._invalidateSelf();
|
|
3561
|
+
this._adaptiveCacheLen = -1;
|
|
3562
|
+
this.curves.forEach((curve) => curve.invalidate());
|
|
3563
|
+
}
|
|
3564
|
+
_getCachedAdaptiveVertices() {
|
|
3565
|
+
if (!this._adaptiveCache || this._adaptiveCacheLen !== this.curves.length) {
|
|
3566
|
+
this.curves.forEach((curve) => {
|
|
3567
|
+
curve._owner = this;
|
|
3568
|
+
});
|
|
3569
|
+
this._adaptiveCache = this.getAdaptiveVertices();
|
|
3570
|
+
this._adaptiveCacheLen = this.curves.length;
|
|
3571
|
+
}
|
|
3572
|
+
return this._adaptiveCache;
|
|
3573
|
+
}
|
|
3420
3574
|
getFlatCurves() {
|
|
3421
3575
|
return this.curves.flatMap((curve) => {
|
|
3422
3576
|
if (curve instanceof CompositeCurve) {
|
|
@@ -3462,6 +3616,7 @@ class CompositeCurve extends Curve {
|
|
|
3462
3616
|
updateLengths() {
|
|
3463
3617
|
const lengths = [];
|
|
3464
3618
|
for (let i = 0, sum = 0, len = this.curves.length; i < len; i++) {
|
|
3619
|
+
this.curves[i]._owner = this;
|
|
3465
3620
|
sum += this.curves[i].getLength();
|
|
3466
3621
|
lengths.push(sum);
|
|
3467
3622
|
}
|
|
@@ -3530,7 +3685,10 @@ class CompositeCurve extends Curve {
|
|
|
3530
3685
|
}
|
|
3531
3686
|
}
|
|
3532
3687
|
applyTransform(transform) {
|
|
3688
|
+
this._invalidating = true;
|
|
3533
3689
|
this.curves.forEach((curve) => curve.applyTransform(transform));
|
|
3690
|
+
this._invalidating = false;
|
|
3691
|
+
this.invalidate();
|
|
3534
3692
|
return this;
|
|
3535
3693
|
}
|
|
3536
3694
|
getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
|
|
@@ -3869,7 +4027,7 @@ class RectangleCurve extends PolygonCurve {
|
|
|
3869
4027
|
}
|
|
3870
4028
|
}
|
|
3871
4029
|
|
|
3872
|
-
class RoundRectangleCurve extends
|
|
4030
|
+
class RoundRectangleCurve extends CompositeCurve {
|
|
3873
4031
|
constructor(x = 0, y = 0, width = 1, height = 1, radius = 1) {
|
|
3874
4032
|
super();
|
|
3875
4033
|
this.x = x;
|
|
@@ -3880,25 +4038,53 @@ class RoundRectangleCurve extends RoundCurve {
|
|
|
3880
4038
|
this.update();
|
|
3881
4039
|
}
|
|
3882
4040
|
update() {
|
|
3883
|
-
const { x, y, width, height
|
|
3884
|
-
const
|
|
3885
|
-
const
|
|
3886
|
-
const
|
|
3887
|
-
const
|
|
3888
|
-
const
|
|
3889
|
-
const
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
4041
|
+
const { x, y, width, height } = this;
|
|
4042
|
+
const r = Math.max(0, Math.min(this.radius, Math.abs(width) / 2, Math.abs(height) / 2));
|
|
4043
|
+
const x0 = x;
|
|
4044
|
+
const x1 = x + r;
|
|
4045
|
+
const x2 = x + width - r;
|
|
4046
|
+
const x3 = x + width;
|
|
4047
|
+
const y0 = y;
|
|
4048
|
+
const y1 = y + r;
|
|
4049
|
+
const y2 = y + height - r;
|
|
4050
|
+
const y3 = y + height;
|
|
4051
|
+
if (r <= 0) {
|
|
4052
|
+
this.curves = [
|
|
4053
|
+
LineCurve.from(x0, y0, x3, y0),
|
|
4054
|
+
LineCurve.from(x3, y0, x3, y3),
|
|
4055
|
+
LineCurve.from(x3, y3, x0, y3),
|
|
4056
|
+
LineCurve.from(x0, y3, x0, y0)
|
|
4057
|
+
];
|
|
4058
|
+
} else {
|
|
4059
|
+
const HALF_PI = Math.PI / 2;
|
|
4060
|
+
this.curves = [
|
|
4061
|
+
LineCurve.from(x1, y0, x2, y0),
|
|
4062
|
+
// top edge
|
|
4063
|
+
new ArcCurve(x2, y1, r, -HALF_PI, 0, true),
|
|
4064
|
+
// top-right corner
|
|
4065
|
+
LineCurve.from(x3, y1, x3, y2),
|
|
4066
|
+
// right edge
|
|
4067
|
+
new ArcCurve(x2, y2, r, 0, HALF_PI, true),
|
|
4068
|
+
// bottom-right corner
|
|
4069
|
+
LineCurve.from(x2, y3, x1, y3),
|
|
4070
|
+
// bottom edge
|
|
4071
|
+
new ArcCurve(x1, y2, r, HALF_PI, Math.PI, true),
|
|
4072
|
+
// bottom-left corner
|
|
4073
|
+
LineCurve.from(x0, y2, x0, y1),
|
|
4074
|
+
// left edge
|
|
4075
|
+
new ArcCurve(x1, y1, r, Math.PI, Math.PI * 1.5, true)
|
|
4076
|
+
// top-left corner
|
|
4077
|
+
];
|
|
4078
|
+
}
|
|
4079
|
+
this.invalidate();
|
|
3893
4080
|
return this;
|
|
3894
4081
|
}
|
|
3895
4082
|
drawTo(ctx) {
|
|
3896
|
-
|
|
3897
|
-
ctx.roundRect(x, y, width, height, radius);
|
|
4083
|
+
ctx.roundRect(this.x, this.y, this.width, this.height, this.radius);
|
|
3898
4084
|
return this;
|
|
3899
4085
|
}
|
|
3900
4086
|
copyFrom(source) {
|
|
3901
|
-
|
|
4087
|
+
this.arcLengthDivision = source.arcLengthDivision;
|
|
3902
4088
|
this.x = source.x;
|
|
3903
4089
|
this.y = source.y;
|
|
3904
4090
|
this.width = source.width;
|
|
@@ -4000,7 +4186,7 @@ class CurvePath extends CompositeCurve {
|
|
|
4000
4186
|
*/
|
|
4001
4187
|
isPointInStroke(point, options = {}) {
|
|
4002
4188
|
const { strokeWidth = 1, tolerance = 0 } = options;
|
|
4003
|
-
const vertices = this.
|
|
4189
|
+
const vertices = this._getCachedAdaptiveVertices();
|
|
4004
4190
|
const len = vertices.length;
|
|
4005
4191
|
const closed = options.closed ?? (this.autoClose || len >= 6 && vertices[0] === vertices[len - 2] && vertices[1] === vertices[len - 1]);
|
|
4006
4192
|
return pointToPolylineDistance(point, vertices, closed) <= strokeWidth / 2 + tolerance;
|
|
@@ -4178,6 +4364,8 @@ class CurvePath extends CompositeCurve {
|
|
|
4178
4364
|
|
|
4179
4365
|
class Path2D extends CompositeCurve {
|
|
4180
4366
|
_meta;
|
|
4367
|
+
_ringsCache;
|
|
4368
|
+
_ringsCacheLen = -1;
|
|
4181
4369
|
currentCurve = new CurvePath();
|
|
4182
4370
|
style;
|
|
4183
4371
|
get startPoint() {
|
|
@@ -4296,18 +4484,21 @@ class Path2D extends CompositeCurve {
|
|
|
4296
4484
|
this.getControlPointRefs().forEach((point) => {
|
|
4297
4485
|
point.scale(sx, sy, target);
|
|
4298
4486
|
});
|
|
4487
|
+
this.invalidate();
|
|
4299
4488
|
return this;
|
|
4300
4489
|
}
|
|
4301
4490
|
skew(ax, ay = 0, target = { x: 0, y: 0 }) {
|
|
4302
4491
|
this.getControlPointRefs().forEach((point) => {
|
|
4303
4492
|
point.skew(ax, ay, target);
|
|
4304
4493
|
});
|
|
4494
|
+
this.invalidate();
|
|
4305
4495
|
return this;
|
|
4306
4496
|
}
|
|
4307
4497
|
rotate(rad, target = { x: 0, y: 0 }) {
|
|
4308
4498
|
this.getControlPointRefs().forEach((point) => {
|
|
4309
4499
|
point.rotate(rad, target);
|
|
4310
4500
|
});
|
|
4501
|
+
this.invalidate();
|
|
4311
4502
|
return this;
|
|
4312
4503
|
}
|
|
4313
4504
|
bold(b) {
|
|
@@ -4366,6 +4557,7 @@ class Path2D extends CompositeCurve {
|
|
|
4366
4557
|
}
|
|
4367
4558
|
});
|
|
4368
4559
|
});
|
|
4560
|
+
this.invalidate();
|
|
4369
4561
|
return this;
|
|
4370
4562
|
}
|
|
4371
4563
|
/**
|
|
@@ -4378,13 +4570,25 @@ class Path2D extends CompositeCurve {
|
|
|
4378
4570
|
*
|
|
4379
4571
|
* Defaults `fillRule` to `style.fillRule`, then `'nonzero'` (matching SVG/Canvas).
|
|
4380
4572
|
*/
|
|
4573
|
+
_invalidateSelf() {
|
|
4574
|
+
super._invalidateSelf();
|
|
4575
|
+
this._ringsCache = void 0;
|
|
4576
|
+
this._ringsCacheLen = -1;
|
|
4577
|
+
}
|
|
4578
|
+
/** Per-sub-path sampled rings, cached for repeated hit tests. */
|
|
4579
|
+
_getRings() {
|
|
4580
|
+
if (!this._ringsCache || this._ringsCacheLen !== this.curves.length) {
|
|
4581
|
+
this._ringsCache = this.curves.map((curve) => {
|
|
4582
|
+
curve._owner = this;
|
|
4583
|
+
return curve.getAdaptiveVertices();
|
|
4584
|
+
});
|
|
4585
|
+
this._ringsCacheLen = this.curves.length;
|
|
4586
|
+
}
|
|
4587
|
+
return this._ringsCache;
|
|
4588
|
+
}
|
|
4381
4589
|
isPointInFill(point, options = {}) {
|
|
4382
4590
|
const fillRule = options.fillRule ?? this.style.fillRule ?? "nonzero";
|
|
4383
|
-
return pointInPolygons(
|
|
4384
|
-
point,
|
|
4385
|
-
this.curves.map((curve) => curve.getAdaptiveVertices()),
|
|
4386
|
-
fillRule
|
|
4387
|
-
);
|
|
4591
|
+
return pointInPolygons(point, this._getRings(), fillRule);
|
|
4388
4592
|
}
|
|
4389
4593
|
/**
|
|
4390
4594
|
* Test whether a point lies on this path's stroke. A hit on any sub-path counts.
|
|
@@ -4403,35 +4607,17 @@ class Path2D extends CompositeCurve {
|
|
|
4403
4607
|
}));
|
|
4404
4608
|
}
|
|
4405
4609
|
getMinMax(min = Vector2.MAX, max = Vector2.MIN, withStyle = true) {
|
|
4406
|
-
const strokeWidth = this.strokeWidth;
|
|
4407
4610
|
this.curves.forEach((curve) => {
|
|
4408
4611
|
curve.getMinMax(min, max);
|
|
4409
|
-
if (withStyle) {
|
|
4410
|
-
if (strokeWidth > 1) {
|
|
4411
|
-
const halfStrokeWidth = strokeWidth / 2;
|
|
4412
|
-
const isClockwise = curve.isClockwise();
|
|
4413
|
-
const points = [];
|
|
4414
|
-
for (let t = 0; t <= 1; t += 1 / curve.arcLengthDivision) {
|
|
4415
|
-
const point = curve.getPoint(t);
|
|
4416
|
-
const normal = curve.getNormal(t);
|
|
4417
|
-
const dist1 = normal.clone().scale(isClockwise ? halfStrokeWidth : -halfStrokeWidth);
|
|
4418
|
-
const dist2 = normal.clone().scale(isClockwise ? -halfStrokeWidth : halfStrokeWidth);
|
|
4419
|
-
points.push(
|
|
4420
|
-
point.clone().add(dist1),
|
|
4421
|
-
point.clone().add(dist2),
|
|
4422
|
-
point.clone().add({ x: halfStrokeWidth, y: 0 }),
|
|
4423
|
-
point.clone().add({ x: -halfStrokeWidth, y: 0 }),
|
|
4424
|
-
point.clone().add({ x: 0, y: halfStrokeWidth }),
|
|
4425
|
-
point.clone().add({ x: 0, y: -halfStrokeWidth }),
|
|
4426
|
-
point.clone().add({ x: halfStrokeWidth, y: halfStrokeWidth }),
|
|
4427
|
-
point.clone().add({ x: -halfStrokeWidth, y: -halfStrokeWidth })
|
|
4428
|
-
);
|
|
4429
|
-
}
|
|
4430
|
-
min.clampMin(...points);
|
|
4431
|
-
max.clampMax(...points);
|
|
4432
|
-
}
|
|
4433
|
-
}
|
|
4434
4612
|
});
|
|
4613
|
+
if (withStyle) {
|
|
4614
|
+
const strokeWidth = this.strokeWidth;
|
|
4615
|
+
if (strokeWidth > 1 && Number.isFinite(min.x)) {
|
|
4616
|
+
const half = strokeWidth / 2;
|
|
4617
|
+
min.set(min.x - half, min.y - half);
|
|
4618
|
+
max.set(max.x + half, max.y + half);
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4435
4621
|
return { min: min.finite(), max: max.finite() };
|
|
4436
4622
|
}
|
|
4437
4623
|
strokeTriangulate(options) {
|
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.7.0",
|
|
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",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"release": "bumpp package.json --commit \"release: v%s\" --push --all --tag",
|
|
53
53
|
"start": "esno src/index.ts",
|
|
54
54
|
"test": "vitest",
|
|
55
|
+
"bench": "vitest bench --run",
|
|
55
56
|
"typecheck": "tsc --noEmit"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|