modern-path2d 1.4.5 → 1.4.7

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 CHANGED
@@ -1722,44 +1722,111 @@ function getDirectedArea(vertices) {
1722
1722
  return area / 2;
1723
1723
  }
1724
1724
 
1725
- function pointInPolygonEvenOdd(point, polygon) {
1726
- let inside = false;
1727
- const [x, y] = point;
1728
- const len = polygon.length / 2;
1729
- for (let i = 0, j = len - 1; i < len; j = i++) {
1730
- const xi = polygon[i * 2];
1731
- const yi = polygon[i * 2 + 1];
1732
- const xj = polygon[j * 2];
1733
- const yj = polygon[j * 2 + 1];
1734
- if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
1735
- inside = !inside;
1736
- }
1725
+ function toKebabCase(str) {
1726
+ return str.replace(/[^a-z0-9]/gi, "-").replace(/\B([A-Z])/g, "-$1").toLowerCase();
1727
+ }
1728
+ function getIntersectionPoint(p1, p2, q1, q2) {
1729
+ const r = p2.clone().sub(p1);
1730
+ const s = q2.clone().sub(q1);
1731
+ const q1p1 = q1.clone().sub(p1);
1732
+ const crossRS = r.cross(s);
1733
+ if (crossRS === 0) {
1734
+ return new Vector2(
1735
+ (p1.x + q1.x) / 2,
1736
+ (p1.y + q1.y) / 2
1737
+ );
1737
1738
  }
1738
- return inside;
1739
+ const t = q1p1.cross(s) / crossRS;
1740
+ if (Math.abs(t) > 1) {
1741
+ return new Vector2(
1742
+ (p1.x + q1.x) / 2,
1743
+ (p1.y + q1.y) / 2
1744
+ );
1745
+ }
1746
+ return new Vector2(
1747
+ p1.x + t * r.x,
1748
+ p1.y + t * r.y
1749
+ );
1750
+ }
1751
+
1752
+ function centroid(path) {
1753
+ const pathsLen = path.length;
1754
+ let sx = 0;
1755
+ let sy = 0;
1756
+ const n = pathsLen / 2;
1757
+ for (let i = 0; i < pathsLen; i += 2) {
1758
+ sx += path[i];
1759
+ sy += path[i + 1];
1760
+ }
1761
+ return [sx / n, sy / n];
1762
+ }
1763
+ function signedArea(pts) {
1764
+ let sum = 0;
1765
+ const len = pts.length / 2;
1766
+ for (let i = 0; i < len; i++) {
1767
+ const xi = pts[2 * i];
1768
+ const yi = pts[2 * i + 1];
1769
+ const j = (i + 1) % len;
1770
+ const xj = pts[2 * j];
1771
+ const yj = pts[2 * j + 1];
1772
+ sum += (xj - xi) * (yj + yi);
1773
+ }
1774
+ return sum;
1775
+ }
1776
+ function cross(ax, ay, bx, by, cx, cy) {
1777
+ return (bx - ax) * (cy - ay) - (cx - ax) * (by - ay);
1739
1778
  }
1740
- function pointInPolygonNonZero(point, polygon) {
1741
- const [x, y] = point;
1742
- const len = polygon.length / 2;
1779
+ function windingNumber(px, py, path) {
1780
+ const pathsLen = path.length;
1743
1781
  let wn = 0;
1744
- for (let i = 0, j = len - 1; i < len; j = i++) {
1745
- const xi = polygon[i * 2];
1746
- const yi = polygon[i * 2 + 1];
1747
- const xj = polygon[j * 2];
1748
- const yj = polygon[j * 2 + 1];
1749
- if (yi <= y) {
1750
- if (yj > y && cross([xj, yj], [xi, yi], [x, y]) > 0) {
1782
+ for (let i = 0, j = pathsLen - 2; i < pathsLen; j = i, i += 2) {
1783
+ const xi = path[i];
1784
+ const yi = path[i + 1];
1785
+ const xj = path[j];
1786
+ const yj = path[j + 1];
1787
+ if (yi <= py) {
1788
+ if (yj > py && cross(xj, yj, xi, yi, px, py) > 0)
1751
1789
  wn++;
1752
- }
1753
1790
  } else {
1754
- if (yj <= y && cross([xj, yj], [xi, yi], [x, y]) < 0) {
1791
+ if (yj <= py && cross(xj, yj, xi, yi, px, py) < 0)
1755
1792
  wn--;
1756
- }
1757
1793
  }
1758
1794
  }
1759
- return wn !== 0;
1795
+ return wn;
1760
1796
  }
1761
- function cross(p0, p1, p2) {
1762
- return (p1[0] - p0[0]) * (p2[1] - p0[1]) - (p2[0] - p0[0]) * (p1[1] - p0[1]);
1797
+ function nonzeroFillRule(paths) {
1798
+ const pathsLen = paths.length;
1799
+ const results = paths.map((_, i) => ({ index: i, parentIndex: null, wn: 0 }));
1800
+ for (let i = 0; i < pathsLen; i++) {
1801
+ let best = null;
1802
+ if (signedArea(paths[i]) < 0) {
1803
+ continue;
1804
+ }
1805
+ const points = [
1806
+ ...centroid(paths[i])
1807
+ ];
1808
+ for (let j = 0; j < pathsLen; j++) {
1809
+ if (i === j) {
1810
+ continue;
1811
+ }
1812
+ let wn0 = 0;
1813
+ for (let p = 0; p < points.length; p += 2) {
1814
+ wn0 = wn0 || windingNumber(points[p], points[p + 1], paths[j]);
1815
+ if (wn0) {
1816
+ break;
1817
+ }
1818
+ }
1819
+ const absWn = Math.abs(wn0);
1820
+ if (absWn !== 0 && (!best || absWn > Math.abs(best.wn))) {
1821
+ best = { idx: j, wn: wn0 };
1822
+ }
1823
+ }
1824
+ if (best) {
1825
+ results[i].parentIndex = best.idx;
1826
+ results[i].wn = best.wn;
1827
+ }
1828
+ }
1829
+ return results;
1763
1830
  }
1764
1831
 
1765
1832
  function quadraticBezierP0(t, p) {
@@ -3652,33 +3719,6 @@ class CurvePath extends CompositeCurve {
3652
3719
  }
3653
3720
  }
3654
3721
 
3655
- function toKebabCase(str) {
3656
- return str.replace(/[^a-z0-9]/gi, "-").replace(/\B([A-Z])/g, "-$1").toLowerCase();
3657
- }
3658
- function getIntersectionPoint(p1, p2, q1, q2) {
3659
- const r = p2.clone().sub(p1);
3660
- const s = q2.clone().sub(q1);
3661
- const q1p1 = q1.clone().sub(p1);
3662
- const crossRS = r.cross(s);
3663
- if (crossRS === 0) {
3664
- return new Vector2(
3665
- (p1.x + q1.x) / 2,
3666
- (p1.y + q1.y) / 2
3667
- );
3668
- }
3669
- const t = q1p1.cross(s) / crossRS;
3670
- if (Math.abs(t) > 1) {
3671
- return new Vector2(
3672
- (p1.x + q1.x) / 2,
3673
- (p1.y + q1.y) / 2
3674
- );
3675
- }
3676
- return new Vector2(
3677
- p1.x + t * r.x,
3678
- p1.y + t * r.y
3679
- );
3680
- }
3681
-
3682
3722
  class Path2D extends CompositeCurve {
3683
3723
  currentCurve = new CurvePath();
3684
3724
  style;
@@ -3920,73 +3960,28 @@ class Path2D extends CompositeCurve {
3920
3960
  ...options?.style
3921
3961
  }
3922
3962
  };
3923
- function signedArea(pts) {
3924
- let sum = 0;
3925
- const len = pts.length / 2;
3926
- for (let i = 0; i < len; i++) {
3927
- const xi = pts[2 * i];
3928
- const yi = pts[2 * i + 1];
3929
- const j = (i + 1) % len;
3930
- const xj = pts[2 * j];
3931
- const yj = pts[2 * j + 1];
3932
- sum += (xj - xi) * (yj + yi);
3933
- }
3934
- return sum;
3935
- }
3936
- function isHoleFlat(pts) {
3937
- return signedArea(pts) > 0;
3938
- }
3939
3963
  const indices = _options.indices ?? [];
3940
3964
  const vertices = _options.vertices ?? [];
3941
3965
  const fillRule = _options.style.fillRule ?? "nonzero";
3942
3966
  if (fillRule === "nonzero") {
3943
- const pointArrays = this.curves.map((curve) => curve.getFillVertices(_options));
3944
- const parentMap = /* @__PURE__ */ new Map();
3945
- const parentd = /* @__PURE__ */ new Set();
3946
- for (let i = 0; i < pointArrays.length; i++) {
3947
- if (!isHoleFlat(pointArrays[i])) {
3967
+ const paths = this.curves.map((curve) => curve.getFillVertices(_options));
3968
+ const groups = nonzeroFillRule(paths);
3969
+ const groupsLen = groups.length;
3970
+ for (let i = 0; i < groupsLen; i++) {
3971
+ const groupA = groups[i];
3972
+ const pointArray = paths[i];
3973
+ if (groupA.wn || !pointArray.length) {
3948
3974
  continue;
3949
3975
  }
3950
- const parents = [];
3951
- for (let j = 0; j < pointArrays.length; j++) {
3952
- if (i === j || isHoleFlat(pointArrays[j]))
3953
- continue;
3954
- let flag = false;
3955
- for (let k = 0; k < pointArrays[i].length; k += 2) {
3956
- flag = flag || pointInPolygonNonZero(
3957
- [pointArrays[i][k], pointArrays[i][k + 1]],
3958
- pointArrays[j]
3959
- );
3960
- if (flag) {
3961
- break;
3962
- }
3963
- }
3964
- if (flag) {
3965
- parents.push(j);
3966
- }
3967
- }
3968
- if (parents.length) {
3969
- parents.forEach((pi) => {
3970
- let set = parentMap.get(pi);
3971
- if (!set) {
3972
- set = /* @__PURE__ */ new Set();
3973
- parentMap.set(pi, set);
3974
- }
3975
- set.add(i);
3976
- });
3977
- parentd.add(i);
3978
- }
3979
- }
3980
- pointArrays.forEach((pointArray, i) => {
3981
- if (parentd.has(i) || !pointArray.length) {
3982
- return;
3983
- }
3984
3976
  const _pointArray = pointArray.slice();
3985
3977
  const holes = [];
3986
- parentMap.get(i)?.forEach((ci) => {
3987
- holes.push(_pointArray.length / 2);
3988
- _pointArray.push(...pointArrays[ci]);
3989
- });
3978
+ for (let j = 0; j < groupsLen; j++) {
3979
+ const groupB = groups[j];
3980
+ if (groupB.parentIndex === i) {
3981
+ holes.push(_pointArray.length / 2);
3982
+ _pointArray.push(...paths[groupB.index]);
3983
+ }
3984
+ }
3990
3985
  fillTriangulate(_pointArray, {
3991
3986
  ...options,
3992
3987
  indices,
@@ -3994,7 +3989,7 @@ class Path2D extends CompositeCurve {
3994
3989
  holes,
3995
3990
  style: { ...this.style }
3996
3991
  });
3997
- });
3992
+ }
3998
3993
  } else {
3999
3994
  this.curves.forEach((curve) => {
4000
3995
  curve.fillTriangulate({
@@ -4117,7 +4112,7 @@ class Path2DSet {
4117
4112
  const p1 = getPoint(indices[i]);
4118
4113
  const p2 = getPoint(indices[i + 1]);
4119
4114
  const p3 = getPoint(indices[i + 2]);
4120
- polygonStr += `<polygon points="${p1.join(",")} ${p2.join(",")} ${p3.join(",")}" fill="black" />`;
4115
+ polygonStr += `<polygon points="${p1.join(",")} ${p2.join(",")} ${p3.join(",")}" stroke="none" fill="black" />`;
4121
4116
  }
4122
4117
  });
4123
4118
  const viewBox = [min.x, min.y, max.x - min.x, max.y - min.y];
@@ -4239,10 +4234,10 @@ exports.fillTriangulate = fillTriangulate;
4239
4234
  exports.getAdaptiveCubicBezierCurvePoints = getAdaptiveCubicBezierCurvePoints;
4240
4235
  exports.getAdaptiveQuadraticBezierCurvePoints = getAdaptiveQuadraticBezierCurvePoints;
4241
4236
  exports.getDirectedArea = getDirectedArea;
4237
+ exports.getIntersectionPoint = getIntersectionPoint;
4238
+ exports.nonzeroFillRule = nonzeroFillRule;
4242
4239
  exports.parseArcCommand = parseArcCommand;
4243
4240
  exports.parsePathDataArgs = parsePathDataArgs;
4244
- exports.pointInPolygonEvenOdd = pointInPolygonEvenOdd;
4245
- exports.pointInPolygonNonZero = pointInPolygonNonZero;
4246
4241
  exports.quadraticBezier = quadraticBezier;
4247
4242
  exports.setCanvasContext = setCanvasContext;
4248
4243
  exports.strokeTriangulate = strokeTriangulate;
@@ -4251,3 +4246,4 @@ exports.svgPathCommandsToData = svgPathCommandsToData;
4251
4246
  exports.svgPathDataToCommands = svgPathDataToCommands;
4252
4247
  exports.svgToDom = svgToDom;
4253
4248
  exports.svgToPath2DSet = svgToPath2DSet;
4249
+ exports.toKebabCase = toKebabCase;
package/dist/index.d.cts CHANGED
@@ -3,6 +3,93 @@ interface DrawPointOptions {
3
3
  }
4
4
  declare function drawPoint(ctx: CanvasRenderingContext2D, x: number, y: number, options?: DrawPointOptions): void;
5
5
 
6
+ /**
7
+ * @link https://developer.mozilla.org/docs/Web/SVG/Attribute/d
8
+ */
9
+ type Path2DCommand = {
10
+ type: 'm' | 'M';
11
+ x: number;
12
+ y: number;
13
+ } | {
14
+ type: 'h' | 'H';
15
+ x: number;
16
+ } | {
17
+ type: 'v' | 'V';
18
+ y: number;
19
+ } | {
20
+ type: 'l' | 'L';
21
+ x: number;
22
+ y: number;
23
+ } | {
24
+ type: 'c' | 'C';
25
+ x1: number;
26
+ y1: number;
27
+ x2: number;
28
+ y2: number;
29
+ x: number;
30
+ y: number;
31
+ } | {
32
+ type: 's' | 'S';
33
+ x2: number;
34
+ y2: number;
35
+ x: number;
36
+ y: number;
37
+ } | {
38
+ type: 'q' | 'Q';
39
+ x1: number;
40
+ y1: number;
41
+ x: number;
42
+ y: number;
43
+ } | {
44
+ type: 't' | 'T';
45
+ x: number;
46
+ y: number;
47
+ } | {
48
+ type: 'a' | 'A';
49
+ rx: number;
50
+ ry: number;
51
+ angle: number;
52
+ largeArcFlag: number;
53
+ sweepFlag: number;
54
+ x: number;
55
+ y: number;
56
+ } | {
57
+ type: 'z' | 'Z';
58
+ };
59
+ /**
60
+ * SVG path data
61
+ *
62
+ * @link https://developer.mozilla.org/docs/Web/SVG/Attribute/d
63
+ */
64
+ type Path2DData = string;
65
+ type FillRule = 'nonzero' | 'evenodd';
66
+ type StrokeLinecap = 'butt' | 'round' | 'square';
67
+ type StrokeLinejoin = 'arcs' | 'bevel' | 'miter' | 'miter-clip' | 'round';
68
+ interface Path2DDrawStyle {
69
+ fill: string | any;
70
+ stroke: string | any;
71
+ shadowColor: string;
72
+ shadowOffsetX: number;
73
+ shadowOffsetY: number;
74
+ shadowBlur: number;
75
+ }
76
+ interface Path2DStyle extends Path2DDrawStyle {
77
+ [key: string]: any;
78
+ fillOpacity: number;
79
+ fillRule: FillRule;
80
+ opacity: number;
81
+ strokeOpacity: number;
82
+ strokeWidth: number;
83
+ strokeLinecap: StrokeLinecap;
84
+ strokeLinejoin: StrokeLinejoin;
85
+ strokeMiterlimit: number;
86
+ strokeDasharray: number[];
87
+ strokeDashoffset: number;
88
+ visibility: string;
89
+ }
90
+
91
+ declare function setCanvasContext(ctx: CanvasRenderingContext2D, style: Partial<Path2DStyle>): void;
92
+
6
93
  declare class Matrix3 {
7
94
  elements: number[];
8
95
  constructor(n11?: number, n12?: number, n13?: number, n21?: number, n22?: number, n23?: number, n31?: number, n32?: number, n33?: number);
@@ -109,8 +196,15 @@ declare function getAdaptiveQuadraticBezierCurvePoints(sX: number, sY: number, x
109
196
 
110
197
  declare function getDirectedArea(vertices: number[]): number;
111
198
 
112
- declare function pointInPolygonEvenOdd(point: number[], polygon: number[]): boolean;
113
- declare function pointInPolygonNonZero(point: number[], polygon: number[]): boolean;
199
+ declare function toKebabCase(str: string): string;
200
+ declare function getIntersectionPoint(p1: Vector2, p2: Vector2, q1: Vector2, q2: Vector2): Vector2;
201
+
202
+ interface Grouping {
203
+ index: number;
204
+ parentIndex: number | null;
205
+ wn: number;
206
+ }
207
+ declare function nonzeroFillRule(paths: number[][]): Grouping[];
114
208
 
115
209
  declare function quadraticBezier(t: number, p0: number, p1: number, p2: number): number;
116
210
 
@@ -349,60 +443,6 @@ declare class SplineCurve extends Curve {
349
443
  copy(source: SplineCurve): this;
350
444
  }
351
445
 
352
- /**
353
- * @link https://developer.mozilla.org/docs/Web/SVG/Attribute/d
354
- */
355
- type Path2DCommand = {
356
- type: 'm' | 'M';
357
- x: number;
358
- y: number;
359
- } | {
360
- type: 'h' | 'H';
361
- x: number;
362
- } | {
363
- type: 'v' | 'V';
364
- y: number;
365
- } | {
366
- type: 'l' | 'L';
367
- x: number;
368
- y: number;
369
- } | {
370
- type: 'c' | 'C';
371
- x1: number;
372
- y1: number;
373
- x2: number;
374
- y2: number;
375
- x: number;
376
- y: number;
377
- } | {
378
- type: 's' | 'S';
379
- x2: number;
380
- y2: number;
381
- x: number;
382
- y: number;
383
- } | {
384
- type: 'q' | 'Q';
385
- x1: number;
386
- y1: number;
387
- x: number;
388
- y: number;
389
- } | {
390
- type: 't' | 'T';
391
- x: number;
392
- y: number;
393
- } | {
394
- type: 'a' | 'A';
395
- rx: number;
396
- ry: number;
397
- angle: number;
398
- largeArcFlag: number;
399
- sweepFlag: number;
400
- x: number;
401
- y: number;
402
- } | {
403
- type: 'z' | 'Z';
404
- };
405
-
406
446
  declare class CurvePath extends CompositeCurve {
407
447
  startPoint?: Vector2;
408
448
  currentPoint?: Vector2;
@@ -435,39 +475,6 @@ declare class CurvePath extends CompositeCurve {
435
475
  copy(source: CurvePath): this;
436
476
  }
437
477
 
438
- /**
439
- * Svg path data
440
- *
441
- * @link https://developer.mozilla.org/docs/Web/SVG/Attribute/d
442
- */
443
- type Path2DData = string;
444
-
445
- type FillRule = 'nonzero' | 'evenodd';
446
- type StrokeLinecap = 'butt' | 'round' | 'square';
447
- type StrokeLinejoin = 'arcs' | 'bevel' | 'miter' | 'miter-clip' | 'round';
448
- interface Path2DDrawStyle {
449
- fill: string | any;
450
- stroke: string | any;
451
- shadowColor: string;
452
- shadowOffsetX: number;
453
- shadowOffsetY: number;
454
- shadowBlur: number;
455
- }
456
- interface Path2DStyle extends Path2DDrawStyle {
457
- [key: string]: any;
458
- fillOpacity: number;
459
- fillRule: FillRule;
460
- opacity: number;
461
- strokeOpacity: number;
462
- strokeWidth: number;
463
- strokeLinecap: StrokeLinecap;
464
- strokeLinejoin: StrokeLinejoin;
465
- strokeMiterlimit: number;
466
- strokeDasharray: number[];
467
- strokeDashoffset: number;
468
- visibility: string;
469
- }
470
-
471
478
  /**
472
479
  * @link https://developer.mozilla.org/zh-CN/docs/Web/API/Path2D
473
480
  *
@@ -534,8 +541,6 @@ declare class Path2DSet {
534
541
  }>): HTMLCanvasElement;
535
542
  }
536
543
 
537
- declare function setCanvasContext(ctx: CanvasRenderingContext2D, style: Partial<Path2DStyle>): void;
538
-
539
544
  declare class FFDControlGrid {
540
545
  rows: number;
541
546
  cols: number;
@@ -578,5 +583,5 @@ declare function svgToDom(svg: string | SVGElement): SVGElement;
578
583
 
579
584
  declare function svgToPath2DSet(svg: string | SVGElement): Path2DSet;
580
585
 
581
- export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, EllipseCurve, EquilateralPloygonCurve, FFDControlGrid, LineCurve, Matrix3, Path2D, Path2DSet, PloygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, Vector2, applyFFD, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, getDirectedArea, parseArcCommand, parsePathDataArgs, pointInPolygonEvenOdd, pointInPolygonNonZero, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDom, svgToPath2DSet };
586
+ export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, EllipseCurve, EquilateralPloygonCurve, FFDControlGrid, LineCurve, Matrix3, Path2D, Path2DSet, PloygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, Vector2, applyFFD, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, getDirectedArea, getIntersectionPoint, nonzeroFillRule, parseArcCommand, parsePathDataArgs, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDom, svgToPath2DSet, toKebabCase };
582
587
  export type { DrawPointOptions, FillRule, FillTriangulateOptions, FillTriangulatedResult, LineCap, LineJoin, LineStyle, Path2DCommand, Path2DData, Path2DDrawStyle, Path2DStyle, StrokeLinecap, StrokeLinejoin, StrokeTriangulateOptions, StrokeTriangulatedResult, VectorLike };