modern-path2d 0.1.18 → 0.2.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/dist/index.cjs CHANGED
@@ -62,6 +62,34 @@ class Vector2 {
62
62
  this.y -= vec.y;
63
63
  return this;
64
64
  }
65
+ multiply(vec) {
66
+ this.x *= vec.x;
67
+ this.y *= vec.y;
68
+ return this;
69
+ }
70
+ divide(vec) {
71
+ this.x /= vec.x;
72
+ this.y /= vec.y;
73
+ return this;
74
+ }
75
+ dot(vec) {
76
+ return this.x * vec.x + this.y * vec.y;
77
+ }
78
+ cross(vec) {
79
+ return this.x * vec.y - this.y * vec.x;
80
+ }
81
+ rotate(a, target = { x: 0, y: 0 }) {
82
+ const rotation = -a / 180 * Math.PI;
83
+ const x = this.x - target.x;
84
+ const y = -(this.y - target.y);
85
+ const sin = Math.sin(rotation);
86
+ const cos = Math.cos(rotation);
87
+ this.set(
88
+ target.x + (x * cos - y * sin),
89
+ target.y - (x * sin + y * cos)
90
+ );
91
+ return this;
92
+ }
65
93
  distanceTo(vec) {
66
94
  return Math.sqrt(this.distanceToSquared(vec));
67
95
  }
@@ -70,24 +98,48 @@ class Vector2 {
70
98
  const dy = this.y - vec.y;
71
99
  return dx * dx + dy * dy;
72
100
  }
101
+ lengthSquared() {
102
+ return this.x * this.x + this.y * this.y;
103
+ }
73
104
  length() {
74
- return Math.sqrt(this.x * this.x + this.y * this.y);
105
+ return Math.sqrt(this.lengthSquared());
75
106
  }
76
- multiplyScalar(scalar) {
77
- this.x *= scalar;
78
- this.y *= scalar;
107
+ scale(sx, sy = sx, target = { x: 0, y: 0 }) {
108
+ const x = sx < 0 ? target.x - this.x + target.x : this.x;
109
+ const y = sy < 0 ? target.y - this.y + target.y : this.y;
110
+ this.x = x * Math.abs(sx);
111
+ this.y = y * Math.abs(sy);
79
112
  return this;
80
113
  }
81
- divideScalar(scalar) {
82
- return this.multiplyScalar(1 / scalar);
114
+ skew(ax, ay = 0, target = { x: 0, y: 0 }) {
115
+ const dx = this.x - target.x;
116
+ const dy = this.y - target.y;
117
+ this.x = target.x + (dx + Math.tan(ax) * dy);
118
+ this.y = target.y + (dy + Math.tan(ay) * dx);
119
+ return this;
120
+ }
121
+ normalize() {
122
+ return this.scale(1 / (this.length() || 1));
123
+ }
124
+ addVectors(a, b) {
125
+ this.x = a.x + b.x;
126
+ this.y = a.y + b.y;
127
+ return this;
83
128
  }
84
129
  subVectors(a, b) {
85
130
  this.x = a.x - b.x;
86
131
  this.y = a.y - b.y;
87
132
  return this;
88
133
  }
89
- normalize() {
90
- return this.divideScalar(this.length() || 1);
134
+ multiplyVectors(a, b) {
135
+ this.x = a.x * b.x;
136
+ this.y = a.y * b.y;
137
+ return this;
138
+ }
139
+ divideVectors(a, b) {
140
+ this.x = a.x / b.x;
141
+ this.y = a.y / b.y;
142
+ return this;
91
143
  }
92
144
  lerpVectors(v1, v2, alpha) {
93
145
  this.x = v1.x + (v2.x - v1.x) * alpha;
@@ -915,8 +967,14 @@ class Curve {
915
967
  __publicField$5(this, "_cacheArcLengths");
916
968
  __publicField$5(this, "_needsUpdate", false);
917
969
  }
970
+ isClockwise() {
971
+ const prev = this.getPoint(1);
972
+ const cur = this.getPoint(0.5);
973
+ const next = this.getPoint(1);
974
+ return (cur.x - prev.x) * (next.y - cur.y) - (cur.y - prev.y) * (next.x - cur.x) < 0;
975
+ }
918
976
  getPointAt(u, output = new Vector2()) {
919
- return this.getPoint(this.getUtoTmapping(u), output);
977
+ return this.getPoint(this.getUToTMapping(u), output);
920
978
  }
921
979
  getPoints(divisions = 5) {
922
980
  const points = [];
@@ -925,6 +983,10 @@ class Curve {
925
983
  }
926
984
  return points;
927
985
  }
986
+ forEachControlPoints(cb) {
987
+ this.getControlPoints().forEach(cb);
988
+ return this;
989
+ }
928
990
  getSpacedPoints(divisions = 5) {
929
991
  const points = [];
930
992
  for (let i = 0; i <= divisions; i++) {
@@ -959,22 +1021,22 @@ class Curve {
959
1021
  this._needsUpdate = true;
960
1022
  this.getLengths();
961
1023
  }
962
- getUtoTmapping(u, distance) {
963
- const arcLengths = this.getLengths();
1024
+ getUToTMapping(u, distance) {
1025
+ const lengths = this.getLengths();
964
1026
  let i = 0;
965
- const il = arcLengths.length;
966
- let targetArcLength;
1027
+ const lengthsLen = lengths.length;
1028
+ let targetLength;
967
1029
  if (distance) {
968
- targetArcLength = distance;
1030
+ targetLength = distance;
969
1031
  } else {
970
- targetArcLength = u * arcLengths[il - 1];
1032
+ targetLength = u * lengths[lengthsLen - 1];
971
1033
  }
972
1034
  let low = 0;
973
- let high = il - 1;
1035
+ let high = lengthsLen - 1;
974
1036
  let comparison;
975
1037
  while (low <= high) {
976
1038
  i = Math.floor(low + (high - low) / 2);
977
- comparison = arcLengths[i] - targetArcLength;
1039
+ comparison = lengths[i] - targetLength;
978
1040
  if (comparison < 0) {
979
1041
  low = i + 1;
980
1042
  } else if (comparison > 0) {
@@ -985,14 +1047,14 @@ class Curve {
985
1047
  }
986
1048
  }
987
1049
  i = high;
988
- if (arcLengths[i] === targetArcLength) {
989
- return i / (il - 1);
1050
+ if (lengths[i] === targetLength) {
1051
+ return i / (lengthsLen - 1);
990
1052
  }
991
- const lengthBefore = arcLengths[i];
992
- const lengthAfter = arcLengths[i + 1];
1053
+ const lengthBefore = lengths[i];
1054
+ const lengthAfter = lengths[i + 1];
993
1055
  const segmentLength = lengthAfter - lengthBefore;
994
- const segmentFraction = (targetArcLength - lengthBefore) / segmentLength;
995
- return (i + segmentFraction) / (il - 1);
1056
+ const segmentFraction = (targetLength - lengthBefore) / segmentLength;
1057
+ return (i + segmentFraction) / (lengthsLen - 1);
996
1058
  }
997
1059
  getTangent(t, output = new Vector2()) {
998
1060
  const delta = 1e-4;
@@ -1001,15 +1063,36 @@ class Curve {
1001
1063
  return output.copy(this.getPoint(t2).sub(this.getPoint(t1)).normalize());
1002
1064
  }
1003
1065
  getTangentAt(u, output) {
1004
- return this.getTangent(this.getUtoTmapping(u), output);
1066
+ return this.getTangent(this.getUToTMapping(u), output);
1005
1067
  }
1006
- /** overrideable */
1007
- // eslint-disable-next-line unused-imports/no-unused-vars
1008
- transformPoint(cb) {
1009
- return this;
1068
+ getNormal(t, output = new Vector2()) {
1069
+ this.getTangent(t, output);
1070
+ return output.set(-output.y, output.x).normalize();
1071
+ }
1072
+ getNormalAt(u, output) {
1073
+ return this.getNormal(this.getUToTMapping(u), output);
1010
1074
  }
1011
- transform(matrix) {
1012
- this.transformPoint((point) => point.applyMatrix3(matrix));
1075
+ getTForPoint(target, epsilon = 1e-3) {
1076
+ let low = 0;
1077
+ let high = 1;
1078
+ let mid = (low + high) / 2;
1079
+ while (high - low > epsilon) {
1080
+ mid = (low + high) / 2;
1081
+ const point = this.getPoint(mid);
1082
+ const distance = point.distanceTo(target);
1083
+ if (distance < epsilon) {
1084
+ return mid;
1085
+ }
1086
+ if (point.x < target.x) {
1087
+ low = mid;
1088
+ } else {
1089
+ high = mid;
1090
+ }
1091
+ }
1092
+ return mid;
1093
+ }
1094
+ matrix(matrix) {
1095
+ this.forEachControlPoints((point) => point.applyMatrix3(matrix));
1013
1096
  return this;
1014
1097
  }
1015
1098
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
@@ -1025,7 +1108,7 @@ class Curve {
1025
1108
  const { min, max } = this.getMinMax();
1026
1109
  return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
1027
1110
  }
1028
- getCommands() {
1111
+ toCommands() {
1029
1112
  return this.getPoints().map((point, i) => {
1030
1113
  if (i === 0) {
1031
1114
  return { type: "M", x: point.x, y: point.y };
@@ -1034,12 +1117,20 @@ class Curve {
1034
1117
  }
1035
1118
  });
1036
1119
  }
1037
- getData() {
1038
- return pathCommandsToPathData(this.getCommands());
1120
+ toData() {
1121
+ return pathCommandsToPathData(this.toCommands());
1039
1122
  }
1040
- /** overrideable */
1041
- // eslint-disable-next-line unused-imports/no-unused-vars
1042
1123
  drawTo(ctx) {
1124
+ this.toCommands().forEach((cmd) => {
1125
+ switch (cmd.type) {
1126
+ case "M":
1127
+ ctx.moveTo(cmd.x, cmd.y);
1128
+ break;
1129
+ case "L":
1130
+ ctx.lineTo(cmd.x, cmd.y);
1131
+ break;
1132
+ }
1133
+ });
1043
1134
  return this;
1044
1135
  }
1045
1136
  copy(source) {
@@ -1061,20 +1152,19 @@ class CircleCurve extends Curve {
1061
1152
  }
1062
1153
  getPoint(t) {
1063
1154
  const { radius, center } = this;
1064
- return center.clone().add(this.getNormal(t).clone().multiplyScalar(radius));
1155
+ return center.clone().add(this.getNormal(t).clone().scale(radius));
1065
1156
  }
1066
- getTangent(t) {
1157
+ getTangent(t, output = new Vector2()) {
1067
1158
  const { x, y } = this.getNormal(t);
1068
- return new Vector2(-y, x);
1159
+ return output.set(-y, x);
1069
1160
  }
1070
- getNormal(t) {
1161
+ getNormal(t, output = new Vector2()) {
1071
1162
  const { start, end } = this;
1072
1163
  const _t = t * (end - start) + start - 0.5 * Math.PI;
1073
- return new Vector2(Math.cos(_t), Math.sin(_t));
1164
+ return output.set(Math.cos(_t), Math.sin(_t));
1074
1165
  }
1075
- transformPoint(cb) {
1076
- cb(this.center);
1077
- return this;
1166
+ getControlPoints() {
1167
+ return [this.center];
1078
1168
  }
1079
1169
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1080
1170
  min.x = Math.min(min.x, this.center.x - this.radius);
@@ -1133,18 +1223,18 @@ class CubicBezierCurve extends Curve {
1133
1223
  }
1134
1224
  getPoint(t, output = new Vector2()) {
1135
1225
  const { start, startControl, endControl, end } = this;
1136
- output.set(
1226
+ return output.set(
1137
1227
  cubicBezier(t, start.x, startControl.x, endControl.x, end.x),
1138
1228
  cubicBezier(t, start.y, startControl.y, endControl.y, end.y)
1139
1229
  );
1140
- return output;
1141
1230
  }
1142
- transformPoint(cb) {
1143
- cb(this.start);
1144
- cb(this.startControl);
1145
- cb(this.endControl);
1146
- cb(this.end);
1147
- return this;
1231
+ getControlPoints() {
1232
+ return [
1233
+ this.start,
1234
+ this.startControl,
1235
+ this.endControl,
1236
+ this.end
1237
+ ];
1148
1238
  }
1149
1239
  _solveQuadratic(a, b, c) {
1150
1240
  const discriminant = b * b - 4 * a * c;
@@ -1187,7 +1277,7 @@ class CubicBezierCurve extends Curve {
1187
1277
  samplePoints(tValues, 10);
1188
1278
  return { min, max };
1189
1279
  }
1190
- getCommands() {
1280
+ toCommands() {
1191
1281
  const { start, startControl, endControl, end } = this;
1192
1282
  return [
1193
1283
  { type: "M", x: start.x, y: start.y },
@@ -1195,7 +1285,8 @@ class CubicBezierCurve extends Curve {
1195
1285
  ];
1196
1286
  }
1197
1287
  drawTo(ctx) {
1198
- const { startControl, endControl, end } = this;
1288
+ const { start, startControl, endControl, end } = this;
1289
+ ctx.lineTo(start.x, start.y);
1199
1290
  ctx.bezierCurveTo(startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y);
1200
1291
  return this;
1201
1292
  }
@@ -1224,6 +1315,9 @@ class EllipseCurve extends Curve {
1224
1315
  this.endAngle = endAngle;
1225
1316
  this.clockwise = clockwise;
1226
1317
  }
1318
+ isClockwise() {
1319
+ return this.clockwise;
1320
+ }
1227
1321
  getPoint(t, output = new Vector2()) {
1228
1322
  const twoPi = Math.PI * 2;
1229
1323
  let deltaAngle = this.endAngle - this.startAngle;
@@ -1259,7 +1353,7 @@ class EllipseCurve extends Curve {
1259
1353
  }
1260
1354
  return output.set(_x, _y);
1261
1355
  }
1262
- getCommands() {
1356
+ toCommands() {
1263
1357
  const { center, radiusX: rx, radiusY: ry, startAngle, endAngle, clockwise, rotation } = this;
1264
1358
  const { x: cx, y: cy } = center;
1265
1359
  const startX = cx + rx * Math.cos(startAngle) * Math.cos(rotation) - ry * Math.sin(startAngle) * Math.sin(rotation);
@@ -1300,7 +1394,7 @@ class EllipseCurve extends Curve {
1300
1394
  );
1301
1395
  return this;
1302
1396
  }
1303
- transform(matrix) {
1397
+ matrix(matrix) {
1304
1398
  tempV2.set(this.center.x, this.center.y);
1305
1399
  tempV2.applyMatrix3(matrix);
1306
1400
  this.center.x = tempV2.x;
@@ -1312,9 +1406,8 @@ class EllipseCurve extends Curve {
1312
1406
  }
1313
1407
  return this;
1314
1408
  }
1315
- transformPoint(cb) {
1316
- cb(this.center);
1317
- return this;
1409
+ getControlPoints() {
1410
+ return [this.center];
1318
1411
  }
1319
1412
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1320
1413
  const { center, radiusX: rx, radiusY: ry, rotation: theta } = this;
@@ -1496,7 +1589,7 @@ class LineCurve extends Curve {
1496
1589
  if (t === 1) {
1497
1590
  output.copy(this.end);
1498
1591
  } else {
1499
- output.copy(this.end).sub(this.start).multiplyScalar(t).add(this.start);
1592
+ output.copy(this.end).sub(this.start).scale(t).add(this.start);
1500
1593
  }
1501
1594
  return output;
1502
1595
  }
@@ -1509,14 +1602,11 @@ class LineCurve extends Curve {
1509
1602
  getTangentAt(u, output = new Vector2()) {
1510
1603
  return this.getTangent(u, output);
1511
1604
  }
1512
- getNormal(t, output = new Vector2()) {
1513
- const { x, y } = this.getPoint(t).sub(this.start);
1514
- return output.set(y, -x).normalize();
1515
- }
1516
- transformPoint(cb) {
1517
- cb(this.start);
1518
- cb(this.end);
1519
- return this;
1605
+ getControlPoints() {
1606
+ return [
1607
+ this.start,
1608
+ this.end
1609
+ ];
1520
1610
  }
1521
1611
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1522
1612
  const { start, end } = this;
@@ -1526,7 +1616,7 @@ class LineCurve extends Curve {
1526
1616
  max.y = Math.max(max.y, start.y, end.y);
1527
1617
  return { min, max };
1528
1618
  }
1529
- getCommands() {
1619
+ toCommands() {
1530
1620
  const { start, end } = this;
1531
1621
  return [
1532
1622
  { type: "M", x: start.x, y: start.y },
@@ -1534,7 +1624,8 @@ class LineCurve extends Curve {
1534
1624
  ];
1535
1625
  }
1536
1626
  drawTo(ctx) {
1537
- const { end } = this;
1627
+ const { start, end } = this;
1628
+ ctx.lineTo(start.x, start.y);
1538
1629
  ctx.lineTo(end.x, end.y);
1539
1630
  return this;
1540
1631
  }
@@ -1610,22 +1701,21 @@ class HeartCurve extends Curve {
1610
1701
  }
1611
1702
  return this.curves[index];
1612
1703
  }
1613
- getTangent(t) {
1614
- return this.getCurve(t).getTangent(this.curveT);
1704
+ getTangent(t, output) {
1705
+ return this.getCurve(t).getTangent(this.curveT, output);
1615
1706
  }
1616
- getNormal(t) {
1617
- return this.getCurve(t).getNormal(this.curveT);
1707
+ getNormal(t, output) {
1708
+ return this.getCurve(t).getNormal(this.curveT, output);
1618
1709
  }
1619
- transformPoint(cb) {
1620
- this.curves.forEach((curve) => curve.transformPoint(cb));
1621
- return this;
1710
+ getControlPoints() {
1711
+ return this.curves.flatMap((curve) => curve.getControlPoints());
1622
1712
  }
1623
1713
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1624
1714
  this.curves.forEach((curve) => curve.getMinMax(min, max));
1625
1715
  return { min, max };
1626
1716
  }
1627
- getCommands() {
1628
- return this.curves.flatMap((curve) => curve.getCommands());
1717
+ toCommands() {
1718
+ return this.curves.flatMap((curve) => curve.toCommands());
1629
1719
  }
1630
1720
  drawTo(ctx) {
1631
1721
  this.curves.forEach((curve) => curve.drawTo(ctx));
@@ -1688,16 +1778,15 @@ class PloygonCurve extends Curve {
1688
1778
  getNormal(t, output) {
1689
1779
  return this.getCurve(t).getNormal(this.curveT, output);
1690
1780
  }
1691
- transformPoint(cb) {
1692
- this.curves.forEach((curve) => curve.transformPoint(cb));
1693
- return this;
1781
+ getControlPoints() {
1782
+ return this.curves.flatMap((curve) => curve.getControlPoints());
1694
1783
  }
1695
1784
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1696
1785
  this.curves.forEach((curve) => curve.getMinMax(min, max));
1697
1786
  return { min, max };
1698
1787
  }
1699
- getCommands() {
1700
- return this.curves.flatMap((curve) => curve.getCommands());
1788
+ toCommands() {
1789
+ return this.curves.flatMap((curve) => curve.toCommands());
1701
1790
  }
1702
1791
  drawTo(ctx) {
1703
1792
  this.curves.forEach((curve) => curve.drawTo(ctx));
@@ -1720,11 +1809,12 @@ class QuadraticBezierCurve extends Curve {
1720
1809
  );
1721
1810
  return output;
1722
1811
  }
1723
- transformPoint(cb) {
1724
- cb(this.start);
1725
- cb(this.control);
1726
- cb(this.end);
1727
- return this;
1812
+ getControlPoints() {
1813
+ return [
1814
+ this.start,
1815
+ this.control,
1816
+ this.end
1817
+ ];
1728
1818
  }
1729
1819
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1730
1820
  const { start, control, end } = this;
@@ -1738,7 +1828,7 @@ class QuadraticBezierCurve extends Curve {
1738
1828
  max.y = Math.max(max.y, start.y, end.y, y1, y2);
1739
1829
  return { min, max };
1740
1830
  }
1741
- getCommands() {
1831
+ toCommands() {
1742
1832
  const { start, control, end } = this;
1743
1833
  return [
1744
1834
  { type: "M", x: start.x, y: start.y },
@@ -1746,7 +1836,8 @@ class QuadraticBezierCurve extends Curve {
1746
1836
  ];
1747
1837
  }
1748
1838
  drawTo(ctx) {
1749
- const { control, end } = this;
1839
+ const { start, control, end } = this;
1840
+ ctx.lineTo(start.x, start.y);
1750
1841
  ctx.quadraticCurveTo(control.x, control.y, end.x, end.y);
1751
1842
  return this;
1752
1843
  }
@@ -1836,16 +1927,15 @@ class RectangularCurve extends Curve {
1836
1927
  getNormal(t, output) {
1837
1928
  return this.getCurve(t).getNormal(this.curveT, output);
1838
1929
  }
1839
- transformPoint(cb) {
1840
- this.curves.forEach((curve) => curve.transformPoint(cb));
1841
- return this;
1930
+ getControlPoints() {
1931
+ return this.curves.flatMap((curve) => curve.getControlPoints());
1842
1932
  }
1843
1933
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1844
1934
  this.curves.forEach((curve) => curve.getMinMax(min, max));
1845
1935
  return { min, max };
1846
1936
  }
1847
- getCommands() {
1848
- return this.curves.flatMap((curve) => curve.getCommands());
1937
+ toCommands() {
1938
+ return this.curves.flatMap((curve) => curve.toCommands());
1849
1939
  }
1850
1940
  drawTo(ctx) {
1851
1941
  this.curves.forEach((curve) => curve.drawTo(ctx));
@@ -1873,9 +1963,8 @@ class SplineCurve extends Curve {
1873
1963
  );
1874
1964
  return output;
1875
1965
  }
1876
- transformPoint(cb) {
1877
- this.points.forEach((point) => cb(point));
1878
- return this;
1966
+ getControlPoints() {
1967
+ return this.points;
1879
1968
  }
1880
1969
  copy(source) {
1881
1970
  super.copy(source);
@@ -1940,6 +2029,9 @@ class CurvePath extends Curve {
1940
2029
  }
1941
2030
  return output;
1942
2031
  }
2032
+ getControlPoints() {
2033
+ return this.curves.flatMap((curve) => curve.getControlPoints());
2034
+ }
1943
2035
  getLength() {
1944
2036
  const lengths = this.getCurveLengths();
1945
2037
  return lengths[lengths.length - 1];
@@ -2111,10 +2203,6 @@ class CurvePath extends Curve {
2111
2203
  this._setCurrentPoint(points[points.length - 1]);
2112
2204
  return this;
2113
2205
  }
2114
- transformPoint(cb) {
2115
- this.curves.forEach((curve) => curve.transformPoint(cb));
2116
- return this;
2117
- }
2118
2206
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
2119
2207
  this.curves.forEach((curve) => curve.getMinMax(min, max));
2120
2208
  return { min, max };
@@ -2123,8 +2211,8 @@ class CurvePath extends Curve {
2123
2211
  const { min, max } = this.getMinMax();
2124
2212
  return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
2125
2213
  }
2126
- getCommands() {
2127
- return this.curves.flatMap((curve) => curve.getCommands());
2214
+ toCommands() {
2215
+ return this.curves.flatMap((curve) => curve.toCommands());
2128
2216
  }
2129
2217
  drawTo(ctx) {
2130
2218
  const point = this.curves[0]?.getPoint(0);
@@ -2152,6 +2240,29 @@ class CurvePath extends Curve {
2152
2240
  function toKebabCase(str) {
2153
2241
  return str.replace(/[^a-z0-9]/gi, "-").replace(/\B([A-Z])/g, "-$1").toLowerCase();
2154
2242
  }
2243
+ function getIntersectionPoint(p1, p2, q1, q2) {
2244
+ const r = p2.clone().sub(p1);
2245
+ const s = q2.clone().sub(q1);
2246
+ const q1p1 = q1.clone().sub(p1);
2247
+ const crossRS = r.cross(s);
2248
+ if (crossRS === 0) {
2249
+ return new Vector2(
2250
+ (p1.x + q1.x) / 2,
2251
+ (p1.y + q1.y) / 2
2252
+ );
2253
+ }
2254
+ const t = q1p1.cross(s) / crossRS;
2255
+ if (Math.abs(t) > 1) {
2256
+ return new Vector2(
2257
+ (p1.x + q1.x) / 2,
2258
+ (p1.y + q1.y) / 2
2259
+ );
2260
+ }
2261
+ return new Vector2(
2262
+ p1.x + t * r.x,
2263
+ p1.y + t * r.y
2264
+ );
2265
+ }
2155
2266
 
2156
2267
  var __defProp = Object.defineProperty;
2157
2268
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -2254,20 +2365,93 @@ class Path2D {
2254
2365
  this.currentPath.splineThru(points);
2255
2366
  return this;
2256
2367
  }
2257
- forEachCurve(cb) {
2258
- this.paths.forEach((path) => path.curves.forEach((curve) => cb(curve)));
2368
+ getControlPoints() {
2369
+ return this.paths.flatMap((path) => path.getControlPoints());
2370
+ }
2371
+ getCurves() {
2372
+ return this.paths.flatMap((path) => path.curves);
2373
+ }
2374
+ scale(sx, sy = sx, target = { x: 0, y: 0 }) {
2375
+ this.getControlPoints().forEach((point) => {
2376
+ point.scale(sx, sy, target);
2377
+ });
2378
+ return this;
2379
+ }
2380
+ skew(ax, ay = 0, target = { x: 0, y: 0 }) {
2381
+ this.getControlPoints().forEach((point) => {
2382
+ point.skew(ax, ay, target);
2383
+ });
2384
+ return this;
2385
+ }
2386
+ rotate(a, target = { x: 0, y: 0 }) {
2387
+ this.getControlPoints().forEach((point) => {
2388
+ point.rotate(a, target);
2389
+ });
2259
2390
  return this;
2260
2391
  }
2261
- transformPoint(cb) {
2262
- this.forEachCurve((curve) => curve.transformPoint(cb));
2392
+ bold(b) {
2393
+ if (b === 0) {
2394
+ return this;
2395
+ }
2396
+ const curves = this.getCurves();
2397
+ const _list = [];
2398
+ const _isClockwise = [];
2399
+ const _points = [];
2400
+ curves.forEach((curve, index) => {
2401
+ const points = curve.getControlPoints();
2402
+ const isClockwise = curve.isClockwise();
2403
+ _points[index] = points;
2404
+ _isClockwise[index] = isClockwise;
2405
+ const start = points[0];
2406
+ const end = points[points.length - 1] ?? start;
2407
+ _list.push({
2408
+ start: isClockwise ? end : start,
2409
+ end: isClockwise ? start : end,
2410
+ index
2411
+ });
2412
+ });
2413
+ const list = [];
2414
+ _list.forEach((itemA, indexA) => {
2415
+ list[indexA] = [];
2416
+ _list.forEach((itemB, indexB) => {
2417
+ if (indexB !== indexA && itemB.start.equals(itemA.end)) {
2418
+ list[indexA].push(itemB.index);
2419
+ }
2420
+ });
2421
+ });
2422
+ curves.forEach((curve, index) => {
2423
+ const isClockwise = _isClockwise[index];
2424
+ const points = _points[index];
2425
+ points.forEach((point) => {
2426
+ const t = curve.getTForPoint(point);
2427
+ const dist = curve.getNormal(t).scale(isClockwise ? b : -b);
2428
+ point.add(dist);
2429
+ });
2430
+ });
2431
+ list.forEach((indexes, indexA) => {
2432
+ const pointsA = _points[indexA];
2433
+ indexes.forEach((indexB) => {
2434
+ const pointsB = _points[indexB];
2435
+ const point = getIntersectionPoint(
2436
+ pointsA[pointsA.length - 1],
2437
+ pointsA[pointsA.length - 2] ?? pointsA[pointsA.length - 1],
2438
+ pointsB[0],
2439
+ pointsB[1] ?? pointsB[0]
2440
+ );
2441
+ if (point) {
2442
+ pointsA[pointsA.length - 1].copy(point);
2443
+ pointsB[0].copy(point);
2444
+ }
2445
+ });
2446
+ });
2263
2447
  return this;
2264
2448
  }
2265
- transform(matrix) {
2266
- this.forEachCurve((curve) => curve.transform(matrix));
2449
+ matrix(matrix) {
2450
+ this.getCurves().forEach((curve) => curve.matrix(matrix));
2267
2451
  return this;
2268
2452
  }
2269
2453
  getMinMax(min = Vector2.MAX, max = Vector2.MIN, withStyle = true) {
2270
- this.forEachCurve((curve) => curve.getMinMax(min, max));
2454
+ this.getCurves().forEach((curve) => curve.getMinMax(min, max));
2271
2455
  if (withStyle) {
2272
2456
  const strokeHalfWidth = this.strokeWidth / 2;
2273
2457
  min.x -= strokeHalfWidth;
@@ -2281,13 +2465,50 @@ class Path2D {
2281
2465
  const { min, max } = this.getMinMax(void 0, void 0, withStyle);
2282
2466
  return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
2283
2467
  }
2284
- getCommands() {
2285
- return this.paths.flatMap((path) => path.getCommands());
2468
+ drawTo(ctx, style = {}) {
2469
+ style = { ...this.style, ...style };
2470
+ const { fill = "#000", stroke = "none" } = style;
2471
+ ctx.beginPath();
2472
+ ctx.save();
2473
+ setCanvasContext(ctx, style);
2474
+ this.paths.forEach((path) => {
2475
+ path.drawTo(ctx);
2476
+ });
2477
+ if (fill !== "none") {
2478
+ ctx.fill();
2479
+ }
2480
+ if (stroke !== "none") {
2481
+ ctx.stroke();
2482
+ }
2483
+ ctx.restore();
2484
+ return this;
2485
+ }
2486
+ drawControlPointsTo(ctx, style = {}) {
2487
+ style = { ...this.style, ...style };
2488
+ const { fill = "#000", stroke = "none" } = style;
2489
+ ctx.beginPath();
2490
+ ctx.save();
2491
+ setCanvasContext(ctx, style);
2492
+ this.getControlPoints().forEach((point) => {
2493
+ ctx.moveTo(point.x, point.y);
2494
+ ctx.arc(point.x, point.y, 4, 0, Math.PI * 2);
2495
+ });
2496
+ if (fill !== "none") {
2497
+ ctx.fill();
2498
+ }
2499
+ if (stroke !== "none") {
2500
+ ctx.stroke();
2501
+ }
2502
+ ctx.restore();
2503
+ return this;
2504
+ }
2505
+ toCommands() {
2506
+ return this.paths.flatMap((path) => path.toCommands());
2286
2507
  }
2287
- getData() {
2288
- return this.paths.map((path) => path.getData()).join(" ");
2508
+ toData() {
2509
+ return this.paths.map((path) => path.toData()).join(" ");
2289
2510
  }
2290
- getSvgPathXml() {
2511
+ toSvgPathString() {
2291
2512
  const style = {
2292
2513
  ...this.style,
2293
2514
  fill: this.style.fill ?? "#000",
@@ -2308,40 +2529,21 @@ class Path2D {
2308
2529
  cssText += `${key}:${cssStyle[key]};`;
2309
2530
  }
2310
2531
  }
2311
- return `<path d="${this.getData()}" style="${cssText}"></path>`;
2532
+ return `<path d="${this.toData()}" style="${cssText}"></path>`;
2312
2533
  }
2313
- getSvgXml() {
2534
+ toSvgString() {
2314
2535
  const { x, y, width, height } = this.getBoundingBox();
2315
- const path = this.getSvgPathXml();
2536
+ const path = this.toSvgPathString();
2316
2537
  return `<svg viewBox="${x} ${y} ${width} ${height}" width="${width}px" height="${height}px" xmlns="http://www.w3.org/2000/svg">${path}</svg>`;
2317
2538
  }
2318
- getSvgDataUri() {
2319
- return `data:image/svg+xml;base64,${btoa(this.getSvgXml())}`;
2320
- }
2321
- drawTo(ctx, style = {}) {
2322
- style = { ...this.style, ...style };
2323
- const { fill = "#000", stroke = "none" } = style;
2324
- setCanvasContext(ctx, style);
2325
- this.paths.forEach((path) => {
2326
- path.drawTo(ctx);
2327
- });
2328
- if (fill !== "none") {
2329
- ctx.fill();
2330
- }
2331
- if (stroke !== "none") {
2332
- ctx.stroke();
2333
- }
2334
- }
2335
- copy(source) {
2336
- this.currentPath = source.currentPath.clone();
2337
- this.paths = source.paths.map((path) => path.clone());
2338
- this.style = { ...source.style };
2339
- return this;
2539
+ toSvgUrl() {
2540
+ return `data:image/svg+xml;base64,${btoa(this.toSvgString())}`;
2340
2541
  }
2341
2542
  toSvg() {
2342
- return new DOMParser().parseFromString(this.getSvgXml(), "image/svg+xml").documentElement;
2543
+ return new DOMParser().parseFromString(this.toSvgString(), "image/svg+xml").documentElement;
2343
2544
  }
2344
- toCanvas(pixelRatio = 2) {
2545
+ toCanvas(options = {}) {
2546
+ const { pixelRatio = 2, ...style } = options;
2345
2547
  const { left, top, width, height } = this.getBoundingBox();
2346
2548
  const canvas = document.createElement("canvas");
2347
2549
  canvas.width = width * pixelRatio;
@@ -2352,10 +2554,16 @@ class Path2D {
2352
2554
  if (ctx) {
2353
2555
  ctx.scale(pixelRatio, pixelRatio);
2354
2556
  ctx.translate(-left, -top);
2355
- this.drawTo(ctx);
2557
+ this.drawTo(ctx, style);
2356
2558
  }
2357
2559
  return canvas;
2358
2560
  }
2561
+ copy(source) {
2562
+ this.currentPath = source.currentPath.clone();
2563
+ this.paths = source.paths.map((path) => path.clone());
2564
+ this.style = { ...source.style };
2565
+ return this;
2566
+ }
2359
2567
  clone() {
2360
2568
  return new this.constructor().copy(this);
2361
2569
  }
@@ -2832,7 +3040,7 @@ function parseNode(node, style, paths = []) {
2832
3040
  const transformStack = [];
2833
3041
  const transform = getNodeTransform(node, currentTransform, transformStack);
2834
3042
  if (path) {
2835
- path.transform(currentTransform);
3043
+ path.matrix(currentTransform);
2836
3044
  paths.push(path);
2837
3045
  path.style = style;
2838
3046
  }