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.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 * Math.abs(sx),
190
- y * Math.abs(sy)
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) % (n - 1)];
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 rx = parseFloatWithUnits(node.getAttribute("rx") || node.getAttribute("ry") || 0);
2323
- const ry = parseFloatWithUnits(node.getAttribute("ry") || node.getAttribute("rx") || 0);
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
- let i = 0;
3277
- while (i < lengths.length) {
3278
- if (lengths[i] >= d) {
3279
- const diff = lengths[i] - d;
3280
- const curve = this.curves[i];
3281
- const length = curve.getLength();
3282
- return curve.getPointAt(
3283
- length === 0 ? 0 : 1 - diff / length,
3284
- output
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
- return output;
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 - p1.x),
3450
- 6 * (cp2.x - cp1.x),
3451
- 3 * (p2.x - cp2.x)
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 - p1.y),
3455
- 6 * (cp2.y - cp1.y),
3456
- 3 * (p2.y - cp2.y)
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 samplePoints = (tValues2, precision) => {
3460
- for (const t of tValues2) {
3461
- for (let i = 0; i <= precision; i++) {
3462
- const delta = i / precision - 0.5;
3463
- const refinedT = Math.min(1, Math.max(0, t + delta));
3464
- const point = this.getPoint(refinedT);
3465
- min.x = Math.min(min.x, point.x);
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 PloygonCurve extends CompositeCurve {
3689
+ class PolygonCurve extends CompositeCurve {
3526
3690
  //
3527
3691
  }
3528
3692
 
3529
- class EquilateralPloygonCurve extends PloygonCurve {
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 x1 = 0.5 * (p1.x + cp.x);
3613
- const y1 = 0.5 * (p1.y + cp.y);
3614
- const x2 = 0.5 * (p1.x + p2.x);
3615
- const y2 = 0.5 * (p1.y + p2.y);
3616
- min.x = Math.min(min.x, p1.x, p2.x, x1, x2);
3617
- min.y = Math.min(min.y, p1.y, p2.y, y1, y2);
3618
- max.x = Math.max(max.x, p1.x, p2.x, x1, x2);
3619
- max.y = Math.max(max.y, p1.y, p2.y, y1, y2);
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 PloygonCurve {
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 (!this.currentCurve.currentPoint?.equals({ x, y })) {
4053
- if (this.currentCurve.curves.length) {
4054
- this.currentCurve = new CurvePath();
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, EquilateralPloygonCurve, FFDControlGrid, LineCurve, PI, PI_2, Path2D, Path2DSet, PloygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, Transform2D, Vector2, applyFFD, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, getDirectedArea, getIntersectionPoint, nonzeroFillRule, parseArcCommand, parseCssArg, parseCssArgs, parseCssFunctions, parsePathDataArgs, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDom, svgToPath2DSet, toKebabCase };
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.5.6",
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",