modern-path2d 0.1.17 → 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;
@@ -141,6 +193,11 @@ class BoundingBox {
141
193
  return this.top + this.height;
142
194
  }
143
195
  static from(...boxes) {
196
+ if (boxes.length === 0) {
197
+ return new BoundingBox();
198
+ } else if (boxes.length === 1) {
199
+ return boxes[0].clone();
200
+ }
144
201
  const firstBox = boxes[0];
145
202
  const merged = boxes.slice(1).reduce(
146
203
  (merged2, box) => {
@@ -910,8 +967,14 @@ class Curve {
910
967
  __publicField$5(this, "_cacheArcLengths");
911
968
  __publicField$5(this, "_needsUpdate", false);
912
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
+ }
913
976
  getPointAt(u, output = new Vector2()) {
914
- return this.getPoint(this.getUtoTmapping(u), output);
977
+ return this.getPoint(this.getUToTMapping(u), output);
915
978
  }
916
979
  getPoints(divisions = 5) {
917
980
  const points = [];
@@ -920,6 +983,10 @@ class Curve {
920
983
  }
921
984
  return points;
922
985
  }
986
+ forEachControlPoints(cb) {
987
+ this.getControlPoints().forEach(cb);
988
+ return this;
989
+ }
923
990
  getSpacedPoints(divisions = 5) {
924
991
  const points = [];
925
992
  for (let i = 0; i <= divisions; i++) {
@@ -954,22 +1021,22 @@ class Curve {
954
1021
  this._needsUpdate = true;
955
1022
  this.getLengths();
956
1023
  }
957
- getUtoTmapping(u, distance) {
958
- const arcLengths = this.getLengths();
1024
+ getUToTMapping(u, distance) {
1025
+ const lengths = this.getLengths();
959
1026
  let i = 0;
960
- const il = arcLengths.length;
961
- let targetArcLength;
1027
+ const lengthsLen = lengths.length;
1028
+ let targetLength;
962
1029
  if (distance) {
963
- targetArcLength = distance;
1030
+ targetLength = distance;
964
1031
  } else {
965
- targetArcLength = u * arcLengths[il - 1];
1032
+ targetLength = u * lengths[lengthsLen - 1];
966
1033
  }
967
1034
  let low = 0;
968
- let high = il - 1;
1035
+ let high = lengthsLen - 1;
969
1036
  let comparison;
970
1037
  while (low <= high) {
971
1038
  i = Math.floor(low + (high - low) / 2);
972
- comparison = arcLengths[i] - targetArcLength;
1039
+ comparison = lengths[i] - targetLength;
973
1040
  if (comparison < 0) {
974
1041
  low = i + 1;
975
1042
  } else if (comparison > 0) {
@@ -980,14 +1047,14 @@ class Curve {
980
1047
  }
981
1048
  }
982
1049
  i = high;
983
- if (arcLengths[i] === targetArcLength) {
984
- return i / (il - 1);
1050
+ if (lengths[i] === targetLength) {
1051
+ return i / (lengthsLen - 1);
985
1052
  }
986
- const lengthBefore = arcLengths[i];
987
- const lengthAfter = arcLengths[i + 1];
1053
+ const lengthBefore = lengths[i];
1054
+ const lengthAfter = lengths[i + 1];
988
1055
  const segmentLength = lengthAfter - lengthBefore;
989
- const segmentFraction = (targetArcLength - lengthBefore) / segmentLength;
990
- return (i + segmentFraction) / (il - 1);
1056
+ const segmentFraction = (targetLength - lengthBefore) / segmentLength;
1057
+ return (i + segmentFraction) / (lengthsLen - 1);
991
1058
  }
992
1059
  getTangent(t, output = new Vector2()) {
993
1060
  const delta = 1e-4;
@@ -996,15 +1063,36 @@ class Curve {
996
1063
  return output.copy(this.getPoint(t2).sub(this.getPoint(t1)).normalize());
997
1064
  }
998
1065
  getTangentAt(u, output) {
999
- return this.getTangent(this.getUtoTmapping(u), output);
1066
+ return this.getTangent(this.getUToTMapping(u), output);
1000
1067
  }
1001
- /** overrideable */
1002
- // eslint-disable-next-line unused-imports/no-unused-vars
1003
- transformPoint(cb) {
1004
- return this;
1068
+ getNormal(t, output = new Vector2()) {
1069
+ this.getTangent(t, output);
1070
+ return output.set(-output.y, output.x).normalize();
1005
1071
  }
1006
- transform(matrix) {
1007
- this.transformPoint((point) => point.applyMatrix3(matrix));
1072
+ getNormalAt(u, output) {
1073
+ return this.getNormal(this.getUToTMapping(u), output);
1074
+ }
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));
1008
1096
  return this;
1009
1097
  }
1010
1098
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
@@ -1020,7 +1108,7 @@ class Curve {
1020
1108
  const { min, max } = this.getMinMax();
1021
1109
  return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
1022
1110
  }
1023
- getCommands() {
1111
+ toCommands() {
1024
1112
  return this.getPoints().map((point, i) => {
1025
1113
  if (i === 0) {
1026
1114
  return { type: "M", x: point.x, y: point.y };
@@ -1029,12 +1117,20 @@ class Curve {
1029
1117
  }
1030
1118
  });
1031
1119
  }
1032
- getData() {
1033
- return pathCommandsToPathData(this.getCommands());
1120
+ toData() {
1121
+ return pathCommandsToPathData(this.toCommands());
1034
1122
  }
1035
- /** overrideable */
1036
- // eslint-disable-next-line unused-imports/no-unused-vars
1037
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
+ });
1038
1134
  return this;
1039
1135
  }
1040
1136
  copy(source) {
@@ -1056,20 +1152,19 @@ class CircleCurve extends Curve {
1056
1152
  }
1057
1153
  getPoint(t) {
1058
1154
  const { radius, center } = this;
1059
- return center.clone().add(this.getNormal(t).clone().multiplyScalar(radius));
1155
+ return center.clone().add(this.getNormal(t).clone().scale(radius));
1060
1156
  }
1061
- getTangent(t) {
1157
+ getTangent(t, output = new Vector2()) {
1062
1158
  const { x, y } = this.getNormal(t);
1063
- return new Vector2(-y, x);
1159
+ return output.set(-y, x);
1064
1160
  }
1065
- getNormal(t) {
1161
+ getNormal(t, output = new Vector2()) {
1066
1162
  const { start, end } = this;
1067
1163
  const _t = t * (end - start) + start - 0.5 * Math.PI;
1068
- return new Vector2(Math.cos(_t), Math.sin(_t));
1164
+ return output.set(Math.cos(_t), Math.sin(_t));
1069
1165
  }
1070
- transformPoint(cb) {
1071
- cb(this.center);
1072
- return this;
1166
+ getControlPoints() {
1167
+ return [this.center];
1073
1168
  }
1074
1169
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1075
1170
  min.x = Math.min(min.x, this.center.x - this.radius);
@@ -1128,18 +1223,18 @@ class CubicBezierCurve extends Curve {
1128
1223
  }
1129
1224
  getPoint(t, output = new Vector2()) {
1130
1225
  const { start, startControl, endControl, end } = this;
1131
- output.set(
1226
+ return output.set(
1132
1227
  cubicBezier(t, start.x, startControl.x, endControl.x, end.x),
1133
1228
  cubicBezier(t, start.y, startControl.y, endControl.y, end.y)
1134
1229
  );
1135
- return output;
1136
1230
  }
1137
- transformPoint(cb) {
1138
- cb(this.start);
1139
- cb(this.startControl);
1140
- cb(this.endControl);
1141
- cb(this.end);
1142
- return this;
1231
+ getControlPoints() {
1232
+ return [
1233
+ this.start,
1234
+ this.startControl,
1235
+ this.endControl,
1236
+ this.end
1237
+ ];
1143
1238
  }
1144
1239
  _solveQuadratic(a, b, c) {
1145
1240
  const discriminant = b * b - 4 * a * c;
@@ -1182,7 +1277,7 @@ class CubicBezierCurve extends Curve {
1182
1277
  samplePoints(tValues, 10);
1183
1278
  return { min, max };
1184
1279
  }
1185
- getCommands() {
1280
+ toCommands() {
1186
1281
  const { start, startControl, endControl, end } = this;
1187
1282
  return [
1188
1283
  { type: "M", x: start.x, y: start.y },
@@ -1190,7 +1285,8 @@ class CubicBezierCurve extends Curve {
1190
1285
  ];
1191
1286
  }
1192
1287
  drawTo(ctx) {
1193
- const { startControl, endControl, end } = this;
1288
+ const { start, startControl, endControl, end } = this;
1289
+ ctx.lineTo(start.x, start.y);
1194
1290
  ctx.bezierCurveTo(startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y);
1195
1291
  return this;
1196
1292
  }
@@ -1219,6 +1315,9 @@ class EllipseCurve extends Curve {
1219
1315
  this.endAngle = endAngle;
1220
1316
  this.clockwise = clockwise;
1221
1317
  }
1318
+ isClockwise() {
1319
+ return this.clockwise;
1320
+ }
1222
1321
  getPoint(t, output = new Vector2()) {
1223
1322
  const twoPi = Math.PI * 2;
1224
1323
  let deltaAngle = this.endAngle - this.startAngle;
@@ -1254,7 +1353,7 @@ class EllipseCurve extends Curve {
1254
1353
  }
1255
1354
  return output.set(_x, _y);
1256
1355
  }
1257
- getCommands() {
1356
+ toCommands() {
1258
1357
  const { center, radiusX: rx, radiusY: ry, startAngle, endAngle, clockwise, rotation } = this;
1259
1358
  const { x: cx, y: cy } = center;
1260
1359
  const startX = cx + rx * Math.cos(startAngle) * Math.cos(rotation) - ry * Math.sin(startAngle) * Math.sin(rotation);
@@ -1295,7 +1394,7 @@ class EllipseCurve extends Curve {
1295
1394
  );
1296
1395
  return this;
1297
1396
  }
1298
- transform(matrix) {
1397
+ matrix(matrix) {
1299
1398
  tempV2.set(this.center.x, this.center.y);
1300
1399
  tempV2.applyMatrix3(matrix);
1301
1400
  this.center.x = tempV2.x;
@@ -1307,9 +1406,8 @@ class EllipseCurve extends Curve {
1307
1406
  }
1308
1407
  return this;
1309
1408
  }
1310
- transformPoint(cb) {
1311
- cb(this.center);
1312
- return this;
1409
+ getControlPoints() {
1410
+ return [this.center];
1313
1411
  }
1314
1412
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1315
1413
  const { center, radiusX: rx, radiusY: ry, rotation: theta } = this;
@@ -1491,7 +1589,7 @@ class LineCurve extends Curve {
1491
1589
  if (t === 1) {
1492
1590
  output.copy(this.end);
1493
1591
  } else {
1494
- 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);
1495
1593
  }
1496
1594
  return output;
1497
1595
  }
@@ -1504,14 +1602,11 @@ class LineCurve extends Curve {
1504
1602
  getTangentAt(u, output = new Vector2()) {
1505
1603
  return this.getTangent(u, output);
1506
1604
  }
1507
- getNormal(t, output = new Vector2()) {
1508
- const { x, y } = this.getPoint(t).sub(this.start);
1509
- return output.set(y, -x).normalize();
1510
- }
1511
- transformPoint(cb) {
1512
- cb(this.start);
1513
- cb(this.end);
1514
- return this;
1605
+ getControlPoints() {
1606
+ return [
1607
+ this.start,
1608
+ this.end
1609
+ ];
1515
1610
  }
1516
1611
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1517
1612
  const { start, end } = this;
@@ -1521,7 +1616,7 @@ class LineCurve extends Curve {
1521
1616
  max.y = Math.max(max.y, start.y, end.y);
1522
1617
  return { min, max };
1523
1618
  }
1524
- getCommands() {
1619
+ toCommands() {
1525
1620
  const { start, end } = this;
1526
1621
  return [
1527
1622
  { type: "M", x: start.x, y: start.y },
@@ -1529,7 +1624,8 @@ class LineCurve extends Curve {
1529
1624
  ];
1530
1625
  }
1531
1626
  drawTo(ctx) {
1532
- const { end } = this;
1627
+ const { start, end } = this;
1628
+ ctx.lineTo(start.x, start.y);
1533
1629
  ctx.lineTo(end.x, end.y);
1534
1630
  return this;
1535
1631
  }
@@ -1605,22 +1701,21 @@ class HeartCurve extends Curve {
1605
1701
  }
1606
1702
  return this.curves[index];
1607
1703
  }
1608
- getTangent(t) {
1609
- return this.getCurve(t).getTangent(this.curveT);
1704
+ getTangent(t, output) {
1705
+ return this.getCurve(t).getTangent(this.curveT, output);
1610
1706
  }
1611
- getNormal(t) {
1612
- return this.getCurve(t).getNormal(this.curveT);
1707
+ getNormal(t, output) {
1708
+ return this.getCurve(t).getNormal(this.curveT, output);
1613
1709
  }
1614
- transformPoint(cb) {
1615
- this.curves.forEach((curve) => curve.transformPoint(cb));
1616
- return this;
1710
+ getControlPoints() {
1711
+ return this.curves.flatMap((curve) => curve.getControlPoints());
1617
1712
  }
1618
1713
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1619
1714
  this.curves.forEach((curve) => curve.getMinMax(min, max));
1620
1715
  return { min, max };
1621
1716
  }
1622
- getCommands() {
1623
- return this.curves.flatMap((curve) => curve.getCommands());
1717
+ toCommands() {
1718
+ return this.curves.flatMap((curve) => curve.toCommands());
1624
1719
  }
1625
1720
  drawTo(ctx) {
1626
1721
  this.curves.forEach((curve) => curve.drawTo(ctx));
@@ -1683,16 +1778,15 @@ class PloygonCurve extends Curve {
1683
1778
  getNormal(t, output) {
1684
1779
  return this.getCurve(t).getNormal(this.curveT, output);
1685
1780
  }
1686
- transformPoint(cb) {
1687
- this.curves.forEach((curve) => curve.transformPoint(cb));
1688
- return this;
1781
+ getControlPoints() {
1782
+ return this.curves.flatMap((curve) => curve.getControlPoints());
1689
1783
  }
1690
1784
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1691
1785
  this.curves.forEach((curve) => curve.getMinMax(min, max));
1692
1786
  return { min, max };
1693
1787
  }
1694
- getCommands() {
1695
- return this.curves.flatMap((curve) => curve.getCommands());
1788
+ toCommands() {
1789
+ return this.curves.flatMap((curve) => curve.toCommands());
1696
1790
  }
1697
1791
  drawTo(ctx) {
1698
1792
  this.curves.forEach((curve) => curve.drawTo(ctx));
@@ -1715,11 +1809,12 @@ class QuadraticBezierCurve extends Curve {
1715
1809
  );
1716
1810
  return output;
1717
1811
  }
1718
- transformPoint(cb) {
1719
- cb(this.start);
1720
- cb(this.control);
1721
- cb(this.end);
1722
- return this;
1812
+ getControlPoints() {
1813
+ return [
1814
+ this.start,
1815
+ this.control,
1816
+ this.end
1817
+ ];
1723
1818
  }
1724
1819
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1725
1820
  const { start, control, end } = this;
@@ -1733,7 +1828,7 @@ class QuadraticBezierCurve extends Curve {
1733
1828
  max.y = Math.max(max.y, start.y, end.y, y1, y2);
1734
1829
  return { min, max };
1735
1830
  }
1736
- getCommands() {
1831
+ toCommands() {
1737
1832
  const { start, control, end } = this;
1738
1833
  return [
1739
1834
  { type: "M", x: start.x, y: start.y },
@@ -1741,7 +1836,8 @@ class QuadraticBezierCurve extends Curve {
1741
1836
  ];
1742
1837
  }
1743
1838
  drawTo(ctx) {
1744
- const { control, end } = this;
1839
+ const { start, control, end } = this;
1840
+ ctx.lineTo(start.x, start.y);
1745
1841
  ctx.quadraticCurveTo(control.x, control.y, end.x, end.y);
1746
1842
  return this;
1747
1843
  }
@@ -1831,16 +1927,15 @@ class RectangularCurve extends Curve {
1831
1927
  getNormal(t, output) {
1832
1928
  return this.getCurve(t).getNormal(this.curveT, output);
1833
1929
  }
1834
- transformPoint(cb) {
1835
- this.curves.forEach((curve) => curve.transformPoint(cb));
1836
- return this;
1930
+ getControlPoints() {
1931
+ return this.curves.flatMap((curve) => curve.getControlPoints());
1837
1932
  }
1838
1933
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1839
1934
  this.curves.forEach((curve) => curve.getMinMax(min, max));
1840
1935
  return { min, max };
1841
1936
  }
1842
- getCommands() {
1843
- return this.curves.flatMap((curve) => curve.getCommands());
1937
+ toCommands() {
1938
+ return this.curves.flatMap((curve) => curve.toCommands());
1844
1939
  }
1845
1940
  drawTo(ctx) {
1846
1941
  this.curves.forEach((curve) => curve.drawTo(ctx));
@@ -1868,9 +1963,8 @@ class SplineCurve extends Curve {
1868
1963
  );
1869
1964
  return output;
1870
1965
  }
1871
- transformPoint(cb) {
1872
- this.points.forEach((point) => cb(point));
1873
- return this;
1966
+ getControlPoints() {
1967
+ return this.points;
1874
1968
  }
1875
1969
  copy(source) {
1876
1970
  super.copy(source);
@@ -1935,6 +2029,9 @@ class CurvePath extends Curve {
1935
2029
  }
1936
2030
  return output;
1937
2031
  }
2032
+ getControlPoints() {
2033
+ return this.curves.flatMap((curve) => curve.getControlPoints());
2034
+ }
1938
2035
  getLength() {
1939
2036
  const lengths = this.getCurveLengths();
1940
2037
  return lengths[lengths.length - 1];
@@ -2106,10 +2203,6 @@ class CurvePath extends Curve {
2106
2203
  this._setCurrentPoint(points[points.length - 1]);
2107
2204
  return this;
2108
2205
  }
2109
- transformPoint(cb) {
2110
- this.curves.forEach((curve) => curve.transformPoint(cb));
2111
- return this;
2112
- }
2113
2206
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
2114
2207
  this.curves.forEach((curve) => curve.getMinMax(min, max));
2115
2208
  return { min, max };
@@ -2118,8 +2211,8 @@ class CurvePath extends Curve {
2118
2211
  const { min, max } = this.getMinMax();
2119
2212
  return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
2120
2213
  }
2121
- getCommands() {
2122
- return this.curves.flatMap((curve) => curve.getCommands());
2214
+ toCommands() {
2215
+ return this.curves.flatMap((curve) => curve.toCommands());
2123
2216
  }
2124
2217
  drawTo(ctx) {
2125
2218
  const point = this.curves[0]?.getPoint(0);
@@ -2147,6 +2240,29 @@ class CurvePath extends Curve {
2147
2240
  function toKebabCase(str) {
2148
2241
  return str.replace(/[^a-z0-9]/gi, "-").replace(/\B([A-Z])/g, "-$1").toLowerCase();
2149
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
+ }
2150
2266
 
2151
2267
  var __defProp = Object.defineProperty;
2152
2268
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -2249,20 +2365,93 @@ class Path2D {
2249
2365
  this.currentPath.splineThru(points);
2250
2366
  return this;
2251
2367
  }
2252
- forEachCurve(cb) {
2253
- 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
+ });
2254
2384
  return this;
2255
2385
  }
2256
- transformPoint(cb) {
2257
- this.forEachCurve((curve) => curve.transformPoint(cb));
2386
+ rotate(a, target = { x: 0, y: 0 }) {
2387
+ this.getControlPoints().forEach((point) => {
2388
+ point.rotate(a, target);
2389
+ });
2390
+ return this;
2391
+ }
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
+ });
2258
2447
  return this;
2259
2448
  }
2260
- transform(matrix) {
2261
- this.forEachCurve((curve) => curve.transform(matrix));
2449
+ matrix(matrix) {
2450
+ this.getCurves().forEach((curve) => curve.matrix(matrix));
2262
2451
  return this;
2263
2452
  }
2264
2453
  getMinMax(min = Vector2.MAX, max = Vector2.MIN, withStyle = true) {
2265
- this.forEachCurve((curve) => curve.getMinMax(min, max));
2454
+ this.getCurves().forEach((curve) => curve.getMinMax(min, max));
2266
2455
  if (withStyle) {
2267
2456
  const strokeHalfWidth = this.strokeWidth / 2;
2268
2457
  min.x -= strokeHalfWidth;
@@ -2276,13 +2465,50 @@ class Path2D {
2276
2465
  const { min, max } = this.getMinMax(void 0, void 0, withStyle);
2277
2466
  return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
2278
2467
  }
2279
- getCommands() {
2280
- 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;
2281
2485
  }
2282
- getData() {
2283
- return this.paths.map((path) => path.getData()).join(" ");
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());
2284
2507
  }
2285
- getSvgPathXml() {
2508
+ toData() {
2509
+ return this.paths.map((path) => path.toData()).join(" ");
2510
+ }
2511
+ toSvgPathString() {
2286
2512
  const style = {
2287
2513
  ...this.style,
2288
2514
  fill: this.style.fill ?? "#000",
@@ -2303,40 +2529,21 @@ class Path2D {
2303
2529
  cssText += `${key}:${cssStyle[key]};`;
2304
2530
  }
2305
2531
  }
2306
- return `<path d="${this.getData()}" style="${cssText}"></path>`;
2532
+ return `<path d="${this.toData()}" style="${cssText}"></path>`;
2307
2533
  }
2308
- getSvgXml() {
2534
+ toSvgString() {
2309
2535
  const { x, y, width, height } = this.getBoundingBox();
2310
- const path = this.getSvgPathXml();
2536
+ const path = this.toSvgPathString();
2311
2537
  return `<svg viewBox="${x} ${y} ${width} ${height}" width="${width}px" height="${height}px" xmlns="http://www.w3.org/2000/svg">${path}</svg>`;
2312
2538
  }
2313
- getSvgDataUri() {
2314
- return `data:image/svg+xml;base64,${btoa(this.getSvgXml())}`;
2315
- }
2316
- drawTo(ctx, style = {}) {
2317
- style = { ...this.style, ...style };
2318
- const { fill = "#000", stroke = "none" } = style;
2319
- setCanvasContext(ctx, style);
2320
- this.paths.forEach((path) => {
2321
- path.drawTo(ctx);
2322
- });
2323
- if (fill !== "none") {
2324
- ctx.fill();
2325
- }
2326
- if (stroke !== "none") {
2327
- ctx.stroke();
2328
- }
2329
- }
2330
- copy(source) {
2331
- this.currentPath = source.currentPath.clone();
2332
- this.paths = source.paths.map((path) => path.clone());
2333
- this.style = { ...source.style };
2334
- return this;
2539
+ toSvgUrl() {
2540
+ return `data:image/svg+xml;base64,${btoa(this.toSvgString())}`;
2335
2541
  }
2336
2542
  toSvg() {
2337
- return new DOMParser().parseFromString(this.getSvgXml(), "image/svg+xml").documentElement;
2543
+ return new DOMParser().parseFromString(this.toSvgString(), "image/svg+xml").documentElement;
2338
2544
  }
2339
- toCanvas(pixelRatio = 2) {
2545
+ toCanvas(options = {}) {
2546
+ const { pixelRatio = 2, ...style } = options;
2340
2547
  const { left, top, width, height } = this.getBoundingBox();
2341
2548
  const canvas = document.createElement("canvas");
2342
2549
  canvas.width = width * pixelRatio;
@@ -2347,10 +2554,16 @@ class Path2D {
2347
2554
  if (ctx) {
2348
2555
  ctx.scale(pixelRatio, pixelRatio);
2349
2556
  ctx.translate(-left, -top);
2350
- this.drawTo(ctx);
2557
+ this.drawTo(ctx, style);
2351
2558
  }
2352
2559
  return canvas;
2353
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
+ }
2354
2567
  clone() {
2355
2568
  return new this.constructor().copy(this);
2356
2569
  }
@@ -2827,7 +3040,7 @@ function parseNode(node, style, paths = []) {
2827
3040
  const transformStack = [];
2828
3041
  const transform = getNodeTransform(node, currentTransform, transformStack);
2829
3042
  if (path) {
2830
- path.transform(currentTransform);
3043
+ path.matrix(currentTransform);
2831
3044
  paths.push(path);
2832
3045
  path.style = style;
2833
3046
  }