modern-path2d 1.0.1 → 1.1.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.mjs CHANGED
@@ -1,3 +1,11 @@
1
+ import earcut from 'earcut';
2
+
3
+ function drawPoint(ctx, x, y, options = {}) {
4
+ const { radius = 1 } = options;
5
+ ctx.moveTo(x, y);
6
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
7
+ }
8
+
1
9
  const lineJoinMap = {
2
10
  "arcs": "bevel",
3
11
  "bevel": "bevel",
@@ -492,10 +500,145 @@ function parseArcCommand(path, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, s
492
500
  path.ellipse(cx, cy, rx, ry, xAxisRotation, theta, theta + delta, sweepFlag === 0);
493
501
  }
494
502
 
503
+ const RE$3 = {
504
+ SEPARATOR: /[ \t\r\n,.\-+]/,
505
+ WHITESPACE: /[ \t\r\n]/,
506
+ DIGIT: /\d/,
507
+ SIGN: /[-+]/,
508
+ POINT: /\./,
509
+ COMMA: /,/,
510
+ EXP: /e/i,
511
+ FLAGS: /[01]/
512
+ };
513
+ function parsePathDataArgs(input, flags, stride = 0) {
514
+ const SEP = 0;
515
+ const INT = 1;
516
+ const FLOAT = 2;
517
+ const EXP = 3;
518
+ let state = SEP;
519
+ let seenComma = true;
520
+ let number = "";
521
+ let exponent = "";
522
+ const result = [];
523
+ function throwSyntaxError(current2, i, partial) {
524
+ const error = new SyntaxError(`Unexpected character "${current2}" at index ${i}.`);
525
+ error.partial = partial;
526
+ throw error;
527
+ }
528
+ function newNumber() {
529
+ if (number !== "") {
530
+ if (exponent === "")
531
+ result.push(Number(number));
532
+ else result.push(Number(number) * 10 ** Number(exponent));
533
+ }
534
+ number = "";
535
+ exponent = "";
536
+ }
537
+ let current;
538
+ const length = input.length;
539
+ for (let i = 0; i < length; i++) {
540
+ current = input[i];
541
+ if (Array.isArray(flags) && flags.includes(result.length % stride) && RE$3.FLAGS.test(current)) {
542
+ state = INT;
543
+ number = current;
544
+ newNumber();
545
+ continue;
546
+ }
547
+ if (state === SEP) {
548
+ if (RE$3.WHITESPACE.test(current)) {
549
+ continue;
550
+ }
551
+ if (RE$3.DIGIT.test(current) || RE$3.SIGN.test(current)) {
552
+ state = INT;
553
+ number = current;
554
+ continue;
555
+ }
556
+ if (RE$3.POINT.test(current)) {
557
+ state = FLOAT;
558
+ number = current;
559
+ continue;
560
+ }
561
+ if (RE$3.COMMA.test(current)) {
562
+ if (seenComma) {
563
+ throwSyntaxError(current, i, result);
564
+ }
565
+ seenComma = true;
566
+ }
567
+ }
568
+ if (state === INT) {
569
+ if (RE$3.DIGIT.test(current)) {
570
+ number += current;
571
+ continue;
572
+ }
573
+ if (RE$3.POINT.test(current)) {
574
+ number += current;
575
+ state = FLOAT;
576
+ continue;
577
+ }
578
+ if (RE$3.EXP.test(current)) {
579
+ state = EXP;
580
+ continue;
581
+ }
582
+ if (RE$3.SIGN.test(current) && number.length === 1 && RE$3.SIGN.test(number[0])) {
583
+ throwSyntaxError(current, i, result);
584
+ }
585
+ }
586
+ if (state === FLOAT) {
587
+ if (RE$3.DIGIT.test(current)) {
588
+ number += current;
589
+ continue;
590
+ }
591
+ if (RE$3.EXP.test(current)) {
592
+ state = EXP;
593
+ continue;
594
+ }
595
+ if (RE$3.POINT.test(current) && number[number.length - 1] === ".") {
596
+ throwSyntaxError(current, i, result);
597
+ }
598
+ }
599
+ if (state === EXP) {
600
+ if (RE$3.DIGIT.test(current)) {
601
+ exponent += current;
602
+ continue;
603
+ }
604
+ if (RE$3.SIGN.test(current)) {
605
+ if (exponent === "") {
606
+ exponent += current;
607
+ continue;
608
+ }
609
+ if (exponent.length === 1 && RE$3.SIGN.test(exponent)) {
610
+ throwSyntaxError(current, i, result);
611
+ }
612
+ }
613
+ }
614
+ if (RE$3.WHITESPACE.test(current)) {
615
+ newNumber();
616
+ state = SEP;
617
+ seenComma = false;
618
+ } else if (RE$3.COMMA.test(current)) {
619
+ newNumber();
620
+ state = SEP;
621
+ seenComma = true;
622
+ } else if (RE$3.SIGN.test(current)) {
623
+ newNumber();
624
+ state = INT;
625
+ number = current;
626
+ } else if (RE$3.POINT.test(current)) {
627
+ newNumber();
628
+ state = FLOAT;
629
+ number = current;
630
+ } else {
631
+ throwSyntaxError(current, i, result);
632
+ }
633
+ }
634
+ newNumber();
635
+ return result;
636
+ }
637
+
495
638
  function getReflection(a, b) {
496
639
  return a - (b - a);
497
640
  }
498
- function addPathCommandsToPath2D(commands, path) {
641
+ function svgPathCommandsAddToPath2D(commands, path) {
499
642
  const current = new Vector2();
500
643
  const control = new Vector2();
501
644
  for (let i = 0, l = commands.length; i < l; i++) {
@@ -661,151 +804,16 @@ function addPathCommandsToPath2D(commands, path) {
661
804
  }
662
805
  }
663
806
 
664
- const RE$3 = {
665
- SEPARATOR: /[ \t\r\n,.\-+]/,
666
- WHITESPACE: /[ \t\r\n]/,
667
- DIGIT: /\d/,
668
- SIGN: /[-+]/,
669
- POINT: /\./,
670
- COMMA: /,/,
671
- EXP: /e/i,
672
- FLAGS: /[01]/
673
- };
674
- function parsePathDataArgs(input, flags, stride = 0) {
675
- const SEP = 0;
676
- const INT = 1;
677
- const FLOAT = 2;
678
- const EXP = 3;
679
- let state = SEP;
680
- let seenComma = true;
681
- let number = "";
682
- let exponent = "";
683
- const result = [];
684
- function throwSyntaxError(current2, i, partial) {
685
- const error = new SyntaxError(`Unexpected character "${current2}" at index ${i}.`);
686
- error.partial = partial;
687
- throw error;
688
- }
689
- function newNumber() {
690
- if (number !== "") {
691
- if (exponent === "")
692
- result.push(Number(number));
693
- else result.push(Number(number) * 10 ** Number(exponent));
694
- }
695
- number = "";
696
- exponent = "";
697
- }
698
- let current;
699
- const length = input.length;
700
- for (let i = 0; i < length; i++) {
701
- current = input[i];
702
- if (Array.isArray(flags) && flags.includes(result.length % stride) && RE$3.FLAGS.test(current)) {
703
- state = INT;
704
- number = current;
705
- newNumber();
706
- continue;
707
- }
708
- if (state === SEP) {
709
- if (RE$3.WHITESPACE.test(current)) {
710
- continue;
711
- }
712
- if (RE$3.DIGIT.test(current) || RE$3.SIGN.test(current)) {
713
- state = INT;
714
- number = current;
715
- continue;
716
- }
717
- if (RE$3.POINT.test(current)) {
718
- state = FLOAT;
719
- number = current;
720
- continue;
721
- }
722
- if (RE$3.COMMA.test(current)) {
723
- if (seenComma) {
724
- throwSyntaxError(current, i, result);
725
- }
726
- seenComma = true;
727
- }
728
- }
729
- if (state === INT) {
730
- if (RE$3.DIGIT.test(current)) {
731
- number += current;
732
- continue;
733
- }
734
- if (RE$3.POINT.test(current)) {
735
- number += current;
736
- state = FLOAT;
737
- continue;
738
- }
739
- if (RE$3.EXP.test(current)) {
740
- state = EXP;
741
- continue;
742
- }
743
- if (RE$3.SIGN.test(current) && number.length === 1 && RE$3.SIGN.test(number[0])) {
744
- throwSyntaxError(current, i, result);
745
- }
746
- }
747
- if (state === FLOAT) {
748
- if (RE$3.DIGIT.test(current)) {
749
- number += current;
750
- continue;
751
- }
752
- if (RE$3.EXP.test(current)) {
753
- state = EXP;
754
- continue;
755
- }
756
- if (RE$3.POINT.test(current) && number[number.length - 1] === ".") {
757
- throwSyntaxError(current, i, result);
758
- }
759
- }
760
- if (state === EXP) {
761
- if (RE$3.DIGIT.test(current)) {
762
- exponent += current;
763
- continue;
764
- }
765
- if (RE$3.SIGN.test(current)) {
766
- if (exponent === "") {
767
- exponent += current;
768
- continue;
769
- }
770
- if (exponent.length === 1 && RE$3.SIGN.test(exponent)) {
771
- throwSyntaxError(current, i, result);
772
- }
773
- }
774
- }
775
- if (RE$3.WHITESPACE.test(current)) {
776
- newNumber();
777
- state = SEP;
778
- seenComma = false;
779
- } else if (RE$3.COMMA.test(current)) {
780
- newNumber();
781
- state = SEP;
782
- seenComma = true;
783
- } else if (RE$3.SIGN.test(current)) {
784
- newNumber();
785
- state = INT;
786
- number = current;
787
- } else if (RE$3.POINT.test(current)) {
788
- newNumber();
789
- state = FLOAT;
790
- number = current;
791
- } else {
792
- throwSyntaxError(current, i, result);
793
- }
794
- }
795
- newNumber();
796
- return result;
797
- }
798
-
799
- function pathCommandsToPathData(commands) {
800
- let first;
801
- let prev;
802
- const data = [];
803
- for (let i = 0, len = commands.length; i < len; i++) {
804
- const cmd = commands[i];
805
- switch (cmd.type) {
806
- case "m":
807
- case "M":
808
- if (cmd.x.toFixed(4) === prev?.x.toFixed(4) && cmd.y.toFixed(4) === prev?.y.toFixed(4)) {
807
+ function svgPathCommandsToData(commands) {
808
+ let first;
809
+ let prev;
810
+ const data = [];
811
+ for (let i = 0, len = commands.length; i < len; i++) {
812
+ const cmd = commands[i];
813
+ switch (cmd.type) {
814
+ case "m":
815
+ case "M":
816
+ if (cmd.x.toFixed(4) === prev?.x.toFixed(4) && cmd.y.toFixed(4) === prev?.y.toFixed(4)) {
809
817
  continue;
810
818
  }
811
819
  data.push(`${cmd.type} ${cmd.x} ${cmd.y}`);
@@ -865,21 +873,21 @@ function pathCommandsToPathData(commands) {
865
873
  }
866
874
 
867
875
  const RE$2 = /[a-df-z][^a-df-z]*/gi;
868
- function pathDataToPathCommands(d) {
876
+ function svgPathDataToCommands(data) {
869
877
  const commands = [];
870
- const matched = d.match(RE$2);
878
+ const matched = data.match(RE$2);
871
879
  if (!matched) {
872
880
  return commands;
873
881
  }
874
882
  for (let i = 0, len = matched.length; i < len; i++) {
875
883
  const command = matched[i];
876
884
  const type = command.charAt(0);
877
- const data = command.slice(1).trim();
885
+ const data2 = command.slice(1).trim();
878
886
  let args;
879
887
  switch (type) {
880
888
  case "m":
881
889
  case "M":
882
- args = parsePathDataArgs(data);
890
+ args = parsePathDataArgs(data2);
883
891
  for (let i2 = 0, len2 = args.length; i2 < len2; i2 += 2) {
884
892
  if (i2 === 0) {
885
893
  commands.push({ type, x: args[i2], y: args[i2 + 1] });
@@ -890,28 +898,28 @@ function pathDataToPathCommands(d) {
890
898
  break;
891
899
  case "h":
892
900
  case "H":
893
- args = parsePathDataArgs(data);
901
+ args = parsePathDataArgs(data2);
894
902
  for (let i2 = 0, len2 = args.length; i2 < len2; i2++) {
895
903
  commands.push({ type, x: args[i2] });
896
904
  }
897
905
  break;
898
906
  case "v":
899
907
  case "V":
900
- args = parsePathDataArgs(data);
908
+ args = parsePathDataArgs(data2);
901
909
  for (let i2 = 0, len2 = args.length; i2 < len2; i2++) {
902
910
  commands.push({ type, y: args[i2] });
903
911
  }
904
912
  break;
905
913
  case "l":
906
914
  case "L":
907
- args = parsePathDataArgs(data);
915
+ args = parsePathDataArgs(data2);
908
916
  for (let i2 = 0, len2 = args.length; i2 < len2; i2 += 2) {
909
917
  commands.push({ type, x: args[i2], y: args[i2 + 1] });
910
918
  }
911
919
  break;
912
920
  case "c":
913
921
  case "C":
914
- args = parsePathDataArgs(data);
922
+ args = parsePathDataArgs(data2);
915
923
  for (let i2 = 0, len2 = args.length; i2 < len2; i2 += 6) {
916
924
  commands.push({
917
925
  type,
@@ -926,7 +934,7 @@ function pathDataToPathCommands(d) {
926
934
  break;
927
935
  case "s":
928
936
  case "S":
929
- args = parsePathDataArgs(data);
937
+ args = parsePathDataArgs(data2);
930
938
  for (let i2 = 0, len2 = args.length; i2 < len2; i2 += 4) {
931
939
  commands.push({
932
940
  type,
@@ -939,7 +947,7 @@ function pathDataToPathCommands(d) {
939
947
  break;
940
948
  case "q":
941
949
  case "Q":
942
- args = parsePathDataArgs(data);
950
+ args = parsePathDataArgs(data2);
943
951
  for (let i2 = 0, len2 = args.length; i2 < len2; i2 += 4) {
944
952
  commands.push({
945
953
  type,
@@ -952,7 +960,7 @@ function pathDataToPathCommands(d) {
952
960
  break;
953
961
  case "t":
954
962
  case "T":
955
- args = parsePathDataArgs(data);
963
+ args = parsePathDataArgs(data2);
956
964
  for (let i2 = 0, len2 = args.length; i2 < len2; i2 += 2) {
957
965
  commands.push({
958
966
  type,
@@ -963,7 +971,7 @@ function pathDataToPathCommands(d) {
963
971
  break;
964
972
  case "a":
965
973
  case "A":
966
- args = parsePathDataArgs(data, [3, 4], 7);
974
+ args = parsePathDataArgs(data2, [3, 4], 7);
967
975
  for (let i2 = 0, len2 = args.length; i2 < len2; i2 += 7) {
968
976
  commands.push({
969
977
  type,
@@ -990,80 +998,684 @@ function pathDataToPathCommands(d) {
990
998
  return commands;
991
999
  }
992
1000
 
1001
+ function catmullRom(t, p0, p1, p2, p3) {
1002
+ const v0 = (p2 - p0) * 0.5;
1003
+ const v1 = (p3 - p1) * 0.5;
1004
+ const t2 = t * t;
1005
+ const t3 = t * t2;
1006
+ return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
1007
+ }
1008
+
1009
+ function cubicBezierP0(t, p) {
1010
+ const k = 1 - t;
1011
+ return k * k * k * p;
1012
+ }
1013
+ function cubicBezierP1(t, p) {
1014
+ const k = 1 - t;
1015
+ return 3 * k * k * t * p;
1016
+ }
1017
+ function cubicBezierP2(t, p) {
1018
+ return 3 * (1 - t) * t * t * p;
1019
+ }
1020
+ function cubicBezierP3(t, p) {
1021
+ return t * t * t * p;
1022
+ }
1023
+ function cubicBezier(t, p0, p1, p2, p3) {
1024
+ return cubicBezierP0(t, p0) + cubicBezierP1(t, p1) + cubicBezierP2(t, p2) + cubicBezierP3(t, p3);
1025
+ }
1026
+
1027
+ function fillTriangulate(points, options = {}) {
1028
+ let {
1029
+ vertices = [],
1030
+ indices = [],
1031
+ holes = [],
1032
+ verticesStride = 2,
1033
+ verticesOffset = 0,
1034
+ indicesOffset = 0
1035
+ } = options;
1036
+ const triangles = earcut(points, holes, 2);
1037
+ if (triangles) {
1038
+ for (let i = 0; i < triangles.length; i += 3) {
1039
+ indices[indicesOffset++] = triangles[i] + verticesOffset;
1040
+ indices[indicesOffset++] = triangles[i + 1] + verticesOffset;
1041
+ indices[indicesOffset++] = triangles[i + 2] + verticesOffset;
1042
+ }
1043
+ let index = verticesOffset * verticesStride;
1044
+ for (let i = 0; i < points.length; i += 2) {
1045
+ vertices[index] = points[i];
1046
+ vertices[index + 1] = points[i + 1];
1047
+ index += verticesStride;
1048
+ }
1049
+ }
1050
+ return {
1051
+ vertices,
1052
+ indices
1053
+ };
1054
+ }
1055
+
1056
+ const RECURSION_LIMIT$1 = 8;
1057
+ const FLT_EPSILON$1 = 11920929e-14;
1058
+ const PATH_DISTANCE_EPSILON$1 = 1;
1059
+ function getAdaptiveCubicBezierCurvePoints(sX, sY, x1, y1, x2, y2, x, y, smoothness = 0.5, points) {
1060
+ const scale = 1;
1061
+ const smoothing = Math.min(
1062
+ 0.99,
1063
+ // a value of 1.0 actually inverts smoothing, so we cap it at 0.99
1064
+ Math.max(0, smoothness)
1065
+ );
1066
+ let distanceTolerance = (PATH_DISTANCE_EPSILON$1 - smoothing) / scale;
1067
+ distanceTolerance *= distanceTolerance;
1068
+ recursive$1(sX, sY, x1, y1, x2, y2, x, y, points, distanceTolerance, 0);
1069
+ points.push(x, y);
1070
+ return points;
1071
+ }
1072
+ function recursive$1(x1, y1, x2, y2, x3, y3, x4, y4, points, distanceTolerance, level) {
1073
+ if (level > RECURSION_LIMIT$1)
1074
+ return;
1075
+ const x12 = (x1 + x2) / 2;
1076
+ const y12 = (y1 + y2) / 2;
1077
+ const x23 = (x2 + x3) / 2;
1078
+ const y23 = (y2 + y3) / 2;
1079
+ const x34 = (x3 + x4) / 2;
1080
+ const y34 = (y3 + y4) / 2;
1081
+ const x123 = (x12 + x23) / 2;
1082
+ const y123 = (y12 + y23) / 2;
1083
+ const x234 = (x23 + x34) / 2;
1084
+ const y234 = (y23 + y34) / 2;
1085
+ const x1234 = (x123 + x234) / 2;
1086
+ const y1234 = (y123 + y234) / 2;
1087
+ if (level > 0) {
1088
+ let dx = x4 - x1;
1089
+ let dy = y4 - y1;
1090
+ const d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx);
1091
+ const d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx);
1092
+ if (d2 > FLT_EPSILON$1 && d3 > FLT_EPSILON$1) {
1093
+ if ((d2 + d3) * (d2 + d3) <= distanceTolerance * (dx * dx + dy * dy)) {
1094
+ {
1095
+ points.push(x1234, y1234);
1096
+ return;
1097
+ }
1098
+ }
1099
+ } else if (d2 > FLT_EPSILON$1) {
1100
+ if (d2 * d2 <= distanceTolerance * (dx * dx + dy * dy)) {
1101
+ {
1102
+ points.push(x1234, y1234);
1103
+ return;
1104
+ }
1105
+ }
1106
+ } else if (d3 > FLT_EPSILON$1) {
1107
+ if (d3 * d3 <= distanceTolerance * (dx * dx + dy * dy)) {
1108
+ {
1109
+ points.push(x1234, y1234);
1110
+ return;
1111
+ }
1112
+ }
1113
+ } else {
1114
+ dx = x1234 - (x1 + x4) / 2;
1115
+ dy = y1234 - (y1 + y4) / 2;
1116
+ if (dx * dx + dy * dy <= distanceTolerance) {
1117
+ points.push(x1234, y1234);
1118
+ return;
1119
+ }
1120
+ }
1121
+ }
1122
+ recursive$1(x1, y1, x12, y12, x123, y123, x1234, y1234, points, distanceTolerance, level + 1);
1123
+ recursive$1(x1234, y1234, x234, y234, x34, y34, x4, y4, points, distanceTolerance, level + 1);
1124
+ }
1125
+
1126
+ const RECURSION_LIMIT = 8;
1127
+ const FLT_EPSILON = 11920929e-14;
1128
+ const PATH_DISTANCE_EPSILON = 1;
1129
+ function getAdaptiveQuadraticBezierCurvePoints(sX, sY, x1, y1, x, y, smoothness = 0.5, points) {
1130
+ const scale = 1;
1131
+ const smoothing = Math.min(
1132
+ 0.99,
1133
+ // a value of 1.0 actually inverts smoothing, so we cap it at 0.99
1134
+ Math.max(0, smoothness)
1135
+ );
1136
+ let distanceTolerance = (PATH_DISTANCE_EPSILON - smoothing) / scale;
1137
+ distanceTolerance *= distanceTolerance;
1138
+ recursive(points, sX, sY, x1, y1, x, y, distanceTolerance, 0);
1139
+ points.push(x, y);
1140
+ return points;
1141
+ }
1142
+ function recursive(points, x1, y1, x2, y2, x3, y3, distanceTolerance, level) {
1143
+ if (level > RECURSION_LIMIT)
1144
+ return;
1145
+ const x12 = (x1 + x2) / 2;
1146
+ const y12 = (y1 + y2) / 2;
1147
+ const x23 = (x2 + x3) / 2;
1148
+ const y23 = (y2 + y3) / 2;
1149
+ const x123 = (x12 + x23) / 2;
1150
+ const y123 = (y12 + y23) / 2;
1151
+ let dx = x3 - x1;
1152
+ let dy = y3 - y1;
1153
+ const d = Math.abs((x2 - x3) * dy - (y2 - y3) * dx);
1154
+ if (d > FLT_EPSILON) {
1155
+ if (d * d <= distanceTolerance * (dx * dx + dy * dy)) {
1156
+ {
1157
+ points.push(x123, y123);
1158
+ return;
1159
+ }
1160
+ }
1161
+ } else {
1162
+ dx = x123 - (x1 + x3) / 2;
1163
+ dy = y123 - (y1 + y3) / 2;
1164
+ if (dx * dx + dy * dy <= distanceTolerance) {
1165
+ points.push(x123, y123);
1166
+ return;
1167
+ }
1168
+ }
1169
+ recursive(points, x1, y1, x12, y12, x123, y123, distanceTolerance, level + 1);
1170
+ recursive(points, x123, y123, x23, y23, x3, y3, distanceTolerance, level + 1);
1171
+ }
1172
+
1173
+ function quadraticBezierP0(t, p) {
1174
+ const k = 1 - t;
1175
+ return k * k * p;
1176
+ }
1177
+ function quadraticBezierP1(t, p) {
1178
+ return 2 * (1 - t) * t * p;
1179
+ }
1180
+ function quadraticBezierP2(t, p) {
1181
+ return t * t * p;
1182
+ }
1183
+ function quadraticBezier(t, p0, p1, p2) {
1184
+ return quadraticBezierP0(t, p0) + quadraticBezierP1(t, p1) + quadraticBezierP2(t, p2);
1185
+ }
1186
+
1187
+ const closePointEps = 1e-4;
1188
+ const curveEps = 1e-4;
1189
+ function strokeTriangulate(points, options = {}) {
1190
+ const {
1191
+ vertices = [],
1192
+ indices = [],
1193
+ lineStyle = {
1194
+ alignment: 0.5,
1195
+ cap: "butt",
1196
+ join: "miter",
1197
+ width: 1,
1198
+ miterLimit: 10
1199
+ },
1200
+ flipAlignment = false,
1201
+ closed = true
1202
+ } = options;
1203
+ const eps = closePointEps;
1204
+ if (points.length === 0) {
1205
+ return { vertices, indices };
1206
+ }
1207
+ const style = lineStyle;
1208
+ let alignment = style.alignment;
1209
+ if (lineStyle.alignment !== 0.5) {
1210
+ let orientation = getOrientationOfPoints(points);
1211
+ if (flipAlignment)
1212
+ orientation *= -1;
1213
+ alignment = (alignment - 0.5) * orientation + 0.5;
1214
+ }
1215
+ const firstPoint = { x: points[0], y: points[1] };
1216
+ const lastPoint = { x: points[points.length - 2], y: points[points.length - 1] };
1217
+ const closedShape = closed;
1218
+ const closedPath = Math.abs(firstPoint.x - lastPoint.x) < eps && Math.abs(firstPoint.y - lastPoint.y) < eps;
1219
+ if (closedShape) {
1220
+ points = points.slice();
1221
+ if (closedPath) {
1222
+ points.pop();
1223
+ points.pop();
1224
+ lastPoint.x = points[points.length - 2];
1225
+ lastPoint.y = points[points.length - 1];
1226
+ }
1227
+ const midPointX = (firstPoint.x + lastPoint.x) * 0.5;
1228
+ const midPointY = (lastPoint.y + firstPoint.y) * 0.5;
1229
+ points.unshift(midPointX, midPointY);
1230
+ points.push(midPointX, midPointY);
1231
+ }
1232
+ const verts = vertices;
1233
+ const length = points.length / 2;
1234
+ let indexCount = points.length;
1235
+ const indexStart = verts.length / 2;
1236
+ const width = style.width / 2;
1237
+ const widthSquared = width * width;
1238
+ const miterLimitSquared = style.miterLimit * style.miterLimit;
1239
+ let x0 = points[0];
1240
+ let y0 = points[1];
1241
+ let x1 = points[2];
1242
+ let y1 = points[3];
1243
+ let x2 = 0;
1244
+ let y2 = 0;
1245
+ let perpX = -(y0 - y1);
1246
+ let perpY = x0 - x1;
1247
+ let perp1x = 0;
1248
+ let perp1y = 0;
1249
+ let dist = Math.sqrt(perpX * perpX + perpY * perpY);
1250
+ perpX /= dist;
1251
+ perpY /= dist;
1252
+ perpX *= width;
1253
+ perpY *= width;
1254
+ const ratio = alignment;
1255
+ const innerWeight = (1 - ratio) * 2;
1256
+ const outerWeight = ratio * 2;
1257
+ if (!closedShape) {
1258
+ if (style.cap === "round") {
1259
+ indexCount += round(
1260
+ x0 - perpX * (innerWeight - outerWeight) * 0.5,
1261
+ y0 - perpY * (innerWeight - outerWeight) * 0.5,
1262
+ x0 - perpX * innerWeight,
1263
+ y0 - perpY * innerWeight,
1264
+ x0 + perpX * outerWeight,
1265
+ y0 + perpY * outerWeight,
1266
+ verts,
1267
+ true
1268
+ ) + 2;
1269
+ } else if (style.cap === "square") {
1270
+ indexCount += square(x0, y0, perpX, perpY, innerWeight, outerWeight, true, verts);
1271
+ }
1272
+ }
1273
+ verts.push(
1274
+ x0 - perpX * innerWeight,
1275
+ y0 - perpY * innerWeight
1276
+ );
1277
+ verts.push(
1278
+ x0 + perpX * outerWeight,
1279
+ y0 + perpY * outerWeight
1280
+ );
1281
+ for (let i = 1; i < length - 1; ++i) {
1282
+ x0 = points[(i - 1) * 2];
1283
+ y0 = points[(i - 1) * 2 + 1];
1284
+ x1 = points[i * 2];
1285
+ y1 = points[i * 2 + 1];
1286
+ x2 = points[(i + 1) * 2];
1287
+ y2 = points[(i + 1) * 2 + 1];
1288
+ perpX = -(y0 - y1);
1289
+ perpY = x0 - x1;
1290
+ dist = Math.sqrt(perpX * perpX + perpY * perpY);
1291
+ perpX /= dist;
1292
+ perpY /= dist;
1293
+ perpX *= width;
1294
+ perpY *= width;
1295
+ perp1x = -(y1 - y2);
1296
+ perp1y = x1 - x2;
1297
+ dist = Math.sqrt(perp1x * perp1x + perp1y * perp1y);
1298
+ perp1x /= dist;
1299
+ perp1y /= dist;
1300
+ perp1x *= width;
1301
+ perp1y *= width;
1302
+ const dx0 = x1 - x0;
1303
+ const dy0 = y0 - y1;
1304
+ const dx1 = x1 - x2;
1305
+ const dy1 = y2 - y1;
1306
+ const dot = dx0 * dx1 + dy0 * dy1;
1307
+ const cross = dy0 * dx1 - dy1 * dx0;
1308
+ const clockwise = cross < 0;
1309
+ if (Math.abs(cross) < 1e-3 * Math.abs(dot)) {
1310
+ verts.push(
1311
+ x1 - perpX * innerWeight,
1312
+ y1 - perpY * innerWeight
1313
+ );
1314
+ verts.push(
1315
+ x1 + perpX * outerWeight,
1316
+ y1 + perpY * outerWeight
1317
+ );
1318
+ if (dot >= 0) {
1319
+ if (style.join === "round") {
1320
+ indexCount += round(
1321
+ x1,
1322
+ y1,
1323
+ x1 - perpX * innerWeight,
1324
+ y1 - perpY * innerWeight,
1325
+ x1 - perp1x * innerWeight,
1326
+ y1 - perp1y * innerWeight,
1327
+ verts,
1328
+ false
1329
+ ) + 4;
1330
+ } else {
1331
+ indexCount += 2;
1332
+ }
1333
+ verts.push(
1334
+ x1 - perp1x * outerWeight,
1335
+ y1 - perp1y * outerWeight
1336
+ );
1337
+ verts.push(
1338
+ x1 + perp1x * innerWeight,
1339
+ y1 + perp1y * innerWeight
1340
+ );
1341
+ }
1342
+ continue;
1343
+ }
1344
+ const c1 = (-perpX + x0) * (-perpY + y1) - (-perpX + x1) * (-perpY + y0);
1345
+ const c2 = (-perp1x + x2) * (-perp1y + y1) - (-perp1x + x1) * (-perp1y + y2);
1346
+ const px = (dx0 * c2 - dx1 * c1) / cross;
1347
+ const py = (dy1 * c1 - dy0 * c2) / cross;
1348
+ const pDist = (px - x1) * (px - x1) + (py - y1) * (py - y1);
1349
+ const imx = x1 + (px - x1) * innerWeight;
1350
+ const imy = y1 + (py - y1) * innerWeight;
1351
+ const omx = x1 - (px - x1) * outerWeight;
1352
+ const omy = y1 - (py - y1) * outerWeight;
1353
+ const smallerInsideSegmentSq = Math.min(dx0 * dx0 + dy0 * dy0, dx1 * dx1 + dy1 * dy1);
1354
+ const insideWeight = clockwise ? innerWeight : outerWeight;
1355
+ const smallerInsideDiagonalSq = smallerInsideSegmentSq + insideWeight * insideWeight * widthSquared;
1356
+ const insideMiterOk = pDist <= smallerInsideDiagonalSq;
1357
+ if (insideMiterOk) {
1358
+ if (style.join === "bevel" || pDist / widthSquared > miterLimitSquared) {
1359
+ if (clockwise) {
1360
+ verts.push(imx, imy);
1361
+ verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight);
1362
+ verts.push(imx, imy);
1363
+ verts.push(x1 + perp1x * outerWeight, y1 + perp1y * outerWeight);
1364
+ } else {
1365
+ verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight);
1366
+ verts.push(omx, omy);
1367
+ verts.push(x1 - perp1x * innerWeight, y1 - perp1y * innerWeight);
1368
+ verts.push(omx, omy);
1369
+ }
1370
+ indexCount += 2;
1371
+ } else if (style.join === "round") {
1372
+ if (clockwise) {
1373
+ verts.push(imx, imy);
1374
+ verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight);
1375
+ indexCount += round(
1376
+ x1,
1377
+ y1,
1378
+ x1 + perpX * outerWeight,
1379
+ y1 + perpY * outerWeight,
1380
+ x1 + perp1x * outerWeight,
1381
+ y1 + perp1y * outerWeight,
1382
+ verts,
1383
+ true
1384
+ ) + 4;
1385
+ verts.push(imx, imy);
1386
+ verts.push(x1 + perp1x * outerWeight, y1 + perp1y * outerWeight);
1387
+ } else {
1388
+ verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight);
1389
+ verts.push(omx, omy);
1390
+ indexCount += round(
1391
+ x1,
1392
+ y1,
1393
+ x1 - perpX * innerWeight,
1394
+ y1 - perpY * innerWeight,
1395
+ x1 - perp1x * innerWeight,
1396
+ y1 - perp1y * innerWeight,
1397
+ verts,
1398
+ false
1399
+ ) + 4;
1400
+ verts.push(x1 - perp1x * innerWeight, y1 - perp1y * innerWeight);
1401
+ verts.push(omx, omy);
1402
+ }
1403
+ } else {
1404
+ verts.push(imx, imy);
1405
+ verts.push(omx, omy);
1406
+ }
1407
+ } else {
1408
+ verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight);
1409
+ verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight);
1410
+ if (style.join === "round") {
1411
+ if (clockwise) {
1412
+ indexCount += round(
1413
+ x1,
1414
+ y1,
1415
+ x1 + perpX * outerWeight,
1416
+ y1 + perpY * outerWeight,
1417
+ x1 + perp1x * outerWeight,
1418
+ y1 + perp1y * outerWeight,
1419
+ verts,
1420
+ true
1421
+ ) + 2;
1422
+ } else {
1423
+ indexCount += round(
1424
+ x1,
1425
+ y1,
1426
+ x1 - perpX * innerWeight,
1427
+ y1 - perpY * innerWeight,
1428
+ x1 - perp1x * innerWeight,
1429
+ y1 - perp1y * innerWeight,
1430
+ verts,
1431
+ false
1432
+ ) + 2;
1433
+ }
1434
+ } else if (style.join === "miter" && pDist / widthSquared <= miterLimitSquared) {
1435
+ if (clockwise) {
1436
+ verts.push(omx, omy);
1437
+ verts.push(omx, omy);
1438
+ } else {
1439
+ verts.push(imx, imy);
1440
+ verts.push(imx, imy);
1441
+ }
1442
+ indexCount += 2;
1443
+ }
1444
+ verts.push(x1 - perp1x * innerWeight, y1 - perp1y * innerWeight);
1445
+ verts.push(x1 + perp1x * outerWeight, y1 + perp1y * outerWeight);
1446
+ indexCount += 2;
1447
+ }
1448
+ }
1449
+ x0 = points[(length - 2) * 2];
1450
+ y0 = points[(length - 2) * 2 + 1];
1451
+ x1 = points[(length - 1) * 2];
1452
+ y1 = points[(length - 1) * 2 + 1];
1453
+ perpX = -(y0 - y1);
1454
+ perpY = x0 - x1;
1455
+ dist = Math.sqrt(perpX * perpX + perpY * perpY);
1456
+ perpX /= dist;
1457
+ perpY /= dist;
1458
+ perpX *= width;
1459
+ perpY *= width;
1460
+ verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight);
1461
+ verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight);
1462
+ if (!closedShape) {
1463
+ if (style.cap === "round") {
1464
+ indexCount += round(
1465
+ x1 - perpX * (innerWeight - outerWeight) * 0.5,
1466
+ y1 - perpY * (innerWeight - outerWeight) * 0.5,
1467
+ x1 - perpX * innerWeight,
1468
+ y1 - perpY * innerWeight,
1469
+ x1 + perpX * outerWeight,
1470
+ y1 + perpY * outerWeight,
1471
+ verts,
1472
+ false
1473
+ ) + 2;
1474
+ } else if (style.cap === "square") {
1475
+ indexCount += square(x1, y1, perpX, perpY, innerWeight, outerWeight, false, verts);
1476
+ }
1477
+ }
1478
+ const eps2 = curveEps * curveEps;
1479
+ for (let i = indexStart; i < indexCount + indexStart - 2; ++i) {
1480
+ x0 = verts[i * 2];
1481
+ y0 = verts[i * 2 + 1];
1482
+ x1 = verts[(i + 1) * 2];
1483
+ y1 = verts[(i + 1) * 2 + 1];
1484
+ x2 = verts[(i + 2) * 2];
1485
+ y2 = verts[(i + 2) * 2 + 1];
1486
+ if (Math.abs(x0 * (y1 - y2) + x1 * (y2 - y0) + x2 * (y0 - y1)) < eps2) {
1487
+ continue;
1488
+ }
1489
+ indices.push(i, i + 1, i + 2);
1490
+ }
1491
+ return {
1492
+ vertices,
1493
+ indices
1494
+ };
1495
+ }
1496
+ function getOrientationOfPoints(points) {
1497
+ const m = points.length;
1498
+ if (m < 6) {
1499
+ return 1;
1500
+ }
1501
+ let area = 0;
1502
+ for (let i = 0, x1 = points[m - 2], y1 = points[m - 1]; i < m; i += 2) {
1503
+ const x2 = points[i];
1504
+ const y2 = points[i + 1];
1505
+ area += (x2 - x1) * (y2 + y1);
1506
+ x1 = x2;
1507
+ y1 = y2;
1508
+ }
1509
+ if (area < 0) {
1510
+ return -1;
1511
+ }
1512
+ return 1;
1513
+ }
1514
+ function square(x, y, nx, ny, innerWeight, outerWeight, clockwise, verts) {
1515
+ const ix = x - nx * innerWeight;
1516
+ const iy = y - ny * innerWeight;
1517
+ const ox = x + nx * outerWeight;
1518
+ const oy = y + ny * outerWeight;
1519
+ let exx;
1520
+ let eyy;
1521
+ if (clockwise) {
1522
+ exx = ny;
1523
+ eyy = -nx;
1524
+ } else {
1525
+ exx = -ny;
1526
+ eyy = nx;
1527
+ }
1528
+ const eix = ix + exx;
1529
+ const eiy = iy + eyy;
1530
+ const eox = ox + exx;
1531
+ const eoy = oy + eyy;
1532
+ verts.push(eix, eiy);
1533
+ verts.push(eox, eoy);
1534
+ return 2;
1535
+ }
1536
+ function round(cx, cy, sx, sy, ex, ey, verts, clockwise) {
1537
+ const cx2p0x = sx - cx;
1538
+ const cy2p0y = sy - cy;
1539
+ let angle0 = Math.atan2(cx2p0x, cy2p0y);
1540
+ let angle1 = Math.atan2(ex - cx, ey - cy);
1541
+ if (clockwise && angle0 < angle1) {
1542
+ angle0 += Math.PI * 2;
1543
+ } else if (!clockwise && angle0 > angle1) {
1544
+ angle1 += Math.PI * 2;
1545
+ }
1546
+ let startAngle = angle0;
1547
+ const angleDiff = angle1 - angle0;
1548
+ const absAngleDiff = Math.abs(angleDiff);
1549
+ const radius = Math.sqrt(cx2p0x * cx2p0x + cy2p0y * cy2p0y);
1550
+ const segCount = (15 * absAngleDiff * Math.sqrt(radius) / Math.PI >> 0) + 1;
1551
+ const angleInc = angleDiff / segCount;
1552
+ startAngle += angleInc;
1553
+ if (clockwise) {
1554
+ verts.push(cx, cy);
1555
+ verts.push(sx, sy);
1556
+ for (let i = 1, angle = startAngle; i < segCount; i++, angle += angleInc) {
1557
+ verts.push(cx, cy);
1558
+ verts.push(cx + Math.sin(angle) * radius, cy + Math.cos(angle) * radius);
1559
+ }
1560
+ verts.push(cx, cy);
1561
+ verts.push(ex, ey);
1562
+ } else {
1563
+ verts.push(sx, sy);
1564
+ verts.push(cx, cy);
1565
+ for (let i = 1, angle = startAngle; i < segCount; i++, angle += angleInc) {
1566
+ verts.push(cx + Math.sin(angle) * radius, cy + Math.cos(angle) * radius);
1567
+ verts.push(cx, cy);
1568
+ }
1569
+ verts.push(ex, ey);
1570
+ verts.push(cx, cy);
1571
+ }
1572
+ return segCount * 2;
1573
+ }
1574
+
993
1575
  class Curve {
994
- arcLengthDivisions = 200;
995
- _cacheArcLengths;
996
- _needsUpdate = false;
1576
+ startT = 0;
1577
+ endT = 1;
1578
+ arcLengthDivision = 200;
1579
+ _arcLengths;
1580
+ getPointAt(u, output = new Vector2()) {
1581
+ return this.getPoint(this.getUToTMapping(u), output);
1582
+ }
997
1583
  isClockwise() {
998
1584
  const prev = this.getPoint(1);
999
1585
  const cur = this.getPoint(0.5);
1000
1586
  const next = this.getPoint(1);
1001
1587
  return (cur.x - prev.x) * (next.y - cur.y) - (cur.y - prev.y) * (next.x - cur.x) < 0;
1002
1588
  }
1003
- getPointAt(u, output = new Vector2()) {
1004
- return this.getPoint(this.getUToTMapping(u), output);
1589
+ getControlPointRefs() {
1590
+ return [];
1005
1591
  }
1006
- getPoints(divisions = 5) {
1007
- const points = [];
1008
- for (let i = 0; i <= divisions; i++) {
1009
- points.push(this.getPoint(i / divisions));
1592
+ getUnevenPointArray(count = 5, output = []) {
1593
+ const p = new Vector2();
1594
+ for (let i = 0, len = Math.max(1, count) - 1; i <= len; i++) {
1595
+ this.getPoint(i / len, p);
1596
+ output.push(p.x, p.y);
1010
1597
  }
1011
- return points;
1598
+ return output;
1012
1599
  }
1013
- forEachControlPoints(cb) {
1014
- this.getControlPoints().forEach(cb);
1015
- return this;
1600
+ getSpacedPointArray(count = 5, output = []) {
1601
+ const p = new Vector2();
1602
+ for (let i = 0, len = Math.max(1, count) - 1; i <= len; i++) {
1603
+ this.getPointAt(i / len, p);
1604
+ output.push(p.x, p.y);
1605
+ }
1606
+ return output;
1016
1607
  }
1017
- getSpacedPoints(divisions = 5) {
1018
- const points = [];
1019
- for (let i = 0; i <= divisions; i++) {
1020
- points.push(this.getPointAt(i / divisions));
1608
+ getAdaptivePointArray(output = []) {
1609
+ return this.getUnevenPointArray(5, output);
1610
+ }
1611
+ _pointArrayToPoint(array, output = []) {
1612
+ for (let i = 0, len = array.length; i < len; i += 2) {
1613
+ const x = array[i];
1614
+ const y = array[i + 1];
1615
+ output.push(new Vector2(x, y));
1616
+ }
1617
+ return output;
1618
+ }
1619
+ getSpacedPoints(count, output = []) {
1620
+ const array = this.getSpacedPointArray(count);
1621
+ this._pointArrayToPoint(array, output);
1622
+ return output;
1623
+ }
1624
+ getUnevenPoints(count, output = []) {
1625
+ const array = this.getUnevenPointArray(count);
1626
+ this._pointArrayToPoint(array, output);
1627
+ return output;
1628
+ }
1629
+ getAdaptivePoints(output = []) {
1630
+ const array = this.getAdaptivePointArray();
1631
+ this._pointArrayToPoint(array, output);
1632
+ return output;
1633
+ }
1634
+ getPoints(count, output = []) {
1635
+ let array;
1636
+ if (count) {
1637
+ array = this.getUnevenPointArray(count);
1638
+ } else {
1639
+ array = this.getAdaptivePointArray();
1021
1640
  }
1022
- return points;
1641
+ this._pointArrayToPoint(array, output);
1642
+ return output;
1023
1643
  }
1024
1644
  getLength() {
1025
1645
  const lengths = this.getLengths();
1026
1646
  return lengths[lengths.length - 1];
1027
1647
  }
1028
- getLengths(divisions = this.arcLengthDivisions) {
1029
- if (this._cacheArcLengths && this._cacheArcLengths.length === divisions + 1 && !this._needsUpdate) {
1030
- return this._cacheArcLengths;
1031
- }
1032
- this._needsUpdate = false;
1033
- const cache = [];
1034
- let current;
1035
- let last = this.getPoint(0);
1036
- let sum = 0;
1037
- cache.push(0);
1038
- for (let i = 1; i <= divisions; i++) {
1039
- current = this.getPoint(i / divisions);
1040
- sum += current.distanceTo(last);
1041
- cache.push(sum);
1042
- last = current;
1043
- }
1044
- this._cacheArcLengths = cache;
1045
- return cache;
1046
- }
1047
- updateArcLengths() {
1048
- this._needsUpdate = true;
1049
- this.getLengths();
1648
+ getLengths() {
1649
+ if (!this._arcLengths || this._arcLengths.length !== this.arcLengthDivision + 1) {
1650
+ this.updateLengths();
1651
+ }
1652
+ return this._arcLengths;
1653
+ }
1654
+ updateLengths() {
1655
+ const divisions = this.arcLengthDivision;
1656
+ const arcLengths = [0];
1657
+ for (let sum = 0, prev = this.getPoint(0), i = 1; i <= divisions; i++) {
1658
+ const current = this.getPoint(i / divisions);
1659
+ sum += current.distanceTo(prev);
1660
+ arcLengths.push(sum);
1661
+ prev = current;
1662
+ }
1663
+ this._arcLengths = arcLengths;
1050
1664
  }
1051
1665
  getUToTMapping(u, distance) {
1052
1666
  const lengths = this.getLengths();
1053
- let i = 0;
1054
1667
  const lengthsLen = lengths.length;
1055
- let targetLength;
1056
- if (distance) {
1057
- targetLength = distance;
1058
- } else {
1059
- targetLength = u * lengths[lengthsLen - 1];
1668
+ const targetLen = distance ?? u * lengths[lengthsLen - 1];
1669
+ if (lengthsLen < 2) {
1670
+ return targetLen / lengths[0];
1060
1671
  }
1672
+ let i = 0;
1061
1673
  let low = 0;
1062
1674
  let high = lengthsLen - 1;
1063
1675
  let comparison;
1064
1676
  while (low <= high) {
1065
1677
  i = Math.floor(low + (high - low) / 2);
1066
- comparison = lengths[i] - targetLength;
1678
+ comparison = lengths[i] - targetLen;
1067
1679
  if (comparison < 0) {
1068
1680
  low = i + 1;
1069
1681
  } else if (comparison > 0) {
@@ -1074,13 +1686,13 @@ class Curve {
1074
1686
  }
1075
1687
  }
1076
1688
  i = high;
1077
- if (lengths[i] === targetLength) {
1689
+ if (lengths[i] === targetLen) {
1078
1690
  return i / (lengthsLen - 1);
1079
1691
  }
1080
- const lengthBefore = lengths[i];
1081
- const lengthAfter = lengths[i + 1];
1082
- const segmentLength = lengthAfter - lengthBefore;
1083
- const segmentFraction = (targetLength - lengthBefore) / segmentLength;
1692
+ const before = lengths[i];
1693
+ const after = lengths[i + 1];
1694
+ const segmentLength = after - before;
1695
+ const segmentFraction = (targetLen - before) / segmentLength;
1084
1696
  return (i + segmentFraction) / (lengthsLen - 1);
1085
1697
  }
1086
1698
  getTangent(t, output = new Vector2()) {
@@ -1119,31 +1731,55 @@ class Curve {
1119
1731
  return mid;
1120
1732
  }
1121
1733
  matrix(matrix) {
1122
- this.forEachControlPoints((point) => point.applyMatrix3(matrix));
1734
+ this.getControlPointRefs().forEach((point) => point.applyMatrix3(matrix));
1123
1735
  return this;
1124
1736
  }
1125
1737
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1126
- this.getPoints().forEach((point) => {
1127
- min.min(point);
1128
- max.max(point);
1129
- });
1738
+ const potins = this.getPoints();
1739
+ for (let i = 0, len = potins.length; i < len; i++) {
1740
+ const p = potins[i];
1741
+ min.min(p);
1742
+ max.max(p);
1743
+ }
1130
1744
  return { min, max };
1131
1745
  }
1132
1746
  getBoundingBox() {
1133
1747
  const { min, max } = this.getMinMax();
1134
1748
  return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
1135
1749
  }
1750
+ fillTriangulate(options) {
1751
+ return fillTriangulate(
1752
+ this.getPoints().reduce((arr, p) => {
1753
+ arr.push(p.x, p.y);
1754
+ return arr;
1755
+ }, []),
1756
+ options
1757
+ );
1758
+ }
1759
+ strokeTriangulate(options) {
1760
+ return strokeTriangulate(
1761
+ this.getPoints().reduce((arr, p) => {
1762
+ arr.push(p.x, p.y);
1763
+ return arr;
1764
+ }, []),
1765
+ options
1766
+ );
1767
+ }
1136
1768
  toCommands() {
1137
- return this.getPoints().map((point, i) => {
1769
+ const comds = [];
1770
+ const potins = this.getPoints();
1771
+ for (let i = 0, len = potins.length; i < len; i++) {
1772
+ const p = potins[i];
1138
1773
  if (i === 0) {
1139
- return { type: "M", x: point.x, y: point.y };
1774
+ comds.push({ type: "M", x: p.x, y: p.y });
1140
1775
  } else {
1141
- return { type: "L", x: point.x, y: point.y };
1776
+ comds.push({ type: "L", x: p.x, y: p.y });
1142
1777
  }
1143
- });
1778
+ }
1779
+ return comds;
1144
1780
  }
1145
1781
  toData() {
1146
- return pathCommandsToPathData(this.toCommands());
1782
+ return svgPathCommandsToData(this.toCommands());
1147
1783
  }
1148
1784
  drawTo(ctx) {
1149
1785
  this.toCommands().forEach((cmd) => {
@@ -1159,7 +1795,7 @@ class Curve {
1159
1795
  return this;
1160
1796
  }
1161
1797
  copy(source) {
1162
- this.arcLengthDivisions = source.arcLengthDivisions;
1798
+ this.arcLengthDivision = source.arcLengthDivision;
1163
1799
  return this;
1164
1800
  }
1165
1801
  clone() {
@@ -1167,179 +1803,22 @@ class Curve {
1167
1803
  }
1168
1804
  }
1169
1805
 
1170
- class CircleCurve extends Curve {
1171
- constructor(center, radius, start = 0, end = Math.PI * 2) {
1172
- super();
1173
- this.center = center;
1174
- this.radius = radius;
1175
- this.start = start;
1176
- this.end = end;
1177
- }
1178
- getPoint(t) {
1179
- const { radius, center } = this;
1180
- return center.clone().add(this.getNormal(t).clone().scale(radius));
1181
- }
1182
- getTangent(t, output = new Vector2()) {
1183
- const { x, y } = this.getNormal(t);
1184
- return output.set(-y, x);
1185
- }
1186
- getNormal(t, output = new Vector2()) {
1187
- const { start, end } = this;
1188
- const _t = t * (end - start) + start - 0.5 * Math.PI;
1189
- return output.set(Math.cos(_t), Math.sin(_t));
1190
- }
1191
- getControlPoints() {
1192
- return [this.center];
1193
- }
1194
- getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1195
- min.x = Math.min(min.x, this.center.x - this.radius);
1196
- min.y = Math.min(min.y, this.center.y - this.radius);
1197
- max.x = Math.max(max.x, this.center.x + this.radius);
1198
- max.y = Math.max(max.y, this.center.y + this.radius);
1199
- return { min, max };
1200
- }
1201
- }
1202
-
1203
- function catmullRom(t, p0, p1, p2, p3) {
1204
- const v0 = (p2 - p0) * 0.5;
1205
- const v1 = (p3 - p1) * 0.5;
1206
- const t2 = t * t;
1207
- const t3 = t * t2;
1208
- return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
1209
- }
1210
- function quadraticBezierP0(t, p) {
1211
- const k = 1 - t;
1212
- return k * k * p;
1213
- }
1214
- function quadraticBezierP1(t, p) {
1215
- return 2 * (1 - t) * t * p;
1216
- }
1217
- function quadraticBezierP2(t, p) {
1218
- return t * t * p;
1219
- }
1220
- function quadraticBezier(t, p0, p1, p2) {
1221
- return quadraticBezierP0(t, p0) + quadraticBezierP1(t, p1) + quadraticBezierP2(t, p2);
1222
- }
1223
- function cubicBezierP0(t, p) {
1224
- const k = 1 - t;
1225
- return k * k * k * p;
1226
- }
1227
- function cubicBezierP1(t, p) {
1228
- const k = 1 - t;
1229
- return 3 * k * k * t * p;
1230
- }
1231
- function cubicBezierP2(t, p) {
1232
- return 3 * (1 - t) * t * t * p;
1233
- }
1234
- function cubicBezierP3(t, p) {
1235
- return t * t * t * p;
1236
- }
1237
- function cubicBezier(t, p0, p1, p2, p3) {
1238
- return cubicBezierP0(t, p0) + cubicBezierP1(t, p1) + cubicBezierP2(t, p2) + cubicBezierP3(t, p3);
1239
- }
1240
-
1241
- class CubicBezierCurve extends Curve {
1242
- constructor(start = new Vector2(), startControl = new Vector2(), endControl = new Vector2(), end = new Vector2()) {
1243
- super();
1244
- this.start = start;
1245
- this.startControl = startControl;
1246
- this.endControl = endControl;
1247
- this.end = end;
1248
- }
1249
- getPoint(t, output = new Vector2()) {
1250
- const { start, startControl, endControl, end } = this;
1251
- return output.set(
1252
- cubicBezier(t, start.x, startControl.x, endControl.x, end.x),
1253
- cubicBezier(t, start.y, startControl.y, endControl.y, end.y)
1254
- );
1255
- }
1256
- getControlPoints() {
1257
- return [
1258
- this.start,
1259
- this.startControl,
1260
- this.endControl,
1261
- this.end
1262
- ];
1263
- }
1264
- _solveQuadratic(a, b, c) {
1265
- const discriminant = b * b - 4 * a * c;
1266
- if (discriminant < 0)
1267
- return [];
1268
- const sqrtDiscriminant = Math.sqrt(discriminant);
1269
- const t1 = (-b + sqrtDiscriminant) / (2 * a);
1270
- const t2 = (-b - sqrtDiscriminant) / (2 * a);
1271
- return [t1, t2].filter((t) => t >= 0 && t <= 1);
1272
- }
1273
- getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1274
- const p0 = this.start;
1275
- const p1 = this.startControl;
1276
- const p2 = this.endControl;
1277
- const p3 = this.end;
1278
- const dxRoots = this._solveQuadratic(
1279
- 3 * (p1.x - p0.x),
1280
- 6 * (p2.x - p1.x),
1281
- 3 * (p3.x - p2.x)
1282
- );
1283
- const dyRoots = this._solveQuadratic(
1284
- 3 * (p1.y - p0.y),
1285
- 6 * (p2.y - p1.y),
1286
- 3 * (p3.y - p2.y)
1287
- );
1288
- const tValues = [0, 1, ...dxRoots, ...dyRoots];
1289
- const samplePoints = (tValues2, precision) => {
1290
- for (const t of tValues2) {
1291
- for (let i = 0; i <= precision; i++) {
1292
- const delta = i / precision - 0.5;
1293
- const refinedT = Math.min(1, Math.max(0, t + delta));
1294
- const point = this.getPoint(refinedT);
1295
- min.x = Math.min(min.x, point.x);
1296
- min.y = Math.min(min.y, point.y);
1297
- max.x = Math.max(max.x, point.x);
1298
- max.y = Math.max(max.y, point.y);
1299
- }
1300
- }
1301
- };
1302
- samplePoints(tValues, 10);
1303
- return { min, max };
1304
- }
1305
- toCommands() {
1306
- const { start, startControl, endControl, end } = this;
1307
- return [
1308
- { type: "M", x: start.x, y: start.y },
1309
- { type: "C", x1: startControl.x, y1: startControl.y, x2: endControl.x, y2: endControl.y, x: end.x, y: end.y }
1310
- ];
1311
- }
1312
- drawTo(ctx) {
1313
- const { start, startControl, endControl, end } = this;
1314
- ctx.lineTo(start.x, start.y);
1315
- ctx.bezierCurveTo(startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y);
1316
- return this;
1317
- }
1318
- copy(source) {
1319
- super.copy(source);
1320
- this.start.copy(source.start);
1321
- this.startControl.copy(source.startControl);
1322
- this.endControl.copy(source.endControl);
1323
- this.end.copy(source.end);
1324
- return this;
1325
- }
1326
- }
1327
-
1328
1806
  const tempTransform0$1 = new Matrix3();
1329
1807
  const tempTransform1$1 = new Matrix3();
1330
1808
  const tempTransform2$1 = new Matrix3();
1331
1809
  const tempV2 = new Vector2();
1332
1810
  class EllipseCurve extends Curve {
1333
- constructor(center = new Vector2(), radiusX = 1, radiusY = 1, rotation = 0, startAngle = 0, endAngle = Math.PI * 2, clockwise = false) {
1811
+ constructor(cx = 0, cy = 0, rx = 1, ry = 1, rotation = 0, startAngle = 0, endAngle = Math.PI * 2, clockwise = false) {
1334
1812
  super();
1335
- this.center = center;
1336
- this.radiusX = radiusX;
1337
- this.radiusY = radiusY;
1338
1813
  this.rotation = rotation;
1339
1814
  this.startAngle = startAngle;
1340
1815
  this.endAngle = endAngle;
1341
1816
  this.clockwise = clockwise;
1817
+ this.center = new Vector2(cx, cy);
1818
+ this.radius = new Vector2(rx, ry);
1342
1819
  }
1820
+ center;
1821
+ radius;
1343
1822
  isClockwise() {
1344
1823
  return this.clockwise;
1345
1824
  }
@@ -1364,8 +1843,8 @@ class EllipseCurve extends Curve {
1364
1843
  }
1365
1844
  }
1366
1845
  const angle = this.startAngle + t * deltaAngle;
1367
- let _x = this.center.x + this.radiusX * Math.cos(angle);
1368
- let _y = this.center.y + this.radiusY * Math.sin(angle);
1846
+ let _x = this.center.x + this.radius.x * Math.cos(angle);
1847
+ let _y = this.center.y + this.radius.y * Math.sin(angle);
1369
1848
  if (this.rotation !== 0) {
1370
1849
  const cos = Math.cos(this.rotation);
1371
1850
  const sin = Math.sin(this.rotation);
@@ -1377,8 +1856,9 @@ class EllipseCurve extends Curve {
1377
1856
  return output.set(_x, _y);
1378
1857
  }
1379
1858
  toCommands() {
1380
- const { center, radiusX: rx, radiusY: ry, startAngle, endAngle, clockwise, rotation } = this;
1859
+ const { center, radius, startAngle, endAngle, clockwise, rotation } = this;
1381
1860
  const { x: cx, y: cy } = center;
1861
+ const { x: rx, y: ry } = radius;
1382
1862
  const startX = cx + rx * Math.cos(startAngle) * Math.cos(rotation) - ry * Math.sin(startAngle) * Math.sin(rotation);
1383
1863
  const startY = cy + rx * Math.cos(startAngle) * Math.sin(rotation) + ry * Math.sin(startAngle) * Math.cos(rotation);
1384
1864
  const angleDiff = Math.abs(startAngle - endAngle);
@@ -1404,12 +1884,12 @@ class EllipseCurve extends Curve {
1404
1884
  }
1405
1885
  }
1406
1886
  drawTo(ctx) {
1407
- const { center, radiusX, radiusY, rotation, startAngle, endAngle, clockwise } = this;
1887
+ const { center, radius, rotation, startAngle, endAngle, clockwise } = this;
1408
1888
  ctx.ellipse(
1409
1889
  center.x,
1410
1890
  center.y,
1411
- radiusX,
1412
- radiusY,
1891
+ radius.x,
1892
+ radius.y,
1413
1893
  rotation,
1414
1894
  startAngle,
1415
1895
  endAngle,
@@ -1429,11 +1909,119 @@ class EllipseCurve extends Curve {
1429
1909
  }
1430
1910
  return this;
1431
1911
  }
1432
- getControlPoints() {
1912
+ getControlPointRefs() {
1433
1913
  return [this.center];
1434
1914
  }
1915
+ getAdaptivePointArray(output = []) {
1916
+ const i = output.length - 1;
1917
+ const x = this.center.x;
1918
+ const y = this.center.y;
1919
+ const rx = this.radius.x;
1920
+ const ry = this.radius.y;
1921
+ const dx = 0;
1922
+ const dy = 0;
1923
+ if (!(rx >= 0 && ry >= 0 && dx >= 0 && dy >= 0)) {
1924
+ return output;
1925
+ }
1926
+ const n = Math.ceil(2.3 * Math.sqrt(rx + ry));
1927
+ const m = n * 8 + (0) + (0);
1928
+ if (m === 0) {
1929
+ return output;
1930
+ }
1931
+ if (n === 0) {
1932
+ output[i] = output[i + 6] = x + dx;
1933
+ output[i + 1] = output[i + 3] = y + dy;
1934
+ output[i + 2] = output[i + 4] = x - dx;
1935
+ output[i + 5] = output[i + 7] = y - dy;
1936
+ return output;
1937
+ }
1938
+ let j1 = i;
1939
+ let j2 = n * 4 + (0) + 2 + i;
1940
+ let j3 = j2;
1941
+ let j4 = m + i;
1942
+ let x0 = dx + rx;
1943
+ let y0 = dy;
1944
+ let x1 = x + x0;
1945
+ let x2 = x - x0;
1946
+ let y1 = y + y0;
1947
+ output[j1++] = x1;
1948
+ output[j1++] = y1;
1949
+ output[--j2] = y1;
1950
+ output[--j2] = x2;
1951
+ for (let i2 = 1; i2 < n; i2++) {
1952
+ const a = Math.PI / 2 * (i2 / n);
1953
+ const x02 = dx + Math.cos(a) * rx;
1954
+ const y02 = dy + Math.sin(a) * ry;
1955
+ const x12 = x + x02;
1956
+ const x22 = x - x02;
1957
+ const y12 = y + y02;
1958
+ const y22 = y - y02;
1959
+ output[j1++] = x12;
1960
+ output[j1++] = y12;
1961
+ output[--j2] = y12;
1962
+ output[--j2] = x22;
1963
+ output[j3++] = x22;
1964
+ output[j3++] = y22;
1965
+ output[--j4] = y22;
1966
+ output[--j4] = x12;
1967
+ }
1968
+ x0 = dx;
1969
+ y0 = dy + ry;
1970
+ x1 = x + x0;
1971
+ x2 = x - x0;
1972
+ y1 = y + y0;
1973
+ const y2 = y - y0;
1974
+ output[j1++] = x1;
1975
+ output[j1++] = y1;
1976
+ output[--j4] = y2;
1977
+ output[--j4] = x1;
1978
+ return output;
1979
+ }
1980
+ fillTriangulate(options = {}) {
1981
+ let {
1982
+ vertices = [],
1983
+ indices = [],
1984
+ verticesStride = 2,
1985
+ verticesOffset = 0,
1986
+ indicesOffset = 0
1987
+ } = options;
1988
+ const points = this.getAdaptivePointArray();
1989
+ if (points.length === 0) {
1990
+ return { vertices, indices };
1991
+ }
1992
+ let centerX = 0;
1993
+ let centerY = 0;
1994
+ for (let i = 0; i < points.length; i += 2) {
1995
+ centerX += points[i];
1996
+ centerY += points[i + 1];
1997
+ }
1998
+ centerX /= points.length / 2;
1999
+ centerY /= points.length / 2;
2000
+ let count = verticesOffset;
2001
+ vertices[count * verticesStride] = centerX;
2002
+ vertices[count * verticesStride + 1] = centerY;
2003
+ const centerIndex = count++;
2004
+ for (let i = 0; i < points.length; i += 2) {
2005
+ vertices[count * verticesStride] = points[i];
2006
+ vertices[count * verticesStride + 1] = points[i + 1];
2007
+ if (i > 0) {
2008
+ indices[indicesOffset++] = count;
2009
+ indices[indicesOffset++] = centerIndex;
2010
+ indices[indicesOffset++] = count - 1;
2011
+ }
2012
+ count++;
2013
+ }
2014
+ indices[indicesOffset++] = centerIndex + 1;
2015
+ indices[indicesOffset++] = centerIndex;
2016
+ indices[indicesOffset++] = count - 1;
2017
+ return {
2018
+ vertices,
2019
+ indices
2020
+ };
2021
+ }
1435
2022
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1436
- const { center, radiusX: rx, radiusY: ry, rotation: theta } = this;
2023
+ const { center, radius, rotation: theta } = this;
2024
+ const { x: rx, y: ry } = radius;
1437
2025
  const { x: cx, y: cy } = center;
1438
2026
  const cosTheta = Math.cos(theta);
1439
2027
  const sinTheta = Math.sin(theta);
@@ -1453,8 +2041,8 @@ class EllipseCurve extends Curve {
1453
2041
  super.copy(source);
1454
2042
  this.center.x = source.center.x;
1455
2043
  this.center.y = source.center.y;
1456
- this.radiusX = source.radiusX;
1457
- this.radiusY = source.radiusY;
2044
+ this.radius.x = source.radius.x;
2045
+ this.radius.y = source.radius.y;
1458
2046
  this.startAngle = source.startAngle;
1459
2047
  this.endAngle = source.endAngle;
1460
2048
  this.clockwise = source.clockwise;
@@ -1463,8 +2051,8 @@ class EllipseCurve extends Curve {
1463
2051
  }
1464
2052
  }
1465
2053
  function transfEllipseGeneric(curve, m) {
1466
- const a = curve.radiusX;
1467
- const b = curve.radiusY;
2054
+ const a = curve.radius.x;
2055
+ const b = curve.radius.y;
1468
2056
  const cosTheta = Math.cos(curve.rotation);
1469
2057
  const sinTheta = Math.sin(curve.rotation);
1470
2058
  const v1 = new Vector2(a * cosTheta, a * sinTheta);
@@ -1489,8 +2077,8 @@ function transfEllipseGeneric(curve, m) {
1489
2077
  const ed = eigenDecomposition(mQe[0], mQe[1], mQe[4]);
1490
2078
  const rt1sqrt = Math.sqrt(ed.rt1);
1491
2079
  const rt2sqrt = Math.sqrt(ed.rt2);
1492
- curve.radiusX = 1 / rt1sqrt;
1493
- curve.radiusY = 1 / rt2sqrt;
2080
+ curve.radius.x = 1 / rt1sqrt;
2081
+ curve.radius.y = 1 / rt2sqrt;
1494
2082
  curve.rotation = Math.atan2(ed.sn, ed.cs);
1495
2083
  const isFullEllipse = (curve.endAngle - curve.startAngle) % (2 * Math.PI) < Number.EPSILON;
1496
2084
  if (!isFullEllipse) {
@@ -1531,8 +2119,8 @@ function transfEllipseGeneric(curve, m) {
1531
2119
  function transfEllipseNoSkew(curve, m) {
1532
2120
  const sx = getTransformScaleX(m);
1533
2121
  const sy = getTransformScaleY(m);
1534
- curve.radiusX *= sx;
1535
- curve.radiusY *= sy;
2122
+ curve.radius.x *= sx;
2123
+ curve.radius.y *= sy;
1536
2124
  const theta = sx > Number.EPSILON ? Math.atan2(m.elements[1], m.elements[0]) : Math.atan2(-m.elements[3], m.elements[4]);
1537
2125
  curve.rotation += theta;
1538
2126
  if (isTransformFlipped(m)) {
@@ -1594,357 +2182,455 @@ function eigenDecomposition(A, B, C) {
1594
2182
  cs = 1 / Math.sqrt(1 + t * t);
1595
2183
  sn = t * cs;
1596
2184
  }
1597
- if (df > 0) {
1598
- t = cs;
1599
- cs = -sn;
1600
- sn = t;
2185
+ if (df > 0) {
2186
+ t = cs;
2187
+ cs = -sn;
2188
+ sn = t;
2189
+ }
2190
+ return { rt1, rt2, cs, sn };
2191
+ }
2192
+
2193
+ class ArcCurve extends EllipseCurve {
2194
+ constructor(cx = 0, cy = 0, radius = 1, startAngle = 0, endAngle = Math.PI * 2, clockwise = false) {
2195
+ super(cx, cy, radius, radius, 0, startAngle, endAngle, clockwise);
2196
+ }
2197
+ getAdaptivePointArray(output = []) {
2198
+ const start = 0;
2199
+ const end = 1;
2200
+ const x = this.center.x;
2201
+ const y = this.center.y;
2202
+ const clockwise = this.clockwise;
2203
+ const radius = this.radius.x;
2204
+ let dist = Math.abs(start - end);
2205
+ if (clockwise && end > start) {
2206
+ dist = 2 * Math.PI - dist;
2207
+ }
2208
+ let steps = Math.max(6, Math.floor(6 * radius ** (1 / 3) * (dist / Math.PI)));
2209
+ steps = Math.max(steps, 3);
2210
+ let f = dist / steps;
2211
+ let t = start;
2212
+ f *= clockwise ? -1 : 1;
2213
+ for (let i = 0; i < steps + 1; i++) {
2214
+ const cs = Math.cos(t);
2215
+ const sn = Math.sin(t);
2216
+ const nx = x + cs * radius;
2217
+ const ny = y + sn * radius;
2218
+ output.push(nx, ny);
2219
+ t += f;
2220
+ }
2221
+ return output;
2222
+ }
2223
+ }
2224
+
2225
+ class CompositeCurve extends Curve {
2226
+ constructor(curves = []) {
2227
+ super();
2228
+ this.curves = curves;
2229
+ }
2230
+ addCurve(curve) {
2231
+ this.curves.push(curve);
2232
+ return this;
2233
+ }
2234
+ getPoint(t, output = new Vector2()) {
2235
+ const d = t * this.getLength();
2236
+ const lengths = this.getLengths();
2237
+ let i = 0;
2238
+ while (i < lengths.length) {
2239
+ if (lengths[i] >= d) {
2240
+ const diff = lengths[i] - d;
2241
+ const curve = this.curves[i];
2242
+ const length = curve.getLength();
2243
+ return curve.getPointAt(
2244
+ length === 0 ? 0 : 1 - diff / length,
2245
+ output
2246
+ );
2247
+ }
2248
+ i++;
2249
+ }
2250
+ return output;
2251
+ }
2252
+ updateLengths() {
2253
+ const arcLengths = [];
2254
+ for (let i = 0, sum = 0, len = this.curves.length; i < len; i++) {
2255
+ sum += this.curves[i].getLength();
2256
+ arcLengths.push(sum);
2257
+ }
2258
+ this._arcLengths = arcLengths;
2259
+ }
2260
+ getControlPointRefs() {
2261
+ return this.curves.flatMap((curve) => curve.getControlPointRefs());
2262
+ }
2263
+ getAdaptivePointArray(output = []) {
2264
+ let offset;
2265
+ this.curves.forEach((curve) => {
2266
+ curve.getAdaptivePointArray(output);
2267
+ if (offset) {
2268
+ if (output[offset - 1] === output[offset + 1] && output[offset] === output[offset + 2]) {
2269
+ output.splice(offset + 1, 2);
2270
+ }
2271
+ }
2272
+ offset = output.length - 1;
2273
+ });
2274
+ return output;
2275
+ }
2276
+ getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
2277
+ this.curves.forEach((curve) => curve.getMinMax(min, max));
2278
+ return { min, max };
2279
+ }
2280
+ getBoundingBox() {
2281
+ const { min, max } = this.getMinMax();
2282
+ return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
2283
+ }
2284
+ toCommands() {
2285
+ return this.curves.flatMap((curve) => curve.toCommands());
2286
+ }
2287
+ drawTo(ctx) {
2288
+ const point = this.curves[0]?.getPoint(0);
2289
+ if (point) {
2290
+ ctx.moveTo(point.x, point.y);
2291
+ }
2292
+ this.curves.forEach((curve) => curve.drawTo(ctx));
2293
+ return this;
1601
2294
  }
1602
- return { rt1, rt2, cs, sn };
1603
2295
  }
1604
2296
 
1605
- class LineCurve extends Curve {
1606
- constructor(start = new Vector2(), end = new Vector2()) {
2297
+ class CubicBezierCurve extends Curve {
2298
+ p1;
2299
+ cp1;
2300
+ cp2;
2301
+ p2;
2302
+ static from(p1, cp1, cp2, p2) {
2303
+ return new CubicBezierCurve(
2304
+ p1.x,
2305
+ p1.y,
2306
+ cp1.x,
2307
+ cp1.y,
2308
+ cp2.x,
2309
+ cp2.y,
2310
+ p2.x,
2311
+ p2.y
2312
+ );
2313
+ }
2314
+ constructor(p1x, p1y, cp1x, cp1y, cp2x, cp2y, p2x, p2y) {
1607
2315
  super();
1608
- this.start = start;
1609
- this.end = end;
2316
+ this.p1 = new Vector2(p1x, p1y);
2317
+ this.cp1 = new Vector2(cp1x, cp1y);
2318
+ this.cp2 = new Vector2(cp2x, cp2y);
2319
+ this.p2 = new Vector2(p2x, p2y);
1610
2320
  }
1611
2321
  getPoint(t, output = new Vector2()) {
1612
- if (t === 1) {
1613
- output.copy(this.end);
1614
- } else {
1615
- output.copy(this.end).sub(this.start).scale(t).add(this.start);
1616
- }
1617
- return output;
1618
- }
1619
- getPointAt(u, output = new Vector2()) {
1620
- return this.getPoint(u, output);
2322
+ const { p1, cp1, cp2, p2 } = this;
2323
+ return output.set(
2324
+ cubicBezier(t, p1.x, cp1.x, cp2.x, p2.x),
2325
+ cubicBezier(t, p1.y, cp1.y, cp2.y, p2.y)
2326
+ );
1621
2327
  }
1622
- getTangent(_t, output = new Vector2()) {
1623
- return output.subVectors(this.end, this.start).normalize();
2328
+ getAdaptivePointArray(output = []) {
2329
+ return getAdaptiveCubicBezierCurvePoints(
2330
+ this.p1.x,
2331
+ this.p1.y,
2332
+ this.cp1.x,
2333
+ this.cp1.y,
2334
+ this.cp2.x,
2335
+ this.cp2.y,
2336
+ this.p2.x,
2337
+ this.p2.y,
2338
+ 0.5,
2339
+ output
2340
+ );
1624
2341
  }
1625
- getTangentAt(u, output = new Vector2()) {
1626
- return this.getTangent(u, output);
2342
+ getControlPointRefs() {
2343
+ return [this.p1, this.cp1, this.cp2, this.p2];
1627
2344
  }
1628
- getControlPoints() {
1629
- return [
1630
- this.start,
1631
- this.end
1632
- ];
2345
+ _solveQuadratic(a, b, c) {
2346
+ const discriminant = b * b - 4 * a * c;
2347
+ if (discriminant < 0)
2348
+ return [];
2349
+ const sqrtDiscriminant = Math.sqrt(discriminant);
2350
+ const t1 = (-b + sqrtDiscriminant) / (2 * a);
2351
+ const t2 = (-b - sqrtDiscriminant) / (2 * a);
2352
+ return [t1, t2].filter((t) => t >= 0 && t <= 1);
1633
2353
  }
1634
2354
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1635
- const { start, end } = this;
1636
- min.x = Math.min(min.x, start.x, end.x);
1637
- min.y = Math.min(min.y, start.y, end.y);
1638
- max.x = Math.max(max.x, start.x, end.x);
1639
- max.y = Math.max(max.y, start.y, end.y);
2355
+ const { p1, cp1, cp2, p2 } = this;
2356
+ const dxRoots = this._solveQuadratic(
2357
+ 3 * (cp1.x - p1.x),
2358
+ 6 * (cp2.x - cp1.x),
2359
+ 3 * (p2.x - cp2.x)
2360
+ );
2361
+ const dyRoots = this._solveQuadratic(
2362
+ 3 * (cp1.y - p1.y),
2363
+ 6 * (cp2.y - cp1.y),
2364
+ 3 * (p2.y - cp2.y)
2365
+ );
2366
+ const tValues = [0, 1, ...dxRoots, ...dyRoots];
2367
+ const samplePoints = (tValues2, precision) => {
2368
+ for (const t of tValues2) {
2369
+ for (let i = 0; i <= precision; i++) {
2370
+ const delta = i / precision - 0.5;
2371
+ const refinedT = Math.min(1, Math.max(0, t + delta));
2372
+ const point = this.getPoint(refinedT);
2373
+ min.x = Math.min(min.x, point.x);
2374
+ min.y = Math.min(min.y, point.y);
2375
+ max.x = Math.max(max.x, point.x);
2376
+ max.y = Math.max(max.y, point.y);
2377
+ }
2378
+ }
2379
+ };
2380
+ samplePoints(tValues, 10);
1640
2381
  return { min, max };
1641
2382
  }
1642
2383
  toCommands() {
1643
- const { start, end } = this;
2384
+ const { p1, cp1, cp2, p2 } = this;
1644
2385
  return [
1645
- { type: "M", x: start.x, y: start.y },
1646
- { type: "L", x: end.x, y: end.y }
2386
+ { type: "M", x: p1.x, y: p1.y },
2387
+ { type: "C", x1: cp1.x, y1: cp1.y, x2: cp2.x, y2: cp2.y, x: p2.x, y: p2.y }
1647
2388
  ];
1648
2389
  }
1649
2390
  drawTo(ctx) {
1650
- const { start, end } = this;
1651
- ctx.lineTo(start.x, start.y);
1652
- ctx.lineTo(end.x, end.y);
2391
+ const { p1, cp1, cp2, p2 } = this;
2392
+ ctx.lineTo(p1.x, p1.y);
2393
+ ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, p2.x, p2.y);
1653
2394
  return this;
1654
2395
  }
1655
2396
  copy(source) {
1656
2397
  super.copy(source);
1657
- this.start.copy(source.start);
1658
- this.end.copy(source.end);
2398
+ this.p1.copy(source.p1);
2399
+ this.cp1.copy(source.cp1);
2400
+ this.cp2.copy(source.cp2);
2401
+ this.p2.copy(source.p2);
1659
2402
  return this;
1660
2403
  }
1661
2404
  }
1662
2405
 
1663
- class HeartCurve extends Curve {
1664
- constructor(center, size, start = 0, end = 1) {
2406
+ class LineCurve extends Curve {
2407
+ p1;
2408
+ p2;
2409
+ static from(p1, p2) {
2410
+ return new LineCurve(p1.x, p1.y, p2.x, p2.y);
2411
+ }
2412
+ constructor(p1x, p1y, p2x, p2y) {
1665
2413
  super();
1666
- this.center = center;
1667
- this.size = size;
1668
- this.start = start;
1669
- this.end = end;
1670
- this.update();
1671
- }
1672
- curveT = 0;
1673
- update() {
1674
- const { x, y } = this.center;
1675
- const A = new Vector2(x + 0.5 * this.size, y - 0.5 * this.size);
1676
- const t = new Vector2(x - 0.5 * this.size, y - 0.5 * this.size);
1677
- const i = new Vector2(x, y + 0.5 * this.size);
1678
- const curve1 = new CircleCurve(A, Math.SQRT1_2 * this.size, -0.25 * Math.PI, 0.75 * Math.PI);
1679
- const curve5 = new CircleCurve(t, Math.SQRT1_2 * this.size, -0.75 * Math.PI, 0.25 * Math.PI);
1680
- const curve3 = new CircleCurve(i, 0.5 * Math.SQRT1_2 * this.size, 0.75 * Math.PI, 1.25 * Math.PI);
1681
- const e = new Vector2(x, y + this.size);
1682
- const l = new Vector2(x + this.size, y);
1683
- const c = new Vector2().lerpVectors(l, e, 0.75);
1684
- const h = new Vector2(x - this.size, y);
1685
- const a = new Vector2().lerpVectors(h, e, 0.75);
1686
- const curve2 = new LineCurve(l, c);
1687
- const curve4 = new LineCurve(a, h);
1688
- this.curves = [curve1, curve2, curve3, curve4, curve5];
1689
- return this;
1690
- }
1691
- getPoint(t) {
1692
- return this.getCurve(t).getPoint(this.curveT);
1693
- }
1694
- getPointAt(t) {
1695
- return this.getPoint(t);
1696
- }
1697
- getCurve(t) {
1698
- let val = (t * (this.end - this.start) + this.start) % 1;
1699
- val < 0 && (val += 1);
1700
- val *= 9 * Math.PI / 8 + 1.5;
1701
- let index;
1702
- const PI_1_2 = 0.5 * Math.PI;
1703
- if (val < PI_1_2) {
1704
- index = 0;
1705
- this.curveT = val / PI_1_2;
1706
- } else if (val < PI_1_2 + 0.75) {
1707
- index = 1;
1708
- this.curveT = (val - PI_1_2) / 0.75;
1709
- } else if (val < 5 * Math.PI / 8 + 0.75) {
1710
- index = 2;
1711
- this.curveT = (val - PI_1_2 - 0.75) / (Math.PI / 8);
1712
- } else if (val < 5 * Math.PI / 8 + 1.5) {
1713
- index = 3;
1714
- this.curveT = (val - 5 * Math.PI / 8 - 0.75) / 0.75;
2414
+ this.p1 = new Vector2(p1x, p1y);
2415
+ this.p2 = new Vector2(p2x, p2y);
2416
+ }
2417
+ getPoint(t, output = new Vector2()) {
2418
+ if (t === 1) {
2419
+ output.copy(this.p2);
1715
2420
  } else {
1716
- index = 4;
1717
- this.curveT = (val - 5 * Math.PI / 8 - 1.5) / PI_1_2;
2421
+ output.copy(this.p2).sub(this.p1).scale(t).add(this.p1);
1718
2422
  }
1719
- return this.curves[index];
2423
+ return output;
2424
+ }
2425
+ getPointAt(u, output = new Vector2()) {
2426
+ return this.getPoint(u, output);
2427
+ }
2428
+ getTangent(_t, output = new Vector2()) {
2429
+ return output.subVectors(this.p2, this.p1).normalize();
1720
2430
  }
1721
- getTangent(t, output) {
1722
- return this.getCurve(t).getTangent(this.curveT, output);
2431
+ getTangentAt(u, output = new Vector2()) {
2432
+ return this.getTangent(u, output);
1723
2433
  }
1724
- getNormal(t, output) {
1725
- return this.getCurve(t).getNormal(this.curveT, output);
2434
+ getControlPointRefs() {
2435
+ return [this.p1, this.p2];
1726
2436
  }
1727
- getControlPoints() {
1728
- return this.curves.flatMap((curve) => curve.getControlPoints());
2437
+ getAdaptivePointArray(output = []) {
2438
+ output.push(
2439
+ this.p1.x,
2440
+ this.p1.y,
2441
+ this.p2.x,
2442
+ this.p2.y
2443
+ );
2444
+ return output;
1729
2445
  }
1730
2446
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1731
- this.curves.forEach((curve) => curve.getMinMax(min, max));
2447
+ const { p1, p2 } = this;
2448
+ min.x = Math.min(min.x, p1.x, p2.x);
2449
+ min.y = Math.min(min.y, p1.y, p2.y);
2450
+ max.x = Math.max(max.x, p1.x, p2.x);
2451
+ max.y = Math.max(max.y, p1.y, p2.y);
1732
2452
  return { min, max };
1733
2453
  }
1734
2454
  toCommands() {
1735
- return this.curves.flatMap((curve) => curve.toCommands());
2455
+ const { p1, p2 } = this;
2456
+ return [
2457
+ { type: "M", x: p1.x, y: p1.y },
2458
+ { type: "L", x: p2.x, y: p2.y }
2459
+ ];
1736
2460
  }
1737
2461
  drawTo(ctx) {
1738
- this.curves.forEach((curve) => curve.drawTo(ctx));
2462
+ const { p1, p2 } = this;
2463
+ ctx.lineTo(p1.x, p1.y);
2464
+ ctx.lineTo(p2.x, p2.y);
2465
+ return this;
2466
+ }
2467
+ copy(source) {
2468
+ super.copy(source);
2469
+ this.p1.copy(source.p1);
2470
+ this.p2.copy(source.p2);
1739
2471
  return this;
1740
2472
  }
1741
2473
  }
1742
2474
 
1743
- class PloygonCurve extends Curve {
1744
- constructor(center, radius = 0, number = 0, start = 0, end = 1) {
1745
- super();
1746
- this.center = center;
1747
- this.radius = radius;
1748
- this.number = number;
1749
- this.start = start;
1750
- this.end = end;
1751
- this.update();
1752
- }
1753
- curves = [];
1754
- curveT = 0;
1755
- points = [];
1756
- update() {
1757
- for (let i = 0; i < this.number; i++) {
1758
- let radian = i * 2 * Math.PI / this.number;
1759
- radian -= 0.5 * Math.PI;
1760
- this.points.push(
2475
+ class PloygonCurve extends CompositeCurve {
2476
+ static equilateral(cx = 0, cy = 0, radius = 1, sideCount = 3) {
2477
+ const ploygon = new PloygonCurve();
2478
+ const points = [];
2479
+ for (let i = 0; i < sideCount; i++) {
2480
+ const radian = i * 2 * Math.PI / sideCount - 0.5 * Math.PI;
2481
+ points.push(
1761
2482
  new Vector2(
1762
- this.radius * Math.cos(radian),
1763
- this.radius * Math.sin(radian)
1764
- ).add(this.center)
2483
+ radius * Math.cos(radian),
2484
+ radius * Math.sin(radian)
2485
+ ).add({ x: cx, y: cy })
1765
2486
  );
1766
2487
  }
1767
- for (let i = 0; i < this.number; i++) {
1768
- this.curves.push(new LineCurve(this.points[i], this.points[(i + 1) % this.number]));
2488
+ for (let i = 0; i < sideCount; i++) {
2489
+ ploygon.curves.push(
2490
+ LineCurve.from(points[i], points[(i + 1) % sideCount])
2491
+ );
1769
2492
  }
1770
- return this;
1771
- }
1772
- getCurve(t) {
1773
- let pos = (t * (this.end - this.start) + this.start) % 1;
1774
- pos < 0 && (pos += 1);
1775
- const v = pos * this.number;
1776
- const index = Math.floor(v);
1777
- this.curveT = v - index;
1778
- return this.curves[index];
1779
- }
1780
- getPoint(t, output) {
1781
- return this.getCurve(t).getPoint(this.curveT, output);
1782
- }
1783
- getPointAt(u, output) {
1784
- return this.getPoint(u, output);
1785
- }
1786
- getTangent(t, output) {
1787
- return this.getCurve(t).getTangent(this.curveT, output);
1788
- }
1789
- getNormal(t, output) {
1790
- return this.getCurve(t).getNormal(this.curveT, output);
1791
- }
1792
- getControlPoints() {
1793
- return this.curves.flatMap((curve) => curve.getControlPoints());
1794
- }
1795
- getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1796
- this.curves.forEach((curve) => curve.getMinMax(min, max));
1797
- return { min, max };
1798
- }
1799
- toCommands() {
1800
- return this.curves.flatMap((curve) => curve.toCommands());
1801
- }
1802
- drawTo(ctx) {
1803
- this.curves.forEach((curve) => curve.drawTo(ctx));
1804
- return this;
2493
+ return ploygon;
1805
2494
  }
1806
2495
  }
1807
2496
 
1808
2497
  class QuadraticBezierCurve extends Curve {
1809
- constructor(start = new Vector2(), control = new Vector2(), end = new Vector2()) {
2498
+ p1;
2499
+ cp;
2500
+ p2;
2501
+ static from(p1, cp, p2) {
2502
+ return new QuadraticBezierCurve(
2503
+ p1.x,
2504
+ p1.y,
2505
+ cp.x,
2506
+ cp.y,
2507
+ p2.x,
2508
+ p2.y
2509
+ );
2510
+ }
2511
+ constructor(p1x, p1y, cpx, cpy, p2x, p2y) {
1810
2512
  super();
1811
- this.start = start;
1812
- this.control = control;
1813
- this.end = end;
2513
+ this.p1 = new Vector2(p1x, p1y);
2514
+ this.cp = new Vector2(cpx, cpy);
2515
+ this.p2 = new Vector2(p2x, p2y);
1814
2516
  }
1815
2517
  getPoint(t, output = new Vector2()) {
1816
- const { start, control, end } = this;
2518
+ const { p1, cp, p2 } = this;
1817
2519
  output.set(
1818
- quadraticBezier(t, start.x, control.x, end.x),
1819
- quadraticBezier(t, start.y, control.y, end.y)
2520
+ quadraticBezier(t, p1.x, cp.x, p2.x),
2521
+ quadraticBezier(t, p1.y, cp.y, p2.y)
1820
2522
  );
1821
2523
  return output;
1822
2524
  }
1823
- getControlPoints() {
1824
- return [
1825
- this.start,
1826
- this.control,
1827
- this.end
1828
- ];
2525
+ getControlPointRefs() {
2526
+ return [this.p1, this.cp, this.p2];
2527
+ }
2528
+ getAdaptivePointArray(output = []) {
2529
+ return getAdaptiveQuadraticBezierCurvePoints(
2530
+ this.p1.x,
2531
+ this.p1.y,
2532
+ this.cp.x,
2533
+ this.cp.y,
2534
+ this.p2.x,
2535
+ this.p2.y,
2536
+ 0.5,
2537
+ output
2538
+ );
1829
2539
  }
1830
2540
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1831
- const { start, control, end } = this;
1832
- const x1 = 0.5 * (start.x + control.x);
1833
- const y1 = 0.5 * (start.y + control.y);
1834
- const x2 = 0.5 * (start.x + end.x);
1835
- const y2 = 0.5 * (start.y + end.y);
1836
- min.x = Math.min(min.x, start.x, end.x, x1, x2);
1837
- min.y = Math.min(min.y, start.y, end.y, y1, y2);
1838
- max.x = Math.max(max.x, start.x, end.x, x1, x2);
1839
- max.y = Math.max(max.y, start.y, end.y, y1, y2);
2541
+ const { p1, cp, p2 } = this;
2542
+ const x1 = 0.5 * (p1.x + cp.x);
2543
+ const y1 = 0.5 * (p1.y + cp.y);
2544
+ const x2 = 0.5 * (p1.x + p2.x);
2545
+ const y2 = 0.5 * (p1.y + p2.y);
2546
+ min.x = Math.min(min.x, p1.x, p2.x, x1, x2);
2547
+ min.y = Math.min(min.y, p1.y, p2.y, y1, y2);
2548
+ max.x = Math.max(max.x, p1.x, p2.x, x1, x2);
2549
+ max.y = Math.max(max.y, p1.y, p2.y, y1, y2);
1840
2550
  return { min, max };
1841
2551
  }
1842
2552
  toCommands() {
1843
- const { start, control, end } = this;
2553
+ const { p1, cp, p2 } = this;
1844
2554
  return [
1845
- { type: "M", x: start.x, y: start.y },
1846
- { type: "Q", x1: control.x, y1: control.y, x: end.x, y: end.y }
2555
+ { type: "M", x: p1.x, y: p1.y },
2556
+ { type: "Q", x1: cp.x, y1: cp.y, x: p2.x, y: p2.y }
1847
2557
  ];
1848
2558
  }
1849
2559
  drawTo(ctx) {
1850
- const { start, control, end } = this;
1851
- ctx.lineTo(start.x, start.y);
1852
- ctx.quadraticCurveTo(control.x, control.y, end.x, end.y);
2560
+ const { p1, cp, p2 } = this;
2561
+ ctx.lineTo(p1.x, p1.y);
2562
+ ctx.quadraticCurveTo(cp.x, cp.y, p2.x, p2.y);
1853
2563
  return this;
1854
2564
  }
1855
2565
  copy(source) {
1856
2566
  super.copy(source);
1857
- this.start.copy(source.start);
1858
- this.control.copy(source.control);
1859
- this.end.copy(source.end);
2567
+ this.p1.copy(source.p1);
2568
+ this.cp.copy(source.cp);
2569
+ this.p2.copy(source.p2);
1860
2570
  return this;
1861
2571
  }
1862
2572
  }
1863
2573
 
1864
- class RectangularCurve extends Curve {
1865
- constructor(center, rx, aspectRatio = 1, start = 0, end = 1) {
1866
- super();
1867
- this.center = center;
1868
- this.rx = rx;
1869
- this.aspectRatio = aspectRatio;
1870
- this.start = start;
1871
- this.end = end;
1872
- this.update();
1873
- }
1874
- curves = [];
1875
- curveT = 0;
1876
- get x() {
1877
- return this.center.x - this.rx;
1878
- }
1879
- get y() {
1880
- return this.center.y - this.rx / this.aspectRatio;
1881
- }
1882
- get width() {
1883
- return this.rx * 2;
1884
- }
1885
- get height() {
1886
- return this.rx / this.aspectRatio * 2;
1887
- }
1888
- update() {
1889
- const { x, y } = this.center;
1890
- const offsetX = this.rx;
1891
- const offsetY = this.rx / this.aspectRatio;
2574
+ class RectangleCurve extends PloygonCurve {
2575
+ constructor(x = 0, y = 0, width = 0, height = 0) {
1892
2576
  const points = [
1893
- new Vector2(x - offsetX, y - offsetY),
1894
- new Vector2(x + offsetX, y - offsetY),
1895
- new Vector2(x + offsetX, y + offsetY),
1896
- new Vector2(x - offsetX, y + offsetY)
2577
+ new Vector2(x, y),
2578
+ new Vector2(x + width, y),
2579
+ new Vector2(x + width, y + height),
2580
+ new Vector2(x, y + height)
1897
2581
  ];
1898
- for (let i = 0; i < 4; i++) {
1899
- this.curves.push(new LineCurve(points[i].clone(), points[(i + 1) % 4].clone()));
1900
- }
1901
- return this;
1902
- }
1903
- getCurve(t) {
1904
- let current = (t * (this.end - this.start) + this.start) % 1;
1905
- current < 0 && (current += 1);
1906
- current *= (1 + this.aspectRatio) * 2;
1907
- let i;
1908
- if (current < this.aspectRatio) {
1909
- i = 0;
1910
- this.curveT = current / this.aspectRatio;
1911
- } else if (current < this.aspectRatio + 1) {
1912
- i = 1;
1913
- this.curveT = (current - this.aspectRatio) / 1;
1914
- } else if (current < 2 * this.aspectRatio + 1) {
1915
- i = 2;
1916
- this.curveT = (current - this.aspectRatio - 1) / this.aspectRatio;
1917
- } else {
1918
- i = 3;
1919
- this.curveT = (current - 2 * this.aspectRatio - 1) / 1;
1920
- }
1921
- return this.curves[i];
1922
- }
1923
- getPoint(t, output) {
1924
- return this.getCurve(t).getPoint(this.curveT, output);
1925
- }
1926
- getPointAt(u, output) {
1927
- return this.getPoint(u, output);
1928
- }
1929
- getTangent(t, output) {
1930
- return this.getCurve(t).getTangent(this.curveT, output);
1931
- }
1932
- getNormal(t, output) {
1933
- return this.getCurve(t).getNormal(this.curveT, output);
1934
- }
1935
- getControlPoints() {
1936
- return this.curves.flatMap((curve) => curve.getControlPoints());
1937
- }
1938
- getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1939
- this.curves.forEach((curve) => curve.getMinMax(min, max));
1940
- return { min, max };
1941
- }
1942
- toCommands() {
1943
- return this.curves.flatMap((curve) => curve.toCommands());
2582
+ super([
2583
+ LineCurve.from(points[0], points[1]),
2584
+ LineCurve.from(points[1], points[2]),
2585
+ LineCurve.from(points[2], points[3]),
2586
+ LineCurve.from(points[3], points[0])
2587
+ ]);
2588
+ this.x = x;
2589
+ this.y = y;
2590
+ this.width = width;
2591
+ this.height = height;
1944
2592
  }
1945
- drawTo(ctx) {
1946
- this.curves.forEach((curve) => curve.drawTo(ctx));
1947
- return this;
2593
+ fillTriangulate(options = {}) {
2594
+ let {
2595
+ vertices = [],
2596
+ indices = [],
2597
+ verticesStride = 2,
2598
+ verticesOffset = 0,
2599
+ indicesOffset = 0
2600
+ } = options;
2601
+ const { x, y, width, height } = this;
2602
+ const points = [
2603
+ x,
2604
+ y,
2605
+ x + width,
2606
+ y,
2607
+ x + width,
2608
+ y + height,
2609
+ x,
2610
+ y + height
2611
+ ];
2612
+ let count = 0;
2613
+ verticesOffset *= verticesStride;
2614
+ vertices[verticesOffset + count] = points[0];
2615
+ vertices[verticesOffset + count + 1] = points[1];
2616
+ count += verticesStride;
2617
+ vertices[verticesOffset + count] = points[2];
2618
+ vertices[verticesOffset + count + 1] = points[3];
2619
+ count += verticesStride;
2620
+ vertices[verticesOffset + count] = points[6];
2621
+ vertices[verticesOffset + count + 1] = points[7];
2622
+ count += verticesStride;
2623
+ vertices[verticesOffset + count] = points[4];
2624
+ vertices[verticesOffset + count + 1] = points[5];
2625
+ count += verticesStride;
2626
+ const verticesIndex = verticesOffset / verticesStride;
2627
+ indices[indicesOffset++] = verticesIndex;
2628
+ indices[indicesOffset++] = verticesIndex + 1;
2629
+ indices[indicesOffset++] = verticesIndex + 2;
2630
+ indices[indicesOffset++] = verticesIndex + 1;
2631
+ indices[indicesOffset++] = verticesIndex + 3;
2632
+ indices[indicesOffset++] = verticesIndex + 2;
2633
+ return { vertices, indices };
1948
2634
  }
1949
2635
  }
1950
2636
 
@@ -1968,7 +2654,7 @@ class SplineCurve extends Curve {
1968
2654
  );
1969
2655
  return output;
1970
2656
  }
1971
- getControlPoints() {
2657
+ getControlPointRefs() {
1972
2658
  return this.points;
1973
2659
  }
1974
2660
  copy(source) {
@@ -1981,22 +2667,16 @@ class SplineCurve extends Curve {
1981
2667
  }
1982
2668
  }
1983
2669
 
1984
- class CurvePath extends Curve {
1985
- curves = [];
2670
+ class CurvePath extends CompositeCurve {
1986
2671
  startPoint;
1987
2672
  currentPoint;
1988
2673
  autoClose = false;
1989
- _cacheLengths = [];
1990
2674
  constructor(points) {
1991
2675
  super();
1992
2676
  if (points) {
1993
2677
  this.addPoints(points);
1994
2678
  }
1995
2679
  }
1996
- addCurve(curve) {
1997
- this.curves.push(curve);
1998
- return this;
1999
- }
2000
2680
  addPoints(points) {
2001
2681
  this.moveTo(points[0].x, points[0].y);
2002
2682
  for (let i = 1, len = points.length; i < len; i++) {
@@ -2006,82 +2686,26 @@ class CurvePath extends Curve {
2006
2686
  return this;
2007
2687
  }
2008
2688
  addCommands(commands) {
2009
- addPathCommandsToPath2D(commands, this);
2689
+ svgPathCommandsAddToPath2D(commands, this);
2010
2690
  return this;
2011
2691
  }
2012
2692
  addData(data) {
2013
- this.addCommands(pathDataToPathCommands(data));
2693
+ this.addCommands(svgPathDataToCommands(data));
2014
2694
  return this;
2015
2695
  }
2016
- getPoint(t, output = new Vector2()) {
2017
- const d = t * this.getLength();
2018
- const curveLengths = this.getCurveLengths();
2019
- let i = 0;
2020
- while (i < curveLengths.length) {
2021
- if (curveLengths[i] >= d) {
2022
- const diff = curveLengths[i] - d;
2023
- const curve = this.curves[i];
2024
- const segmentLength = curve.getLength();
2025
- return curve.getPointAt(segmentLength === 0 ? 0 : 1 - diff / segmentLength, output);
2026
- }
2027
- i++;
2696
+ getUnevenPointArray(count = 40, output = []) {
2697
+ super.getUnevenPointArray(count, output);
2698
+ if (this.autoClose && output.length >= 4 && (output[0] !== output[output.length - 2] && output[1] !== output[output.length - 1])) {
2699
+ output.push(output[0], output[1]);
2028
2700
  }
2029
2701
  return output;
2030
2702
  }
2031
- getControlPoints() {
2032
- return this.curves.flatMap((curve) => curve.getControlPoints());
2033
- }
2034
- getLength() {
2035
- const lengths = this.getCurveLengths();
2036
- return lengths[lengths.length - 1];
2037
- }
2038
- updateArcLengths() {
2039
- super.updateArcLengths();
2040
- this._cacheLengths = [];
2041
- this.getCurveLengths();
2042
- }
2043
- getCurveLengths() {
2044
- if (this._cacheLengths.length === this.curves.length) {
2045
- return this._cacheLengths;
2046
- }
2047
- const lengths = [];
2048
- let sums = 0;
2049
- for (let i = 0, l = this.curves.length; i < l; i++) {
2050
- sums += this.curves[i].getLength();
2051
- lengths.push(sums);
2052
- }
2053
- this._cacheLengths = lengths;
2054
- return lengths;
2055
- }
2056
- getSpacedPoints(divisions = 40) {
2057
- const points = [];
2058
- for (let i = 0; i <= divisions; i++) {
2059
- points.push(this.getPoint(i / divisions));
2060
- }
2061
- if (this.autoClose) {
2062
- points.push(points[0]);
2063
- }
2064
- return points;
2065
- }
2066
- getPoints(divisions = 12) {
2067
- const points = [];
2068
- const curves = this.curves;
2069
- let last;
2070
- for (let i = 0, len = curves.length; i < len; i++) {
2071
- const curve = curves[i];
2072
- const curvePoints = curve.getPoints(divisions);
2073
- for (let i2 = 0; i2 < curvePoints.length; i2++) {
2074
- const point = curvePoints[i2];
2075
- if (last?.equals(point))
2076
- continue;
2077
- points.push(point);
2078
- last = point;
2079
- }
2703
+ getSpacedPointArray(count = 40, output = []) {
2704
+ super.getSpacedPointArray(count, output);
2705
+ if (this.autoClose && output.length >= 4 && (output[0] !== output[output.length - 2] && output[1] !== output[output.length - 1])) {
2706
+ output.push(output[0], output[1]);
2080
2707
  }
2081
- if (this.autoClose && points.length > 1 && !points[points.length - 1].equals(points[0])) {
2082
- points.push(points[0]);
2083
- }
2084
- return points;
2708
+ return output;
2085
2709
  }
2086
2710
  _setCurrentPoint(point) {
2087
2711
  this.currentPoint = new Vector2(point.x, point.y);
@@ -2090,12 +2714,21 @@ class CurvePath extends Curve {
2090
2714
  }
2091
2715
  return this;
2092
2716
  }
2717
+ _connetLineTo(curve) {
2718
+ if (this.curves.length > 0) {
2719
+ const first = curve.getPoint(0);
2720
+ if (!this.currentPoint || !first.equals(this.currentPoint)) {
2721
+ this.lineTo(first.x, first.y);
2722
+ }
2723
+ }
2724
+ return this;
2725
+ }
2093
2726
  closePath() {
2094
2727
  const start = this.startPoint;
2095
2728
  if (start) {
2096
2729
  const end = this.currentPoint;
2097
2730
  if (end && !start.equals(end)) {
2098
- this.curves.push(new LineCurve(end.clone(), start));
2731
+ this.curves.push(LineCurve.from(end, start));
2099
2732
  end.copy(start);
2100
2733
  }
2101
2734
  this.startPoint = void 0;
@@ -2112,8 +2745,10 @@ class CurvePath extends Curve {
2112
2745
  if (!start?.equals({ x, y })) {
2113
2746
  this.curves.push(
2114
2747
  new LineCurve(
2115
- start?.clone() ?? new Vector2(),
2116
- new Vector2(x, y)
2748
+ start?.x ?? 0,
2749
+ start?.y ?? 0,
2750
+ x,
2751
+ y
2117
2752
  )
2118
2753
  );
2119
2754
  }
@@ -2125,10 +2760,14 @@ class CurvePath extends Curve {
2125
2760
  if (!start?.equals({ x, y })) {
2126
2761
  this.curves.push(
2127
2762
  new CubicBezierCurve(
2128
- start?.clone() ?? new Vector2(),
2129
- new Vector2(cp1x, cp1y),
2130
- new Vector2(cp2x, cp2y),
2131
- new Vector2(x, y)
2763
+ start?.x ?? 0,
2764
+ start?.y ?? 0,
2765
+ cp1x,
2766
+ cp1y,
2767
+ cp2x,
2768
+ cp2y,
2769
+ x,
2770
+ y
2132
2771
  )
2133
2772
  );
2134
2773
  }
@@ -2140,9 +2779,12 @@ class CurvePath extends Curve {
2140
2779
  if (!start?.equals({ x, y })) {
2141
2780
  this.curves.push(
2142
2781
  new QuadraticBezierCurve(
2143
- start?.clone() ?? new Vector2(),
2144
- new Vector2(cpx, cpy),
2145
- new Vector2(x, y)
2782
+ start?.x ?? 0,
2783
+ start?.y ?? 0,
2784
+ cpx,
2785
+ cpy,
2786
+ x,
2787
+ y
2146
2788
  )
2147
2789
  );
2148
2790
  }
@@ -2150,12 +2792,23 @@ class CurvePath extends Curve {
2150
2792
  return this;
2151
2793
  }
2152
2794
  arc(x, y, radius, startAngle, endAngle, counterclockwise) {
2153
- this.ellipse(x, y, radius, radius, 0, startAngle, endAngle, counterclockwise);
2795
+ const curve = new ArcCurve(
2796
+ x,
2797
+ y,
2798
+ radius,
2799
+ startAngle,
2800
+ endAngle,
2801
+ !counterclockwise
2802
+ );
2803
+ this._connetLineTo(curve);
2804
+ this.curves.push(curve);
2805
+ this._setCurrentPoint(curve.getPoint(1));
2154
2806
  return this;
2155
2807
  }
2156
2808
  relativeArc(x, y, radius, startAngle, endAngle, counterclockwise) {
2157
- const point = this.currentPoint ?? new Vector2();
2158
- this.arc(x + point.x, y + point.y, radius, startAngle, endAngle, counterclockwise);
2809
+ x += this.currentPoint?.x ?? 0;
2810
+ y += this.currentPoint?.y ?? 0;
2811
+ this.arc(x, y, radius, startAngle, endAngle, counterclockwise);
2159
2812
  return this;
2160
2813
  }
2161
2814
  // TODO
@@ -2166,7 +2819,8 @@ class CurvePath extends Curve {
2166
2819
  }
2167
2820
  ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise = true) {
2168
2821
  const curve = new EllipseCurve(
2169
- new Vector2(x, y),
2822
+ x,
2823
+ y,
2170
2824
  radiusX,
2171
2825
  radiusY,
2172
2826
  rotation,
@@ -2174,29 +2828,21 @@ class CurvePath extends Curve {
2174
2828
  endAngle,
2175
2829
  !counterclockwise
2176
2830
  );
2177
- if (this.curves.length > 0) {
2178
- const first = curve.getPoint(0);
2179
- if (!this.currentPoint || !first.equals(this.currentPoint)) {
2180
- this.lineTo(first.x, first.y);
2181
- }
2182
- }
2831
+ this._connetLineTo(curve);
2183
2832
  this.curves.push(curve);
2184
2833
  this._setCurrentPoint(curve.getPoint(1));
2185
2834
  return this;
2186
2835
  }
2187
2836
  relativeEllipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
2188
- const point = this.currentPoint ?? new Vector2();
2189
- this.ellipse(x + point.x, y + point.y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
2837
+ x += this.currentPoint?.x ?? 0;
2838
+ y += this.currentPoint?.y ?? 0;
2839
+ this.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
2190
2840
  return this;
2191
2841
  }
2192
2842
  rect(x, y, w, h) {
2193
- this.curves.push(
2194
- new RectangularCurve(
2195
- new Vector2(x + w / 2, y + h / 2),
2196
- w / 2,
2197
- w / h
2198
- )
2199
- );
2843
+ const curve = new RectangleCurve(x, y, w, h);
2844
+ this._connetLineTo(curve);
2845
+ this.curves.push(curve);
2200
2846
  this._setCurrentPoint({ x, y });
2201
2847
  return this;
2202
2848
  }
@@ -2206,17 +2852,6 @@ class CurvePath extends Curve {
2206
2852
  this._setCurrentPoint(points[points.length - 1]);
2207
2853
  return this;
2208
2854
  }
2209
- getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
2210
- this.curves.forEach((curve) => curve.getMinMax(min, max));
2211
- return { min, max };
2212
- }
2213
- getBoundingBox() {
2214
- const { min, max } = this.getMinMax();
2215
- return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
2216
- }
2217
- toCommands() {
2218
- return this.curves.flatMap((curve) => curve.toCommands());
2219
- }
2220
2855
  drawTo(ctx) {
2221
2856
  const point = this.curves[0]?.getPoint(0);
2222
2857
  if (point) {
@@ -2267,20 +2902,22 @@ function getIntersectionPoint(p1, p2, q1, q2) {
2267
2902
  );
2268
2903
  }
2269
2904
 
2270
- class Path2D {
2271
- currentPath = new CurvePath();
2272
- paths = [this.currentPath];
2905
+ class Path2D extends CompositeCurve {
2906
+ currentCurve = new CurvePath();
2273
2907
  style;
2274
2908
  get startPoint() {
2275
- return this.currentPath.startPoint;
2909
+ return this.currentCurve.startPoint;
2276
2910
  }
2277
2911
  get currentPoint() {
2278
- return this.currentPath.currentPoint;
2912
+ return this.currentCurve.currentPoint;
2279
2913
  }
2280
2914
  get strokeWidth() {
2281
2915
  return this.style.strokeWidth ?? ((this.style.stroke ?? "none") === "none" ? 0 : 1);
2282
2916
  }
2283
2917
  constructor(path, style = {}) {
2918
+ super();
2919
+ this.curves.push(this.currentCurve);
2920
+ this.style = style;
2284
2921
  if (path) {
2285
2922
  if (path instanceof Path2D) {
2286
2923
  this.addPath(path);
@@ -2290,99 +2927,90 @@ class Path2D {
2290
2927
  this.addData(path);
2291
2928
  }
2292
2929
  }
2293
- this.style = style;
2294
2930
  }
2295
2931
  addPath(path) {
2296
2932
  if (path instanceof Path2D) {
2297
- this.paths.push(...path.paths.map((v) => v.clone()));
2933
+ this.curves.push(...path.curves.map((v) => v.clone()));
2298
2934
  } else {
2299
- this.paths.push(path);
2935
+ this.curves.push(path);
2300
2936
  }
2301
2937
  return this;
2302
2938
  }
2303
2939
  closePath() {
2304
2940
  const startPoint = this.startPoint;
2305
2941
  if (startPoint) {
2306
- this.currentPath.closePath();
2307
- if (this.currentPath.curves.length > 0) {
2308
- this.currentPath = new CurvePath().moveTo(startPoint.x, startPoint.y);
2309
- this.paths.push(this.currentPath);
2942
+ this.currentCurve.closePath();
2943
+ if (this.currentCurve.curves.length > 0) {
2944
+ this.currentCurve = new CurvePath().moveTo(startPoint.x, startPoint.y);
2945
+ this.curves.push(this.currentCurve);
2310
2946
  }
2311
2947
  }
2312
2948
  return this;
2313
2949
  }
2314
2950
  moveTo(x, y) {
2315
- const { currentPoint, curves } = this.currentPath;
2316
- if (!currentPoint?.equals({ x, y })) {
2317
- if (curves.length) {
2318
- this.currentPath = new CurvePath().moveTo(x, y);
2319
- this.paths.push(this.currentPath);
2320
- } else {
2321
- this.currentPath.moveTo(x, y);
2951
+ if (!this.currentCurve.currentPoint?.equals({ x, y })) {
2952
+ if (this.currentCurve.curves.length) {
2953
+ this.currentCurve = new CurvePath();
2954
+ this.curves.push(this.currentCurve);
2322
2955
  }
2956
+ this.currentCurve.moveTo(x, y);
2323
2957
  }
2324
2958
  return this;
2325
2959
  }
2326
2960
  lineTo(x, y) {
2327
- this.currentPath.lineTo(x, y);
2961
+ this.currentCurve.lineTo(x, y);
2328
2962
  return this;
2329
2963
  }
2330
2964
  bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
2331
- this.currentPath.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
2965
+ this.currentCurve.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
2332
2966
  return this;
2333
2967
  }
2334
2968
  quadraticCurveTo(cpx, cpy, x, y) {
2335
- this.currentPath.quadraticCurveTo(cpx, cpy, x, y);
2969
+ this.currentCurve.quadraticCurveTo(cpx, cpy, x, y);
2336
2970
  return this;
2337
2971
  }
2338
2972
  arc(x, y, radius, startAngle, endAngle, counterclockwise) {
2339
- this.currentPath.arc(x, y, radius, startAngle, endAngle, counterclockwise);
2973
+ this.currentCurve.arc(x, y, radius, startAngle, endAngle, counterclockwise);
2340
2974
  return this;
2341
2975
  }
2342
2976
  arcTo(x1, y1, x2, y2, radius) {
2343
- this.currentPath.arcTo(x1, y1, x2, y2, radius);
2977
+ this.currentCurve.arcTo(x1, y1, x2, y2, radius);
2344
2978
  return this;
2345
2979
  }
2346
2980
  ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
2347
- this.currentPath.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
2981
+ this.currentCurve.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
2348
2982
  return this;
2349
2983
  }
2350
2984
  rect(x, y, w, h) {
2351
- this.currentPath.rect(x, y, w, h);
2985
+ this.currentCurve.rect(x, y, w, h);
2352
2986
  return this;
2353
2987
  }
2354
2988
  addCommands(commands) {
2355
- addPathCommandsToPath2D(commands, this);
2989
+ svgPathCommandsAddToPath2D(commands, this);
2356
2990
  return this;
2357
2991
  }
2358
2992
  addData(data) {
2359
- this.addCommands(pathDataToPathCommands(data));
2993
+ this.addCommands(svgPathDataToCommands(data));
2360
2994
  return this;
2361
2995
  }
2362
2996
  splineThru(points) {
2363
- this.currentPath.splineThru(points);
2997
+ this.currentCurve.splineThru(points);
2364
2998
  return this;
2365
2999
  }
2366
- getControlPoints() {
2367
- return this.paths.flatMap((path) => path.getControlPoints());
2368
- }
2369
- getCurves() {
2370
- return this.paths.flatMap((path) => path.curves);
2371
- }
2372
3000
  scale(sx, sy = sx, target = { x: 0, y: 0 }) {
2373
- this.getControlPoints().forEach((point) => {
3001
+ this.getControlPointRefs().forEach((point) => {
2374
3002
  point.scale(sx, sy, target);
2375
3003
  });
2376
3004
  return this;
2377
3005
  }
2378
3006
  skew(ax, ay = 0, target = { x: 0, y: 0 }) {
2379
- this.getControlPoints().forEach((point) => {
3007
+ this.getControlPointRefs().forEach((point) => {
2380
3008
  point.skew(ax, ay, target);
2381
3009
  });
2382
3010
  return this;
2383
3011
  }
2384
3012
  rotate(a, target = { x: 0, y: 0 }) {
2385
- this.getControlPoints().forEach((point) => {
3013
+ this.getControlPointRefs().forEach((point) => {
2386
3014
  point.rotate(a, target);
2387
3015
  });
2388
3016
  return this;
@@ -2391,12 +3019,14 @@ class Path2D {
2391
3019
  if (b === 0) {
2392
3020
  return this;
2393
3021
  }
2394
- const curves = this.getCurves();
3022
+ const curves = this.curves;
2395
3023
  const _list = [];
2396
3024
  const _isClockwise = [];
2397
3025
  const _points = [];
2398
3026
  curves.forEach((curve, index) => {
2399
- const points = curve.getControlPoints();
3027
+ if (!curve.getLength())
3028
+ return;
3029
+ const points = curve.getControlPointRefs();
2400
3030
  const isClockwise = curve.isClockwise();
2401
3031
  _points[index] = points;
2402
3032
  _isClockwise[index] = isClockwise;
@@ -2418,6 +3048,8 @@ class Path2D {
2418
3048
  });
2419
3049
  });
2420
3050
  curves.forEach((curve, index) => {
3051
+ if (!curve.getLength())
3052
+ return;
2421
3053
  const isClockwise = _isClockwise[index];
2422
3054
  const points = _points[index];
2423
3055
  points.forEach((point) => {
@@ -2445,19 +3077,19 @@ class Path2D {
2445
3077
  return this;
2446
3078
  }
2447
3079
  matrix(matrix) {
2448
- this.getCurves().forEach((curve) => curve.matrix(matrix));
3080
+ this.curves.forEach((curve) => curve.matrix(matrix));
2449
3081
  return this;
2450
3082
  }
2451
3083
  getMinMax(min = Vector2.MAX, max = Vector2.MIN, withStyle = true) {
2452
3084
  const strokeWidth = this.strokeWidth;
2453
- this.getCurves().forEach((curve) => {
3085
+ this.curves.forEach((curve) => {
2454
3086
  curve.getMinMax(min, max);
2455
3087
  if (withStyle) {
2456
3088
  if (strokeWidth > 1) {
2457
3089
  const halfStrokeWidth = strokeWidth / 2;
2458
3090
  const isClockwise = curve.isClockwise();
2459
3091
  const points = [];
2460
- for (let t = 0; t <= 1; t += 1 / curve.arcLengthDivisions) {
3092
+ for (let t = 0; t <= 1; t += 1 / curve.arcLengthDivision) {
2461
3093
  const point = curve.getPoint(t);
2462
3094
  const normal = curve.getNormal(t);
2463
3095
  const dist1 = normal.clone().scale(isClockwise ? halfStrokeWidth : -halfStrokeWidth);
@@ -2490,7 +3122,7 @@ class Path2D {
2490
3122
  ctx.beginPath();
2491
3123
  ctx.save();
2492
3124
  setCanvasContext(ctx, style);
2493
- this.paths.forEach((path) => {
3125
+ this.curves.forEach((path) => {
2494
3126
  path.drawTo(ctx);
2495
3127
  });
2496
3128
  if (fill !== "none") {
@@ -2508,9 +3140,8 @@ class Path2D {
2508
3140
  ctx.beginPath();
2509
3141
  ctx.save();
2510
3142
  setCanvasContext(ctx, style);
2511
- this.getControlPoints().forEach((point) => {
2512
- ctx.moveTo(point.x, point.y);
2513
- ctx.arc(point.x, point.y, 4, 0, Math.PI * 2);
3143
+ this.getControlPointRefs().forEach((point) => {
3144
+ drawPoint(ctx, point.x, point.y, { radius: 4 });
2514
3145
  });
2515
3146
  if (fill !== "none") {
2516
3147
  ctx.fill();
@@ -2522,10 +3153,10 @@ class Path2D {
2522
3153
  return this;
2523
3154
  }
2524
3155
  toCommands() {
2525
- return this.paths.flatMap((path) => path.toCommands());
3156
+ return this.curves.flatMap((v) => v.toCommands());
2526
3157
  }
2527
3158
  toData() {
2528
- return this.paths.map((path) => path.toData()).join(" ");
3159
+ return this.curves.filter((v) => v.curves.length).map((v) => v.toData()).join(" ");
2529
3160
  }
2530
3161
  toSVGPathString() {
2531
3162
  const style = {
@@ -2550,10 +3181,34 @@ class Path2D {
2550
3181
  }
2551
3182
  return `<path d="${this.toData()}" style="${cssText}"></path>`;
2552
3183
  }
3184
+ copy(source) {
3185
+ this.currentCurve = source.currentCurve.clone();
3186
+ this.curves = source.curves.map((path) => path.clone());
3187
+ this.style = { ...source.style };
3188
+ return this;
3189
+ }
3190
+ clone() {
3191
+ return new this.constructor().copy(this);
3192
+ }
3193
+ }
3194
+
3195
+ class Path2DSet {
3196
+ constructor(paths = []) {
3197
+ this.paths = paths;
3198
+ }
3199
+ getBoundingBox(withStyle = true) {
3200
+ if (!this.paths.length) {
3201
+ return void 0;
3202
+ }
3203
+ const min = Vector2.MAX;
3204
+ const max = Vector2.MIN;
3205
+ this.paths.forEach((path) => path.getMinMax(min, max, withStyle));
3206
+ return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
3207
+ }
2553
3208
  toSVGString() {
2554
3209
  const { x, y, width, height } = this.getBoundingBox();
2555
- const path = this.toSVGPathString();
2556
- return `<svg viewBox="${x} ${y} ${width} ${height}" width="${width}px" height="${height}px" xmlns="http://www.w3.org/2000/svg">${path}</svg>`;
3210
+ const content = this.paths.map((path) => path.toSVGPathString()).join("");
3211
+ return `<svg viewBox="${x} ${y} ${width} ${height}" width="${width}px" height="${height}px" xmlns="http://www.w3.org/2000/svg">${content}</svg>`;
2557
3212
  }
2558
3213
  toSVGUrl() {
2559
3214
  return `data:image/svg+xml;base64,${btoa(this.toSVGString())}`;
@@ -2573,19 +3228,12 @@ class Path2D {
2573
3228
  if (ctx) {
2574
3229
  ctx.scale(pixelRatio, pixelRatio);
2575
3230
  ctx.translate(-left, -top);
2576
- this.drawTo(ctx, style);
3231
+ this.paths.forEach((path) => {
3232
+ path.drawTo(ctx, style);
3233
+ });
2577
3234
  }
2578
3235
  return canvas;
2579
3236
  }
2580
- copy(source) {
2581
- this.currentPath = source.currentPath.clone();
2582
- this.paths = source.paths.map((path) => path.clone());
2583
- this.style = { ...source.style };
2584
- return this;
2585
- }
2586
- clone() {
2587
- return new this.constructor().copy(this);
2588
- }
2589
3237
  }
2590
3238
 
2591
3239
  const defaultUnit = "px";
@@ -3114,46 +3762,7 @@ ${xml}`);
3114
3762
  }
3115
3763
  }
3116
3764
  function parseSVG(svg) {
3117
- return parseNode(parseSVGToDOM(svg), {});
3118
- }
3119
-
3120
- function getPathsBoundingBox(paths, withStyle = true) {
3121
- if (!paths.length) {
3122
- return void 0;
3123
- }
3124
- const min = Vector2.MAX;
3125
- const max = Vector2.MIN;
3126
- paths.forEach((path) => path.getMinMax(min, max, withStyle));
3127
- return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
3128
- }
3129
- function pathsToSVGString(paths) {
3130
- const { x, y, width, height } = getPathsBoundingBox(paths);
3131
- const content = paths.map((path) => path.toSVGPathString()).join("");
3132
- return `<svg viewBox="${x} ${y} ${width} ${height}" width="${width}px" height="${height}px" xmlns="http://www.w3.org/2000/svg">${content}</svg>`;
3133
- }
3134
- function pathsToSVGUrl(paths) {
3135
- return `data:image/svg+xml;base64,${btoa(pathsToSVGString(paths))}`;
3136
- }
3137
- function pathsToSVG(paths) {
3138
- return new DOMParser().parseFromString(pathsToSVGString(paths), "image/svg+xml").documentElement;
3139
- }
3140
- function pathsToCanvas(paths, options = {}) {
3141
- const { pixelRatio = 2, ...style } = options;
3142
- const { left, top, width, height } = getPathsBoundingBox(paths);
3143
- const canvas = document.createElement("canvas");
3144
- canvas.width = width * pixelRatio;
3145
- canvas.height = height * pixelRatio;
3146
- canvas.style.width = `${width}px`;
3147
- canvas.style.height = `${height}px`;
3148
- const ctx = canvas.getContext("2d");
3149
- if (ctx) {
3150
- ctx.scale(pixelRatio, pixelRatio);
3151
- ctx.translate(-left, -top);
3152
- paths.forEach((path) => {
3153
- path.drawTo(ctx, style);
3154
- });
3155
- }
3156
- return canvas;
3765
+ return new Path2DSet(parseNode(parseSVGToDOM(svg), {}));
3157
3766
  }
3158
3767
 
3159
- export { BoundingBox, CircleCurve, CubicBezierCurve, Curve, CurvePath, EllipseCurve, HeartCurve, LineCurve, Matrix3, Path2D, PloygonCurve, QuadraticBezierCurve, RectangularCurve, SplineCurve, Vector2, addPathCommandsToPath2D, getPathsBoundingBox, parseArcCommand, parsePathDataArgs, parseSVG, parseSVGToDOM, pathCommandsToPathData, pathDataToPathCommands, pathsToCanvas, pathsToSVG, pathsToSVGString, pathsToSVGUrl, setCanvasContext };
3768
+ export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, EllipseCurve, LineCurve, Matrix3, Path2D, Path2DSet, PloygonCurve, QuadraticBezierCurve, RectangleCurve, SplineCurve, Vector2, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, parseArcCommand, parsePathDataArgs, parseSVG, parseSVGToDOM, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands };