modern-path2d 0.0.2 → 0.0.4

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
@@ -3,6 +3,12 @@ class Point2D {
3
3
  this.x = x;
4
4
  this.y = y;
5
5
  }
6
+ static get MAX() {
7
+ return new Point2D(Infinity, Infinity);
8
+ }
9
+ static get MIN() {
10
+ return new Point2D(-Infinity, -Infinity);
11
+ }
6
12
  set(x, y) {
7
13
  this.x = x;
8
14
  this.y = y;
@@ -75,6 +81,9 @@ class Curve {
75
81
  __publicField$5(this, "_cacheArcLengths");
76
82
  __publicField$5(this, "_needsUpdate", false);
77
83
  }
84
+ getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
85
+ return { min, max };
86
+ }
78
87
  getDivisions(divisions) {
79
88
  return divisions;
80
89
  }
@@ -170,6 +179,26 @@ class Curve {
170
179
  getTangentAt(u, output = new Point2D()) {
171
180
  return this.getTangent(this.getUtoTmapping(u), output);
172
181
  }
182
+ getData() {
183
+ return this.getCommands().map((cmd) => {
184
+ switch (cmd.type) {
185
+ case "M":
186
+ return `M ${cmd.x} ${cmd.y}`;
187
+ case "L":
188
+ return `L ${cmd.x} ${cmd.y}`;
189
+ case "C":
190
+ return `C ${cmd.x1} ${cmd.y1} ${cmd.x2} ${cmd.y2} ${cmd.x} ${cmd.y}`;
191
+ case "Q":
192
+ return `Q ${cmd.x1} ${cmd.y1} ${cmd.x} ${cmd.y}`;
193
+ case "A":
194
+ return `A ${cmd.rx} ${cmd.ry} ${cmd.xAxisRotation} ${cmd.largeArcFlag} ${cmd.sweepFlag} ${cmd.x} ${cmd.y}`;
195
+ case "Z":
196
+ return "Z";
197
+ default:
198
+ return "";
199
+ }
200
+ }).join(" ");
201
+ }
173
202
  clone() {
174
203
  return new this.constructor().copy(this);
175
204
  }
@@ -187,26 +216,27 @@ class CircleCurve extends Curve {
187
216
  this.start = start;
188
217
  this.end = end;
189
218
  }
190
- getPole(min, max) {
219
+ getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
191
220
  min.x = Math.min(min.x, this.center.x - this.radius);
192
221
  min.y = Math.min(min.y, this.center.y - this.radius);
193
222
  max.x = Math.max(max.x, this.center.x + this.radius);
194
223
  max.y = Math.max(max.y, this.center.y + this.radius);
224
+ return { min, max };
195
225
  }
196
- getPoint(index) {
226
+ getPoint(t) {
197
227
  const { radius, center } = this;
198
- return center.clone().add(this.getNormal(index).clone().multiplyScalar(radius));
228
+ return center.clone().add(this.getNormal(t).clone().multiplyScalar(radius));
199
229
  }
200
- getTangent(index) {
201
- const { x, y } = this.getNormal(index);
230
+ getTangent(t) {
231
+ const { x, y } = this.getNormal(t);
202
232
  return new Point2D(-y, x);
203
233
  }
204
- getNormal(index) {
234
+ getNormal(t) {
205
235
  const { start, end } = this;
206
- const t = index * (end - start) + start - 0.5 * Math.PI;
207
- return new Point2D(Math.cos(t), Math.sin(t));
236
+ const _t = t * (end - start) + start - 0.5 * Math.PI;
237
+ return new Point2D(Math.cos(_t), Math.sin(_t));
208
238
  }
209
- toPathCommands() {
239
+ getCommands() {
210
240
  return [];
211
241
  }
212
242
  drawTo(_ctx) {
@@ -267,8 +297,20 @@ class CubicBezierCurve extends Curve {
267
297
  );
268
298
  return output;
269
299
  }
270
- toPathCommands() {
271
- return [];
300
+ getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
301
+ const { v0, v1, v2, v3 } = this;
302
+ min.x = Math.min(min.x, v0.x, v1.x, v2.x, v3.x);
303
+ min.y = Math.min(min.y, v0.y, v1.y, v2.y, v3.y);
304
+ max.x = Math.max(max.x, v0.x, v1.x, v2.x, v3.x);
305
+ max.y = Math.max(max.y, v0.y, v1.y, v2.y, v3.y);
306
+ return { min, max };
307
+ }
308
+ getCommands() {
309
+ const { v0, v1, v2, v3 } = this;
310
+ return [
311
+ { type: "M", x: v0.x, y: v0.y },
312
+ { type: "C", x1: v1.x, y1: v1.y, x2: v2.x, y2: v2.y, x: v3.x, y: v3.y }
313
+ ];
272
314
  }
273
315
  drawTo(ctx) {
274
316
  const { v0, v1, v2, v3 } = this;
@@ -335,18 +377,30 @@ class EllipseCurve extends Curve {
335
377
  }
336
378
  return output.set(_x, _y);
337
379
  }
338
- toPathCommands() {
380
+ getCommands() {
339
381
  const { x, y, rx, ry, startAngle, endAngle, clockwise } = this;
382
+ const anticlockwise = !clockwise;
340
383
  const startX = x + rx * Math.cos(startAngle);
341
384
  const startY = y + ry * Math.sin(startAngle);
342
385
  const endX = x + rx * Math.cos(endAngle);
343
386
  const endY = y + ry * Math.sin(endAngle);
344
- const largeArcFlag = (endAngle - startAngle) % (2 * Math.PI) > Math.PI ? 1 : 0;
345
- const sweepFlag = clockwise ? 0 : 1;
346
- return [
347
- { type: "M", x: startX, y: startY },
348
- { type: "A", rx, ry, xAxisRotation: 0, largeArcFlag, sweepFlag, x: endX, y: endY }
349
- ];
387
+ const angleDiff = Math.abs(startAngle - endAngle);
388
+ const largeArcFlag = angleDiff > Math.PI ? 1 : 0;
389
+ const sweepFlag = anticlockwise ? 0 : 1;
390
+ const midX = x + rx * Math.cos(startAngle + (endAngle - startAngle) / 2);
391
+ const midY = y + ry * Math.sin(startAngle + (endAngle - startAngle) / 2);
392
+ if (angleDiff >= 2 * Math.PI) {
393
+ return [
394
+ { type: "M", x: startX, y: startY },
395
+ { type: "A", rx, ry, xAxisRotation: 0, largeArcFlag: 1, sweepFlag, x: midX, y: midY },
396
+ { type: "A", rx, ry, xAxisRotation: 0, largeArcFlag: 1, sweepFlag, x: startX, y: startY }
397
+ ];
398
+ } else {
399
+ return [
400
+ { type: "M", x: startX, y: startY },
401
+ { type: "A", rx, ry, xAxisRotation: 0, largeArcFlag, sweepFlag, x: endX, y: endY }
402
+ ];
403
+ }
350
404
  }
351
405
  drawTo(ctx) {
352
406
  const { x, y, rx, ry, startAngle } = this;
@@ -403,12 +457,21 @@ class LineCurve extends Curve {
403
457
  getTangentAt(u, output = new Point2D()) {
404
458
  return this.getTangent(u, output);
405
459
  }
406
- toPathCommands() {
460
+ getCommands() {
461
+ const { v1, v2 } = this;
407
462
  return [
408
- { type: "M", x: this.v1.x, y: this.v1.y },
409
- { type: "L", x: this.v2.x, y: this.v2.y }
463
+ { type: "M", x: v1.x, y: v1.y },
464
+ { type: "L", x: v2.x, y: v2.y }
410
465
  ];
411
466
  }
467
+ getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
468
+ const { v1, v2 } = this;
469
+ min.x = Math.min(min.x, v1.x, v2.x);
470
+ min.y = Math.min(min.y, v1.y, v2.y);
471
+ max.x = Math.max(max.x, v1.x, v2.x);
472
+ max.y = Math.max(max.y, v1.y, v2.y);
473
+ return { min, max };
474
+ }
412
475
  drawTo(ctx) {
413
476
  const { v1, v2 } = this;
414
477
  ctx.moveTo(v1.x, v1.y);
@@ -490,8 +553,8 @@ class HeartCurve extends Curve {
490
553
  const line = this.getCurrentLine(value);
491
554
  return new Point2D(line.v2.y - line.v1.y, -(line.v2.x - line.v1.x)).normalize();
492
555
  }
493
- toPathCommands() {
494
- return this.curves.flatMap((curve) => curve.toPathCommands());
556
+ getCommands() {
557
+ return this.curves.flatMap((curve) => curve.getCommands());
495
558
  }
496
559
  drawTo(ctx) {
497
560
  this.curves.forEach((curve) => curve.drawTo(ctx));
@@ -548,8 +611,12 @@ class PloygonCurve extends Curve {
548
611
  const line = this.getCurrentLine(value);
549
612
  return new Point2D(line.v2.y - line.v1.y, -(line.v2.x - line.v1.x)).normalize();
550
613
  }
551
- toPathCommands() {
552
- return this.curves.flatMap((curve) => curve.toPathCommands());
614
+ getCommands() {
615
+ return this.curves.flatMap((curve) => curve.getCommands());
616
+ }
617
+ getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
618
+ this.curves.forEach((curve) => curve.getMinMax(min, max));
619
+ return { min, max };
553
620
  }
554
621
  drawTo(ctx) {
555
622
  this.curves.forEach((curve) => curve.drawTo(ctx));
@@ -571,8 +638,24 @@ class QuadraticBezierCurve extends Curve {
571
638
  );
572
639
  return output;
573
640
  }
574
- toPathCommands() {
575
- return [];
641
+ getCommands() {
642
+ const { v0, v1, v2 } = this;
643
+ return [
644
+ { type: "M", x: v0.x, y: v0.y },
645
+ { type: "Q", x1: v1.x, y1: v1.y, x: v2.x, y: v2.y }
646
+ ];
647
+ }
648
+ getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
649
+ const { v0, v1, v2 } = this;
650
+ const x1 = 0.5 * (v0.x + v1.x);
651
+ const y1 = 0.5 * (v0.y + v1.y);
652
+ const x2 = 0.5 * (v0.x + v2.x);
653
+ const y2 = 0.5 * (v0.y + v2.y);
654
+ min.x = Math.min(min.x, v0.x, v2.x, x1, x2);
655
+ min.y = Math.min(min.y, v0.y, v2.y, y1, y2);
656
+ max.x = Math.max(max.x, v0.x, v2.x, x1, x2);
657
+ max.y = Math.max(max.y, v0.y, v2.y, y1, y2);
658
+ return { min, max };
576
659
  }
577
660
  drawTo(ctx) {
578
661
  const { v0, v1, v2 } = this;
@@ -662,8 +745,12 @@ class RectangularCurve extends Curve {
662
745
  const { v1, v2 } = this.getCurrentLine(value);
663
746
  return new Point2D(v2.y - v1.y, -(v2.x - v1.x)).normalize();
664
747
  }
665
- toPathCommands() {
666
- return this.curves.flatMap((curve) => curve.toPathCommands());
748
+ getCommands() {
749
+ return this.curves.flatMap((curve) => curve.getCommands());
750
+ }
751
+ getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
752
+ this.curves.forEach((curve) => curve.getMinMax(min, max));
753
+ return { min, max };
667
754
  }
668
755
  drawTo(ctx) {
669
756
  this.curves.forEach((curve) => curve.drawTo(ctx));
@@ -693,7 +780,7 @@ class SplineCurve extends Curve {
693
780
  );
694
781
  return output;
695
782
  }
696
- toPathCommands() {
783
+ getCommands() {
697
784
  return [];
698
785
  }
699
786
  drawTo(_ctx) {
@@ -701,9 +788,8 @@ class SplineCurve extends Curve {
701
788
  copy(source) {
702
789
  super.copy(source);
703
790
  this.points = [];
704
- for (let i = 0, l = source.points.length; i < l; i++) {
705
- const point = source.points[i];
706
- this.points.push(point.clone());
791
+ for (let i = 0, len = source.points.length; i < len; i++) {
792
+ this.points.push(source.points[i].clone());
707
793
  }
708
794
  return this;
709
795
  }
@@ -887,8 +973,12 @@ class CurvePath extends Curve {
887
973
  this.currentPoint.copy(curve.getPoint(1));
888
974
  return this;
889
975
  }
890
- toPathCommands() {
891
- return this.curves.flatMap((curve) => curve.toPathCommands());
976
+ getCommands() {
977
+ return this.curves.flatMap((curve) => curve.getCommands());
978
+ }
979
+ getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
980
+ this.curves.forEach((curve) => curve.getMinMax(min, max));
981
+ return { min, max };
892
982
  }
893
983
  drawTo(ctx) {
894
984
  this.curves.forEach((curve) => curve.drawTo(ctx));
@@ -917,16 +1007,12 @@ class Path2D {
917
1007
  __publicField(this, "currentPath", new CurvePath());
918
1008
  __publicField(this, "paths", [this.currentPath]);
919
1009
  }
920
- arc(x, y, radius, startAngle, endAngle, counterclockwise) {
921
- this.currentPath.absarc(x, y, radius, startAngle, endAngle, !counterclockwise);
922
- return this;
923
- }
924
- bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
925
- this.currentPath.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
1010
+ addPath(path) {
1011
+ this.paths.push(...path.paths.map((v) => v.clone()));
926
1012
  return this;
927
1013
  }
928
- lineTo(x, y) {
929
- this.currentPath.lineTo(x, y);
1014
+ closePath() {
1015
+ this.currentPath.closePath();
930
1016
  return this;
931
1017
  }
932
1018
  moveTo(x, y) {
@@ -935,10 +1021,56 @@ class Path2D {
935
1021
  this.currentPath.moveTo(x, y);
936
1022
  return this;
937
1023
  }
1024
+ lineTo(x, y) {
1025
+ this.currentPath.lineTo(x, y);
1026
+ return this;
1027
+ }
1028
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
1029
+ this.currentPath.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
1030
+ return this;
1031
+ }
938
1032
  quadraticCurveTo(cpx, cpy, x, y) {
939
1033
  this.currentPath.quadraticCurveTo(cpx, cpy, x, y);
940
1034
  return this;
941
1035
  }
1036
+ arc(x, y, radius, startAngle, endAngle, counterclockwise) {
1037
+ this.currentPath.absarc(x, y, radius, startAngle, endAngle, !counterclockwise);
1038
+ return this;
1039
+ }
1040
+ arcTo(x1, y1, x2, y2, radius) {
1041
+ const point = this.currentPath.currentPoint;
1042
+ const currentX = point.x;
1043
+ const currentY = point.y;
1044
+ const dx1 = x1 - currentX;
1045
+ const dy1 = y1 - currentY;
1046
+ const dx2 = x2 - x1;
1047
+ const dy2 = y2 - y1;
1048
+ const len1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
1049
+ const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
1050
+ if (len1 < radius || len2 < radius) {
1051
+ this.lineTo(x2, y2);
1052
+ return this;
1053
+ }
1054
+ const unitV1 = { x: dx1 / len1, y: dy1 / len1 };
1055
+ const unitV2 = { x: dx2 / len2, y: dy2 / len2 };
1056
+ const centerX = x1 - unitV1.y * radius;
1057
+ const centerY = y1 + unitV1.x * radius;
1058
+ const startAngle = Math.atan2(unitV1.y, unitV1.x);
1059
+ const endAngle = Math.atan2(unitV2.y, unitV2.x);
1060
+ let angleDiff = endAngle - startAngle;
1061
+ if (angleDiff > Math.PI) {
1062
+ angleDiff -= 2 * Math.PI;
1063
+ } else if (angleDiff < -Math.PI) {
1064
+ angleDiff += 2 * Math.PI;
1065
+ }
1066
+ this.arc(centerX, centerY, radius, startAngle, startAngle + angleDiff, false);
1067
+ this.lineTo(x2, y2);
1068
+ return this;
1069
+ }
1070
+ ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
1071
+ this.currentPath.absellipse(x, y, radiusX, radiusY, startAngle, endAngle, !counterclockwise, rotation);
1072
+ return this;
1073
+ }
942
1074
  rect(x, y, w, h) {
943
1075
  this.currentPath.rect(x, y, w, h);
944
1076
  return this;
@@ -947,8 +1079,33 @@ class Path2D {
947
1079
  this.currentPath.splineThru(points);
948
1080
  return this;
949
1081
  }
950
- toPathCommands() {
951
- return this.paths.flatMap((path) => path.curves.flatMap((curve) => curve.toPathCommands()));
1082
+ getMinMax(min = new Point2D(), max = new Point2D()) {
1083
+ this.paths.forEach((path) => path.curves.forEach((curve) => curve.getMinMax(min, max)));
1084
+ return { min, max };
1085
+ }
1086
+ getCommands() {
1087
+ return this.paths.flatMap((path) => path.curves.flatMap((curve) => curve.getCommands()));
1088
+ }
1089
+ getData() {
1090
+ return this.paths.map((path) => path.getData()).join(" ");
1091
+ }
1092
+ getBoundingBox() {
1093
+ const min = Point2D.MAX;
1094
+ const max = Point2D.MIN;
1095
+ this.paths.forEach((path) => path.getMinMax(min, max));
1096
+ return {
1097
+ x: min.x,
1098
+ y: min.y,
1099
+ width: max.x - min.x,
1100
+ height: max.y - min.y
1101
+ };
1102
+ }
1103
+ getSvgString() {
1104
+ const { x, y, width, height } = this.getBoundingBox();
1105
+ return `<svg viewBox="${x} ${y} ${width} ${height}" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" d="${this.getData()}"></path></svg>`;
1106
+ }
1107
+ getSvgDataUri() {
1108
+ return `data:image/svg+xml;base64,${btoa(this.getSvgString())}`;
952
1109
  }
953
1110
  drawTo(ctx) {
954
1111
  this.paths.forEach((path) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "modern-path2d",
3
3
  "type": "module",
4
- "version": "0.0.2",
4
+ "version": "0.0.4",
5
5
  "packageManager": "pnpm@9.9.0",
6
6
  "description": "A modern Path2D library, fully compatible with Web Path2D, with additional support for path animation, path deformation, path playback, etc.",
7
7
  "author": "wxm",