modern-path2d 1.8.0 → 1.8.2

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
@@ -485,6 +485,195 @@ function cubicBezier(t, p0, p1, p2, p3) {
485
485
  return cubicBezierP0(t, p0) + cubicBezierP1(t, p1) + cubicBezierP2(t, p2) + cubicBezierP3(t, p3);
486
486
  }
487
487
 
488
+ function isLeft(ax, ay, bx, by, px, py) {
489
+ return (bx - ax) * (py - ay) - (px - ax) * (by - ay);
490
+ }
491
+ function windingNumber$1(px, py, vertices) {
492
+ const len = vertices.length;
493
+ let wn = 0;
494
+ for (let i = 0; i < len; i += 2) {
495
+ const ax = vertices[i];
496
+ const ay = vertices[i + 1];
497
+ const k = (i + 2) % len;
498
+ const bx = vertices[k];
499
+ const by = vertices[k + 1];
500
+ if (ay <= py) {
501
+ if (by > py && isLeft(ax, ay, bx, by, px, py) > 0) {
502
+ wn++;
503
+ }
504
+ } else {
505
+ if (by <= py && isLeft(ax, ay, bx, by, px, py) < 0) {
506
+ wn--;
507
+ }
508
+ }
509
+ }
510
+ return wn;
511
+ }
512
+ function crossingNumber(px, py, vertices) {
513
+ const len = vertices.length;
514
+ let cn = 0;
515
+ for (let i = 0; i < len; i += 2) {
516
+ const ax = vertices[i];
517
+ const ay = vertices[i + 1];
518
+ const k = (i + 2) % len;
519
+ const bx = vertices[k];
520
+ const by = vertices[k + 1];
521
+ if (ay <= py && by > py || ay > py && by <= py) {
522
+ const t = (py - ay) / (by - ay);
523
+ if (px < ax + t * (bx - ax)) {
524
+ cn++;
525
+ }
526
+ }
527
+ }
528
+ return cn;
529
+ }
530
+ function segmentDistance(px, py, ax, ay, bx, by) {
531
+ const dx = bx - ax;
532
+ const dy = by - ay;
533
+ const lenSq = dx * dx + dy * dy;
534
+ let t = lenSq === 0 ? 0 : ((px - ax) * dx + (py - ay) * dy) / lenSq;
535
+ if (t < 0) {
536
+ t = 0;
537
+ } else if (t > 1) {
538
+ t = 1;
539
+ }
540
+ const cx = ax + t * dx;
541
+ const cy = ay + t * dy;
542
+ return Math.hypot(px - cx, py - cy);
543
+ }
544
+ function pointInPolygon(point, vertices, fillRule = "nonzero") {
545
+ if (vertices.length < 6) {
546
+ return false;
547
+ }
548
+ if (fillRule === "evenodd") {
549
+ return (crossingNumber(point.x, point.y, vertices) & 1) === 1;
550
+ }
551
+ return windingNumber$1(point.x, point.y, vertices) !== 0;
552
+ }
553
+ function pointInPolygons(point, polygons, fillRule = "nonzero") {
554
+ const { x, y } = point;
555
+ if (fillRule === "evenodd") {
556
+ let cn = 0;
557
+ for (let i = 0, len = polygons.length; i < len; i++) {
558
+ const ring = polygons[i];
559
+ if (ring.length >= 6) {
560
+ cn += crossingNumber(x, y, ring);
561
+ }
562
+ }
563
+ return (cn & 1) === 1;
564
+ }
565
+ let wn = 0;
566
+ for (let i = 0, len = polygons.length; i < len; i++) {
567
+ const ring = polygons[i];
568
+ if (ring.length >= 6) {
569
+ wn += windingNumber$1(x, y, ring);
570
+ }
571
+ }
572
+ return wn !== 0;
573
+ }
574
+ function pointToSegmentDistance(point, a, b) {
575
+ return segmentDistance(point.x, point.y, a.x, a.y, b.x, b.y);
576
+ }
577
+ function pointToPolylineDistance(point, vertices, closed = false) {
578
+ const len = vertices.length;
579
+ if (len < 2) {
580
+ return Infinity;
581
+ }
582
+ const { x: px, y: py } = point;
583
+ if (len === 2) {
584
+ return Math.hypot(px - vertices[0], py - vertices[1]);
585
+ }
586
+ let min = Infinity;
587
+ for (let i = 0; i < len - 2; i += 2) {
588
+ const d = segmentDistance(px, py, vertices[i], vertices[i + 1], vertices[i + 2], vertices[i + 3]);
589
+ if (d < min) {
590
+ min = d;
591
+ }
592
+ }
593
+ if (closed && len >= 6) {
594
+ const d = segmentDistance(px, py, vertices[len - 2], vertices[len - 1], vertices[0], vertices[1]);
595
+ if (d < min) {
596
+ min = d;
597
+ }
598
+ }
599
+ return min;
600
+ }
601
+
602
+ function boundsOf(ring) {
603
+ if (ring.length < 6) {
604
+ return null;
605
+ }
606
+ let minX = Infinity;
607
+ let minY = Infinity;
608
+ let maxX = -Infinity;
609
+ let maxY = -Infinity;
610
+ for (let i = 0; i < ring.length; i += 2) {
611
+ const x = ring[i];
612
+ const y = ring[i + 1];
613
+ if (x < minX)
614
+ minX = x;
615
+ if (y < minY)
616
+ minY = y;
617
+ if (x > maxX)
618
+ maxX = x;
619
+ if (y > maxY)
620
+ maxY = y;
621
+ }
622
+ return { minX, minY, maxX, maxY };
623
+ }
624
+ function bboxInside(inner, outer) {
625
+ return inner.minX >= outer.minX && inner.maxX <= outer.maxX && inner.minY >= outer.minY && inner.maxY <= outer.maxY;
626
+ }
627
+ function ringInsideRing(inner, outer) {
628
+ const n = inner.length / 2;
629
+ const step = Math.max(1, Math.floor(n / 9));
630
+ let tested = 0;
631
+ let inside = 0;
632
+ for (let i = 0; i < n; i += step) {
633
+ tested++;
634
+ if (pointInPolygon({ x: inner[i * 2], y: inner[i * 2 + 1] }, outer, "evenodd")) {
635
+ inside++;
636
+ }
637
+ }
638
+ return tested > 0 && inside * 2 > tested;
639
+ }
640
+ function evenoddFillRule(paths) {
641
+ const len = paths.length;
642
+ const bboxes = paths.map(boundsOf);
643
+ const depth = Array.from({ length: len }).fill(0);
644
+ const containers = paths.map(() => []);
645
+ for (let i = 0; i < len; i++) {
646
+ const bi = bboxes[i];
647
+ if (!bi) {
648
+ continue;
649
+ }
650
+ for (let j = 0; j < len; j++) {
651
+ if (i === j) {
652
+ continue;
653
+ }
654
+ const bj = bboxes[j];
655
+ if (!bj || !bboxInside(bi, bj)) {
656
+ continue;
657
+ }
658
+ if (ringInsideRing(paths[i], paths[j])) {
659
+ depth[i]++;
660
+ containers[i].push(j);
661
+ }
662
+ }
663
+ }
664
+ return paths.map((_, i) => {
665
+ let parentIndex = -1;
666
+ let bestDepth = -1;
667
+ for (const j of containers[i]) {
668
+ if (depth[j] > bestDepth) {
669
+ bestDepth = depth[j];
670
+ parentIndex = j;
671
+ }
672
+ }
673
+ return { index: i, depth: depth[i], parentIndex };
674
+ });
675
+ }
676
+
488
677
  function fillTriangulate(pointArray, options = {}) {
489
678
  let {
490
679
  vertices = [],
@@ -647,7 +836,7 @@ function getDirectedArea(vertices) {
647
836
  function cross(ax, ay, bx, by, cx, cy) {
648
837
  return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
649
838
  }
650
- function windingNumber$1(px, py, polygon) {
839
+ function windingNumber(px, py, polygon) {
651
840
  const polygonLen = polygon.length;
652
841
  let wn = 0;
653
842
  for (let i = 0, j = polygonLen - 2; i < polygonLen; j = i, i += 2) {
@@ -768,7 +957,7 @@ function nonzeroFillRule(paths) {
768
957
  const wnList = [];
769
958
  for (let p = 0, pLen = testPoints.length; p < pLen; p++) {
770
959
  const [x, y] = testPoints[p];
771
- const winding = windingNumber$1(x, y, paths[j]);
960
+ const winding = windingNumber(x, y, paths[j]);
772
961
  wnMap[winding] = (wnMap[winding] ?? 0) + 1;
773
962
  wnList.push(winding);
774
963
  }
@@ -791,120 +980,6 @@ function nonzeroFillRule(paths) {
791
980
  return results;
792
981
  }
793
982
 
794
- function isLeft(ax, ay, bx, by, px, py) {
795
- return (bx - ax) * (py - ay) - (px - ax) * (by - ay);
796
- }
797
- function windingNumber(px, py, vertices) {
798
- const len = vertices.length;
799
- let wn = 0;
800
- for (let i = 0; i < len; i += 2) {
801
- const ax = vertices[i];
802
- const ay = vertices[i + 1];
803
- const k = (i + 2) % len;
804
- const bx = vertices[k];
805
- const by = vertices[k + 1];
806
- if (ay <= py) {
807
- if (by > py && isLeft(ax, ay, bx, by, px, py) > 0) {
808
- wn++;
809
- }
810
- } else {
811
- if (by <= py && isLeft(ax, ay, bx, by, px, py) < 0) {
812
- wn--;
813
- }
814
- }
815
- }
816
- return wn;
817
- }
818
- function crossingNumber(px, py, vertices) {
819
- const len = vertices.length;
820
- let cn = 0;
821
- for (let i = 0; i < len; i += 2) {
822
- const ax = vertices[i];
823
- const ay = vertices[i + 1];
824
- const k = (i + 2) % len;
825
- const bx = vertices[k];
826
- const by = vertices[k + 1];
827
- if (ay <= py && by > py || ay > py && by <= py) {
828
- const t = (py - ay) / (by - ay);
829
- if (px < ax + t * (bx - ax)) {
830
- cn++;
831
- }
832
- }
833
- }
834
- return cn;
835
- }
836
- function segmentDistance(px, py, ax, ay, bx, by) {
837
- const dx = bx - ax;
838
- const dy = by - ay;
839
- const lenSq = dx * dx + dy * dy;
840
- let t = lenSq === 0 ? 0 : ((px - ax) * dx + (py - ay) * dy) / lenSq;
841
- if (t < 0) {
842
- t = 0;
843
- } else if (t > 1) {
844
- t = 1;
845
- }
846
- const cx = ax + t * dx;
847
- const cy = ay + t * dy;
848
- return Math.hypot(px - cx, py - cy);
849
- }
850
- function pointInPolygon(point, vertices, fillRule = "nonzero") {
851
- if (vertices.length < 6) {
852
- return false;
853
- }
854
- if (fillRule === "evenodd") {
855
- return (crossingNumber(point.x, point.y, vertices) & 1) === 1;
856
- }
857
- return windingNumber(point.x, point.y, vertices) !== 0;
858
- }
859
- function pointInPolygons(point, polygons, fillRule = "nonzero") {
860
- const { x, y } = point;
861
- if (fillRule === "evenodd") {
862
- let cn = 0;
863
- for (let i = 0, len = polygons.length; i < len; i++) {
864
- const ring = polygons[i];
865
- if (ring.length >= 6) {
866
- cn += crossingNumber(x, y, ring);
867
- }
868
- }
869
- return (cn & 1) === 1;
870
- }
871
- let wn = 0;
872
- for (let i = 0, len = polygons.length; i < len; i++) {
873
- const ring = polygons[i];
874
- if (ring.length >= 6) {
875
- wn += windingNumber(x, y, ring);
876
- }
877
- }
878
- return wn !== 0;
879
- }
880
- function pointToSegmentDistance(point, a, b) {
881
- return segmentDistance(point.x, point.y, a.x, a.y, b.x, b.y);
882
- }
883
- function pointToPolylineDistance(point, vertices, closed = false) {
884
- const len = vertices.length;
885
- if (len < 2) {
886
- return Infinity;
887
- }
888
- const { x: px, y: py } = point;
889
- if (len === 2) {
890
- return Math.hypot(px - vertices[0], py - vertices[1]);
891
- }
892
- let min = Infinity;
893
- for (let i = 0; i < len - 2; i += 2) {
894
- const d = segmentDistance(px, py, vertices[i], vertices[i + 1], vertices[i + 2], vertices[i + 3]);
895
- if (d < min) {
896
- min = d;
897
- }
898
- }
899
- if (closed && len >= 6) {
900
- const d = segmentDistance(px, py, vertices[len - 2], vertices[len - 1], vertices[0], vertices[1]);
901
- if (d < min) {
902
- min = d;
903
- }
904
- }
905
- return min;
906
- }
907
-
908
983
  function quadraticBezierP0(t, p) {
909
984
  const k = 1 - t;
910
985
  return k * k * p;
@@ -952,6 +1027,10 @@ function strokeTriangulate(points, options = {}) {
952
1027
  if (points.length === 0) {
953
1028
  return { vertices, indices };
954
1029
  }
1030
+ points = dedupeConsecutivePoints(points, eps);
1031
+ if (points.length < 4) {
1032
+ return { vertices, indices };
1033
+ }
955
1034
  const style = lineStyle;
956
1035
  let alignment = style.alignment;
957
1036
  if (lineStyle.alignment !== 0.5) {
@@ -1241,6 +1320,17 @@ function strokeTriangulate(points, options = {}) {
1241
1320
  indices
1242
1321
  };
1243
1322
  }
1323
+ function dedupeConsecutivePoints(points, eps) {
1324
+ const out = [points[0], points[1]];
1325
+ for (let i = 2; i < points.length; i += 2) {
1326
+ const x = points[i];
1327
+ const y = points[i + 1];
1328
+ if (Math.abs(x - out[out.length - 2]) >= eps || Math.abs(y - out[out.length - 1]) >= eps) {
1329
+ out.push(x, y);
1330
+ }
1331
+ }
1332
+ return out;
1333
+ }
1244
1334
  function getOrientationOfPoints(points) {
1245
1335
  const m = points.length;
1246
1336
  if (m < 6) {
@@ -4935,14 +5025,30 @@ class Path2D extends CompositeCurve {
4935
5025
  });
4936
5026
  }
4937
5027
  } else {
4938
- this.curves.forEach((curve) => {
4939
- curve.fillTriangulate({
5028
+ const paths = this.curves.map((curve) => curve.getFillVertices(_options));
5029
+ const groups = evenoddFillRule(paths);
5030
+ const groupsLen = groups.length;
5031
+ for (let i = 0; i < groupsLen; i++) {
5032
+ const pointArray = paths[i];
5033
+ if ((groups[i].depth & 1) === 1 || !pointArray.length) {
5034
+ continue;
5035
+ }
5036
+ const _pointArray = pointArray.slice();
5037
+ const holes = [];
5038
+ for (let j = 0; j < groupsLen; j++) {
5039
+ if (groups[j].parentIndex === i && (groups[j].depth & 1) === 1) {
5040
+ holes.push(_pointArray.length / 2);
5041
+ _pointArray.push(...paths[j]);
5042
+ }
5043
+ }
5044
+ fillTriangulate(_pointArray, {
4940
5045
  ...options,
4941
5046
  indices,
4942
5047
  vertices,
5048
+ holes,
4943
5049
  style: { ...this.style }
4944
5050
  });
4945
- });
5051
+ }
4946
5052
  }
4947
5053
  return { indices, vertices };
4948
5054
  }
@@ -5259,4 +5365,4 @@ function applyFFD(point, grid, width = grid.width, height = grid.height) {
5259
5365
  point.set(x, y);
5260
5366
  }
5261
5367
 
5262
- export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, EllipseCurve, EquilateralPolygonCurve, FFDControlGrid, LineCurve, PI, PI_2, Path2D, Path2DSet, PathMeasure, 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, polygonBoolean, quadraticBezier, resolveLineStyle, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDom, svgToPath2DSet, toKebabCase };
5368
+ export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, EllipseCurve, EquilateralPolygonCurve, FFDControlGrid, LineCurve, PI, PI_2, Path2D, Path2DSet, PathMeasure, PolygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, Transform2D, Vector2, applyFFD, catmullRom, cubicBezier, drawPoint, evenoddFillRule, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, getDirectedArea, getIntersectionPoint, nonzeroFillRule, parseArcCommand, parseCssArg, parseCssArgs, parseCssFunctions, parsePathDataArgs, pointInPolygon, pointInPolygons, pointToPolylineDistance, pointToSegmentDistance, polygonBoolean, quadraticBezier, resolveLineStyle, 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.8.0",
4
+ "version": "1.8.2",
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",