modern-path2d 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,690 @@ 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
+ applyTransform(transform) {
1593
+ this.getControlPointRefs().forEach((p) => {
1594
+ p.applyMatrix3(transform);
1595
+ });
1596
+ return this;
1597
+ }
1598
+ getUnevenPointArray(count = 5, output = []) {
1599
+ const p = new Vector2();
1600
+ for (let i = 0, len = Math.max(1, count) - 1; i <= len; i++) {
1601
+ this.getPoint(i / len, p);
1602
+ output.push(p.x, p.y);
1010
1603
  }
1011
- return points;
1604
+ return output;
1012
1605
  }
1013
- forEachControlPoints(cb) {
1014
- this.getControlPoints().forEach(cb);
1015
- return this;
1606
+ getSpacedPointArray(count = 5, output = []) {
1607
+ const p = new Vector2();
1608
+ for (let i = 0, len = Math.max(1, count) - 1; i <= len; i++) {
1609
+ this.getPointAt(i / len, p);
1610
+ output.push(p.x, p.y);
1611
+ }
1612
+ return output;
1016
1613
  }
1017
- getSpacedPoints(divisions = 5) {
1018
- const points = [];
1019
- for (let i = 0; i <= divisions; i++) {
1020
- points.push(this.getPointAt(i / divisions));
1614
+ getAdaptivePointArray(output = []) {
1615
+ return this.getUnevenPointArray(5, output);
1616
+ }
1617
+ _pointArrayToPoint(array, output = []) {
1618
+ for (let i = 0, len = array.length; i < len; i += 2) {
1619
+ const x = array[i];
1620
+ const y = array[i + 1];
1621
+ output.push(new Vector2(x, y));
1021
1622
  }
1022
- return points;
1623
+ return output;
1624
+ }
1625
+ getSpacedPoints(count, output = []) {
1626
+ const array = this.getSpacedPointArray(count);
1627
+ this._pointArrayToPoint(array, output);
1628
+ return output;
1629
+ }
1630
+ getUnevenPoints(count, output = []) {
1631
+ const array = this.getUnevenPointArray(count);
1632
+ this._pointArrayToPoint(array, output);
1633
+ return output;
1634
+ }
1635
+ getAdaptivePoints(output = []) {
1636
+ const array = this.getAdaptivePointArray();
1637
+ this._pointArrayToPoint(array, output);
1638
+ return output;
1639
+ }
1640
+ getPoints(count, output = []) {
1641
+ let array;
1642
+ if (count) {
1643
+ array = this.getUnevenPointArray(count);
1644
+ } else {
1645
+ array = this.getAdaptivePointArray();
1646
+ }
1647
+ this._pointArrayToPoint(array, output);
1648
+ return output;
1023
1649
  }
1024
1650
  getLength() {
1025
1651
  const lengths = this.getLengths();
1026
1652
  return lengths[lengths.length - 1];
1027
1653
  }
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();
1654
+ getLengths() {
1655
+ if (!this._arcLengths || this._arcLengths.length !== this.arcLengthDivision + 1) {
1656
+ this.updateLengths();
1657
+ }
1658
+ return this._arcLengths;
1659
+ }
1660
+ updateLengths() {
1661
+ const divisions = this.arcLengthDivision;
1662
+ const arcLengths = [0];
1663
+ for (let sum = 0, prev = this.getPoint(0), i = 1; i <= divisions; i++) {
1664
+ const current = this.getPoint(i / divisions);
1665
+ sum += current.distanceTo(prev);
1666
+ arcLengths.push(sum);
1667
+ prev = current;
1668
+ }
1669
+ this._arcLengths = arcLengths;
1050
1670
  }
1051
1671
  getUToTMapping(u, distance) {
1052
1672
  const lengths = this.getLengths();
1053
- let i = 0;
1054
1673
  const lengthsLen = lengths.length;
1055
- let targetLength;
1056
- if (distance) {
1057
- targetLength = distance;
1058
- } else {
1059
- targetLength = u * lengths[lengthsLen - 1];
1674
+ const targetLen = distance ?? u * lengths[lengthsLen - 1];
1675
+ if (lengthsLen < 2) {
1676
+ return targetLen / lengths[0];
1060
1677
  }
1678
+ let i = 0;
1061
1679
  let low = 0;
1062
1680
  let high = lengthsLen - 1;
1063
1681
  let comparison;
1064
1682
  while (low <= high) {
1065
1683
  i = Math.floor(low + (high - low) / 2);
1066
- comparison = lengths[i] - targetLength;
1684
+ comparison = lengths[i] - targetLen;
1067
1685
  if (comparison < 0) {
1068
1686
  low = i + 1;
1069
1687
  } else if (comparison > 0) {
@@ -1074,13 +1692,13 @@ class Curve {
1074
1692
  }
1075
1693
  }
1076
1694
  i = high;
1077
- if (lengths[i] === targetLength) {
1695
+ if (lengths[i] === targetLen) {
1078
1696
  return i / (lengthsLen - 1);
1079
1697
  }
1080
- const lengthBefore = lengths[i];
1081
- const lengthAfter = lengths[i + 1];
1082
- const segmentLength = lengthAfter - lengthBefore;
1083
- const segmentFraction = (targetLength - lengthBefore) / segmentLength;
1698
+ const before = lengths[i];
1699
+ const after = lengths[i + 1];
1700
+ const segmentLength = after - before;
1701
+ const segmentFraction = (targetLen - before) / segmentLength;
1084
1702
  return (i + segmentFraction) / (lengthsLen - 1);
1085
1703
  }
1086
1704
  getTangent(t, output = new Vector2()) {
@@ -1118,32 +1736,52 @@ class Curve {
1118
1736
  }
1119
1737
  return mid;
1120
1738
  }
1121
- matrix(matrix) {
1122
- this.forEachControlPoints((point) => point.applyMatrix3(matrix));
1123
- return this;
1124
- }
1125
1739
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1126
- this.getPoints().forEach((point) => {
1127
- min.min(point);
1128
- max.max(point);
1129
- });
1740
+ const potins = this.getPoints();
1741
+ for (let i = 0, len = potins.length; i < len; i++) {
1742
+ const p = potins[i];
1743
+ min.min(p);
1744
+ max.max(p);
1745
+ }
1130
1746
  return { min, max };
1131
1747
  }
1132
1748
  getBoundingBox() {
1133
1749
  const { min, max } = this.getMinMax();
1134
1750
  return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
1135
1751
  }
1752
+ fillTriangulate(options) {
1753
+ return fillTriangulate(
1754
+ this.getPoints().reduce((arr, p) => {
1755
+ arr.push(p.x, p.y);
1756
+ return arr;
1757
+ }, []),
1758
+ options
1759
+ );
1760
+ }
1761
+ strokeTriangulate(options) {
1762
+ return strokeTriangulate(
1763
+ this.getPoints().reduce((arr, p) => {
1764
+ arr.push(p.x, p.y);
1765
+ return arr;
1766
+ }, []),
1767
+ options
1768
+ );
1769
+ }
1136
1770
  toCommands() {
1137
- return this.getPoints().map((point, i) => {
1771
+ const comds = [];
1772
+ const potins = this.getPoints();
1773
+ for (let i = 0, len = potins.length; i < len; i++) {
1774
+ const p = potins[i];
1138
1775
  if (i === 0) {
1139
- return { type: "M", x: point.x, y: point.y };
1776
+ comds.push({ type: "M", x: p.x, y: p.y });
1140
1777
  } else {
1141
- return { type: "L", x: point.x, y: point.y };
1778
+ comds.push({ type: "L", x: p.x, y: p.y });
1142
1779
  }
1143
- });
1780
+ }
1781
+ return comds;
1144
1782
  }
1145
1783
  toData() {
1146
- return pathCommandsToPathData(this.toCommands());
1784
+ return svgPathCommandsToData(this.toCommands());
1147
1785
  }
1148
1786
  drawTo(ctx) {
1149
1787
  this.toCommands().forEach((cmd) => {
@@ -1159,7 +1797,7 @@ class Curve {
1159
1797
  return this;
1160
1798
  }
1161
1799
  copy(source) {
1162
- this.arcLengthDivisions = source.arcLengthDivisions;
1800
+ this.arcLengthDivision = source.arcLengthDivision;
1163
1801
  return this;
1164
1802
  }
1165
1803
  clone() {
@@ -1167,178 +1805,56 @@ class Curve {
1167
1805
  }
1168
1806
  }
1169
1807
 
1170
- class CircleCurve extends Curve {
1171
- constructor(center, radius, start = 0, end = Math.PI * 2) {
1808
+ const tempTransform0$1 = new Matrix3();
1809
+ const tempTransform1$1 = new Matrix3();
1810
+ const tempTransform2$1 = new Matrix3();
1811
+ const tempV2 = new Vector2();
1812
+ class RoundCurve extends Curve {
1813
+ constructor(_center, _radius, _diff, rotate = 0, startAngle = 0, endAngle = Math.PI * 2, clockwise = false) {
1172
1814
  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));
1815
+ this._center = _center;
1816
+ this._radius = _radius;
1817
+ this._diff = _diff;
1818
+ this.rotate = rotate;
1819
+ this.startAngle = startAngle;
1820
+ this.endAngle = endAngle;
1821
+ this.clockwise = clockwise;
1181
1822
  }
1182
- getTangent(t, output = new Vector2()) {
1183
- const { x, y } = this.getNormal(t);
1184
- return output.set(-y, x);
1823
+ get cx() {
1824
+ return this._center.x;
1185
1825
  }
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));
1826
+ set cx(val) {
1827
+ this._center.x = val;
1190
1828
  }
1191
- getControlPoints() {
1192
- return [this.center];
1829
+ get cy() {
1830
+ return this._center.y;
1193
1831
  }
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;
1832
+ set cy(val) {
1833
+ this._center.y = val;
1248
1834
  }
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
- );
1835
+ get rx() {
1836
+ return this._radius.x;
1255
1837
  }
1256
- getControlPoints() {
1257
- return [
1258
- this.start,
1259
- this.startControl,
1260
- this.endControl,
1261
- this.end
1262
- ];
1838
+ set rx(val) {
1839
+ this._radius.x = val;
1263
1840
  }
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);
1841
+ get ry() {
1842
+ return this._radius.y;
1272
1843
  }
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 };
1844
+ set ry(val) {
1845
+ this._radius.y = val;
1304
1846
  }
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
- ];
1847
+ get dx() {
1848
+ return this._diff.x;
1311
1849
  }
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;
1850
+ set dx(val) {
1851
+ this._diff.x = val;
1317
1852
  }
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;
1853
+ get dy() {
1854
+ return this._diff.y;
1325
1855
  }
1326
- }
1327
-
1328
- const tempTransform0$1 = new Matrix3();
1329
- const tempTransform1$1 = new Matrix3();
1330
- const tempTransform2$1 = new Matrix3();
1331
- const tempV2 = new Vector2();
1332
- class EllipseCurve extends Curve {
1333
- constructor(center = new Vector2(), radiusX = 1, radiusY = 1, rotation = 0, startAngle = 0, endAngle = Math.PI * 2, clockwise = false) {
1334
- super();
1335
- this.center = center;
1336
- this.radiusX = radiusX;
1337
- this.radiusY = radiusY;
1338
- this.rotation = rotation;
1339
- this.startAngle = startAngle;
1340
- this.endAngle = endAngle;
1341
- this.clockwise = clockwise;
1856
+ set dy(val) {
1857
+ this._diff.y = val;
1342
1858
  }
1343
1859
  isClockwise() {
1344
1860
  return this.clockwise;
@@ -1364,39 +1880,38 @@ class EllipseCurve extends Curve {
1364
1880
  }
1365
1881
  }
1366
1882
  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);
1369
- if (this.rotation !== 0) {
1370
- const cos = Math.cos(this.rotation);
1371
- const sin = Math.sin(this.rotation);
1372
- const tx = _x - this.center.x;
1373
- const ty = _y - this.center.y;
1374
- _x = tx * cos - ty * sin + this.center.x;
1375
- _y = tx * sin + ty * cos + this.center.y;
1883
+ let _x = this.cx + this.rx * Math.cos(angle);
1884
+ let _y = this.cy + this.ry * Math.sin(angle);
1885
+ if (this.rotate !== 0) {
1886
+ const cos = Math.cos(this.rotate);
1887
+ const sin = Math.sin(this.rotate);
1888
+ const tx = _x - this.cx;
1889
+ const ty = _y - this.cy;
1890
+ _x = tx * cos - ty * sin + this.cx;
1891
+ _y = tx * sin + ty * cos + this.cy;
1376
1892
  }
1377
1893
  return output.set(_x, _y);
1378
1894
  }
1379
1895
  toCommands() {
1380
- const { center, radiusX: rx, radiusY: ry, startAngle, endAngle, clockwise, rotation } = this;
1381
- const { x: cx, y: cy } = center;
1382
- const startX = cx + rx * Math.cos(startAngle) * Math.cos(rotation) - ry * Math.sin(startAngle) * Math.sin(rotation);
1383
- const startY = cy + rx * Math.cos(startAngle) * Math.sin(rotation) + ry * Math.sin(startAngle) * Math.cos(rotation);
1896
+ const { cx, cy, rx, ry, startAngle, endAngle, clockwise, rotate } = this;
1897
+ const startX = cx + rx * Math.cos(startAngle) * Math.cos(rotate) - ry * Math.sin(startAngle) * Math.sin(rotate);
1898
+ const startY = cy + rx * Math.cos(startAngle) * Math.sin(rotate) + ry * Math.sin(startAngle) * Math.cos(rotate);
1384
1899
  const angleDiff = Math.abs(startAngle - endAngle);
1385
1900
  const largeArcFlag = angleDiff > Math.PI ? 1 : 0;
1386
1901
  const sweepFlag = clockwise ? 1 : 0;
1387
- const angle = rotation * 180 / Math.PI;
1902
+ const angle = rotate * 180 / Math.PI;
1388
1903
  if (angleDiff >= 2 * Math.PI) {
1389
1904
  const midAngle = startAngle + Math.PI;
1390
- const midX = cx + rx * Math.cos(midAngle) * Math.cos(rotation) - ry * Math.sin(midAngle) * Math.sin(rotation);
1391
- const midY = cy + rx * Math.cos(midAngle) * Math.sin(rotation) + ry * Math.sin(midAngle) * Math.cos(rotation);
1905
+ const midX = cx + rx * Math.cos(midAngle) * Math.cos(rotate) - ry * Math.sin(midAngle) * Math.sin(rotate);
1906
+ const midY = cy + rx * Math.cos(midAngle) * Math.sin(rotate) + ry * Math.sin(midAngle) * Math.cos(rotate);
1392
1907
  return [
1393
1908
  { type: "M", x: startX, y: startY },
1394
1909
  { type: "A", rx, ry, angle, largeArcFlag: 0, sweepFlag, x: midX, y: midY },
1395
1910
  { type: "A", rx, ry, angle, largeArcFlag: 0, sweepFlag, x: startX, y: startY }
1396
1911
  ];
1397
1912
  } else {
1398
- const endX = cx + rx * Math.cos(endAngle) * Math.cos(rotation) - ry * Math.sin(endAngle) * Math.sin(rotation);
1399
- const endY = cy + rx * Math.cos(endAngle) * Math.sin(rotation) + ry * Math.sin(endAngle) * Math.cos(rotation);
1913
+ const endX = cx + rx * Math.cos(endAngle) * Math.cos(rotate) - ry * Math.sin(endAngle) * Math.sin(rotate);
1914
+ const endY = cy + rx * Math.cos(endAngle) * Math.sin(rotate) + ry * Math.sin(endAngle) * Math.cos(rotate);
1400
1915
  return [
1401
1916
  { type: "M", x: startX, y: startY },
1402
1917
  { type: "A", rx, ry, angle, largeArcFlag, sweepFlag, x: endX, y: endY }
@@ -1404,24 +1919,24 @@ class EllipseCurve extends Curve {
1404
1919
  }
1405
1920
  }
1406
1921
  drawTo(ctx) {
1407
- const { center, radiusX, radiusY, rotation, startAngle, endAngle, clockwise } = this;
1922
+ const { cx, cy, rx, ry, rotate, startAngle, endAngle, clockwise } = this;
1408
1923
  ctx.ellipse(
1409
- center.x,
1410
- center.y,
1411
- radiusX,
1412
- radiusY,
1413
- rotation,
1924
+ cx,
1925
+ cy,
1926
+ rx,
1927
+ ry,
1928
+ rotate,
1414
1929
  startAngle,
1415
1930
  endAngle,
1416
1931
  !clockwise
1417
1932
  );
1418
1933
  return this;
1419
1934
  }
1420
- matrix(matrix) {
1421
- tempV2.set(this.center.x, this.center.y);
1935
+ applyTransform(matrix) {
1936
+ tempV2.set(this.cx, this.cy);
1422
1937
  tempV2.applyMatrix3(matrix);
1423
- this.center.x = tempV2.x;
1424
- this.center.y = tempV2.y;
1938
+ this.cx = tempV2.x;
1939
+ this.cy = tempV2.y;
1425
1940
  if (isTransformSkewed(matrix)) {
1426
1941
  transfEllipseGeneric(this, matrix);
1427
1942
  } else {
@@ -1429,14 +1944,129 @@ class EllipseCurve extends Curve {
1429
1944
  }
1430
1945
  return this;
1431
1946
  }
1432
- getControlPoints() {
1433
- return [this.center];
1947
+ getControlPointRefs() {
1948
+ return [this._center];
1949
+ }
1950
+ getAdaptivePointArray(output = []) {
1951
+ const { cx, cy, rx, ry, dx, dy } = this;
1952
+ if (!(rx >= 0 && ry >= 0 && dx >= 0 && dy >= 0)) {
1953
+ return output;
1954
+ }
1955
+ const n = Math.ceil(2.3 * Math.sqrt(rx + ry));
1956
+ const m = n * 8 + (dx ? 4 : 0) + (dy ? 4 : 0);
1957
+ if (m === 0) {
1958
+ return output;
1959
+ }
1960
+ const array = [];
1961
+ if (n === 0) {
1962
+ array[0] = array[6] = cx + dx;
1963
+ array[1] = array[3] = cy + dy;
1964
+ array[2] = array[4] = cx - dx;
1965
+ array[5] = array[7] = cy - dy;
1966
+ } else {
1967
+ let j1 = 0;
1968
+ let j2 = n * 4 + (dx ? 2 : 0) + 2;
1969
+ let j3 = j2;
1970
+ let j4 = m;
1971
+ let x0 = dx + rx;
1972
+ let y0 = dy;
1973
+ let x1 = cx + x0;
1974
+ let x2 = cx - x0;
1975
+ let y1 = cy + y0;
1976
+ array[j1++] = x1;
1977
+ array[j1++] = y1;
1978
+ array[--j2] = y1;
1979
+ array[--j2] = x2;
1980
+ if (dy) {
1981
+ const y22 = cy - y0;
1982
+ array[j3++] = x2;
1983
+ array[j3++] = y22;
1984
+ array[--j4] = y22;
1985
+ array[--j4] = x1;
1986
+ }
1987
+ for (let i = 1; i < n; i++) {
1988
+ const a = Math.PI / 2 * (i / n);
1989
+ const x02 = dx + Math.cos(a) * rx;
1990
+ const y02 = dy + Math.sin(a) * ry;
1991
+ const x12 = cx + x02;
1992
+ const x22 = cx - x02;
1993
+ const y12 = cy + y02;
1994
+ const y22 = cy - y02;
1995
+ array[j1++] = x12;
1996
+ array[j1++] = y12;
1997
+ array[--j2] = y12;
1998
+ array[--j2] = x22;
1999
+ array[j3++] = x22;
2000
+ array[j3++] = y22;
2001
+ array[--j4] = y22;
2002
+ array[--j4] = x12;
2003
+ }
2004
+ x0 = dx;
2005
+ y0 = dy + ry;
2006
+ x1 = cx + x0;
2007
+ x2 = cx - x0;
2008
+ y1 = cy + y0;
2009
+ const y2 = cy - y0;
2010
+ array[j1++] = x1;
2011
+ array[j1++] = y1;
2012
+ array[--j4] = y2;
2013
+ array[--j4] = x1;
2014
+ if (dx) {
2015
+ array[j1++] = x2;
2016
+ array[j1++] = y1;
2017
+ array[--j4] = y2;
2018
+ array[--j4] = x2;
2019
+ }
2020
+ }
2021
+ Array.prototype.push.apply(output, array);
2022
+ return output;
2023
+ }
2024
+ fillTriangulate(options = {}) {
2025
+ let {
2026
+ vertices = [],
2027
+ indices = [],
2028
+ verticesStride = 2,
2029
+ verticesOffset = 0,
2030
+ indicesOffset = 0
2031
+ } = options;
2032
+ const points = this.getAdaptivePointArray();
2033
+ if (points.length === 0) {
2034
+ return { vertices, indices };
2035
+ }
2036
+ let centerX = 0;
2037
+ let centerY = 0;
2038
+ for (let i = 0; i < points.length; i += 2) {
2039
+ centerX += points[i];
2040
+ centerY += points[i + 1];
2041
+ }
2042
+ centerX /= points.length / 2;
2043
+ centerY /= points.length / 2;
2044
+ let count = verticesOffset;
2045
+ vertices[count * verticesStride] = centerX;
2046
+ vertices[count * verticesStride + 1] = centerY;
2047
+ const centerIndex = count++;
2048
+ for (let i = 0; i < points.length; i += 2) {
2049
+ vertices[count * verticesStride] = points[i];
2050
+ vertices[count * verticesStride + 1] = points[i + 1];
2051
+ if (i > 0) {
2052
+ indices[indicesOffset++] = count;
2053
+ indices[indicesOffset++] = centerIndex;
2054
+ indices[indicesOffset++] = count - 1;
2055
+ }
2056
+ count++;
2057
+ }
2058
+ indices[indicesOffset++] = centerIndex + 1;
2059
+ indices[indicesOffset++] = centerIndex;
2060
+ indices[indicesOffset++] = count - 1;
2061
+ return {
2062
+ vertices,
2063
+ indices
2064
+ };
1434
2065
  }
1435
2066
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1436
- const { center, radiusX: rx, radiusY: ry, rotation: theta } = this;
1437
- const { x: cx, y: cy } = center;
1438
- const cosTheta = Math.cos(theta);
1439
- const sinTheta = Math.sin(theta);
2067
+ const { cx, cy, rx, ry, rotate } = this;
2068
+ const cosTheta = Math.cos(rotate);
2069
+ const sinTheta = Math.sin(rotate);
1440
2070
  const halfWidth = Math.sqrt(
1441
2071
  rx * rx * cosTheta * cosTheta + ry * ry * sinTheta * sinTheta
1442
2072
  );
@@ -1451,22 +2081,22 @@ class EllipseCurve extends Curve {
1451
2081
  }
1452
2082
  copy(source) {
1453
2083
  super.copy(source);
1454
- this.center.x = source.center.x;
1455
- this.center.y = source.center.y;
1456
- this.radiusX = source.radiusX;
1457
- this.radiusY = source.radiusY;
2084
+ this.cx = source.cx;
2085
+ this.cy = source.cy;
2086
+ this.rx = source.rx;
2087
+ this.ry = source.ry;
1458
2088
  this.startAngle = source.startAngle;
1459
2089
  this.endAngle = source.endAngle;
1460
2090
  this.clockwise = source.clockwise;
1461
- this.rotation = source.rotation;
2091
+ this.rotate = source.rotate;
1462
2092
  return this;
1463
2093
  }
1464
2094
  }
1465
2095
  function transfEllipseGeneric(curve, m) {
1466
- const a = curve.radiusX;
1467
- const b = curve.radiusY;
1468
- const cosTheta = Math.cos(curve.rotation);
1469
- const sinTheta = Math.sin(curve.rotation);
2096
+ const a = curve.rx;
2097
+ const b = curve.ry;
2098
+ const cosTheta = Math.cos(curve.rotate);
2099
+ const sinTheta = Math.sin(curve.rotate);
1470
2100
  const v1 = new Vector2(a * cosTheta, a * sinTheta);
1471
2101
  const v2 = new Vector2(-b * sinTheta, b * cosTheta);
1472
2102
  const f1 = v1.applyMatrix3(m);
@@ -1489,9 +2119,9 @@ function transfEllipseGeneric(curve, m) {
1489
2119
  const ed = eigenDecomposition(mQe[0], mQe[1], mQe[4]);
1490
2120
  const rt1sqrt = Math.sqrt(ed.rt1);
1491
2121
  const rt2sqrt = Math.sqrt(ed.rt2);
1492
- curve.radiusX = 1 / rt1sqrt;
1493
- curve.radiusY = 1 / rt2sqrt;
1494
- curve.rotation = Math.atan2(ed.sn, ed.cs);
2122
+ curve.rx = 1 / rt1sqrt;
2123
+ curve.ry = 1 / rt2sqrt;
2124
+ curve.rotate = Math.atan2(ed.sn, ed.cs);
1495
2125
  const isFullEllipse = (curve.endAngle - curve.startAngle) % (2 * Math.PI) < Number.EPSILON;
1496
2126
  if (!isFullEllipse) {
1497
2127
  const mDsqrt = tempTransform1$1.set(
@@ -1531,10 +2161,10 @@ function transfEllipseGeneric(curve, m) {
1531
2161
  function transfEllipseNoSkew(curve, m) {
1532
2162
  const sx = getTransformScaleX(m);
1533
2163
  const sy = getTransformScaleY(m);
1534
- curve.radiusX *= sx;
1535
- curve.radiusY *= sy;
2164
+ curve.rx *= sx;
2165
+ curve.ry *= sy;
1536
2166
  const theta = sx > Number.EPSILON ? Math.atan2(m.elements[1], m.elements[0]) : Math.atan2(-m.elements[3], m.elements[4]);
1537
- curve.rotation += theta;
2167
+ curve.rotate += theta;
1538
2168
  if (isTransformFlipped(m)) {
1539
2169
  curve.startAngle *= -1;
1540
2170
  curve.endAngle *= -1;
@@ -1599,20 +2229,276 @@ function eigenDecomposition(A, B, C) {
1599
2229
  cs = -sn;
1600
2230
  sn = t;
1601
2231
  }
1602
- return { rt1, rt2, cs, sn };
2232
+ return { rt1, rt2, cs, sn };
2233
+ }
2234
+
2235
+ class ArcCurve extends RoundCurve {
2236
+ constructor(cx = 0, cy = 0, radius = 1, startAngle = 0, endAngle = Math.PI * 2, clockwise = false) {
2237
+ super(
2238
+ new Vector2(cx, cy),
2239
+ new Vector2(radius, radius),
2240
+ new Vector2(0, 0),
2241
+ 0,
2242
+ startAngle,
2243
+ endAngle,
2244
+ clockwise
2245
+ );
2246
+ this.radius = radius;
2247
+ }
2248
+ drawTo(ctx) {
2249
+ const { cx, cy, radius, startAngle, endAngle, clockwise } = this;
2250
+ ctx.arc(
2251
+ cx,
2252
+ cy,
2253
+ radius,
2254
+ startAngle,
2255
+ endAngle,
2256
+ !clockwise
2257
+ );
2258
+ return this;
2259
+ }
2260
+ getAdaptivePointArray(output = []) {
2261
+ const { cx, cy, radius, startAngle, endAngle, clockwise } = this;
2262
+ let dist = Math.abs(startAngle - endAngle);
2263
+ if (!clockwise && startAngle > endAngle) {
2264
+ dist = 2 * Math.PI - dist;
2265
+ } else if (clockwise && endAngle > startAngle) {
2266
+ dist = 2 * Math.PI - dist;
2267
+ }
2268
+ let steps = Math.max(6, Math.floor(6 * radius ** (1 / 3) * (dist / Math.PI)));
2269
+ steps = Math.max(steps, 3);
2270
+ let f = dist / steps;
2271
+ let t = startAngle;
2272
+ f *= !clockwise ? -1 : 1;
2273
+ for (let i = 0; i < steps + 1; i++) {
2274
+ const cs = Math.cos(t);
2275
+ const sn = Math.sin(t);
2276
+ const nx = cx + cs * radius;
2277
+ const ny = cy + sn * radius;
2278
+ output.push(nx, ny);
2279
+ t += f;
2280
+ }
2281
+ return output;
2282
+ }
2283
+ }
2284
+
2285
+ class CompositeCurve extends Curve {
2286
+ constructor(curves = []) {
2287
+ super();
2288
+ this.curves = curves;
2289
+ }
2290
+ addCurve(curve) {
2291
+ this.curves.push(curve);
2292
+ return this;
2293
+ }
2294
+ getPoint(t, output = new Vector2()) {
2295
+ const d = t * this.getLength();
2296
+ const lengths = this.getLengths();
2297
+ let i = 0;
2298
+ while (i < lengths.length) {
2299
+ if (lengths[i] >= d) {
2300
+ const diff = lengths[i] - d;
2301
+ const curve = this.curves[i];
2302
+ const length = curve.getLength();
2303
+ return curve.getPointAt(
2304
+ length === 0 ? 0 : 1 - diff / length,
2305
+ output
2306
+ );
2307
+ }
2308
+ i++;
2309
+ }
2310
+ return output;
2311
+ }
2312
+ updateLengths() {
2313
+ const arcLengths = [];
2314
+ for (let i = 0, sum = 0, len = this.curves.length; i < len; i++) {
2315
+ sum += this.curves[i].getLength();
2316
+ arcLengths.push(sum);
2317
+ }
2318
+ this._arcLengths = arcLengths;
2319
+ }
2320
+ getControlPointRefs() {
2321
+ return this.curves.flatMap((curve) => curve.getControlPointRefs());
2322
+ }
2323
+ getAdaptivePointArray(output = []) {
2324
+ let offset;
2325
+ this.curves.forEach((curve) => {
2326
+ curve.getAdaptivePointArray(output);
2327
+ if (offset) {
2328
+ if (output[offset - 1] === output[offset + 1] && output[offset] === output[offset + 2]) {
2329
+ output.splice(offset + 1, 2);
2330
+ }
2331
+ }
2332
+ offset = output.length - 1;
2333
+ });
2334
+ return output;
2335
+ }
2336
+ getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
2337
+ this.curves.forEach((curve) => curve.getMinMax(min, max));
2338
+ return { min, max };
2339
+ }
2340
+ getBoundingBox() {
2341
+ const { min, max } = this.getMinMax();
2342
+ return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
2343
+ }
2344
+ toCommands() {
2345
+ return this.curves.flatMap((curve) => curve.toCommands());
2346
+ }
2347
+ drawTo(ctx) {
2348
+ const point = this.curves[0]?.getPoint(0);
2349
+ if (point) {
2350
+ ctx.moveTo(point.x, point.y);
2351
+ }
2352
+ this.curves.forEach((curve) => curve.drawTo(ctx));
2353
+ return this;
2354
+ }
2355
+ }
2356
+
2357
+ class CubicBezierCurve extends Curve {
2358
+ constructor(p1, cp1, cp2, p2) {
2359
+ super();
2360
+ this.p1 = p1;
2361
+ this.cp1 = cp1;
2362
+ this.cp2 = cp2;
2363
+ this.p2 = p2;
2364
+ }
2365
+ static from(p1x, p1y, cp1x, cp1y, cp2x, cp2y, p2x, p2y) {
2366
+ return new CubicBezierCurve(
2367
+ new Vector2(p1x, p1y),
2368
+ new Vector2(cp1x, cp1y),
2369
+ new Vector2(cp2x, cp2y),
2370
+ new Vector2(p2x, p2y)
2371
+ );
2372
+ }
2373
+ getPoint(t, output = new Vector2()) {
2374
+ const { p1, cp1, cp2, p2 } = this;
2375
+ return output.set(
2376
+ cubicBezier(t, p1.x, cp1.x, cp2.x, p2.x),
2377
+ cubicBezier(t, p1.y, cp1.y, cp2.y, p2.y)
2378
+ );
2379
+ }
2380
+ getAdaptivePointArray(output = []) {
2381
+ return getAdaptiveCubicBezierCurvePoints(
2382
+ this.p1.x,
2383
+ this.p1.y,
2384
+ this.cp1.x,
2385
+ this.cp1.y,
2386
+ this.cp2.x,
2387
+ this.cp2.y,
2388
+ this.p2.x,
2389
+ this.p2.y,
2390
+ 0.5,
2391
+ output
2392
+ );
2393
+ }
2394
+ getControlPointRefs() {
2395
+ return [this.p1, this.cp1, this.cp2, this.p2];
2396
+ }
2397
+ _solveQuadratic(a, b, c) {
2398
+ const discriminant = b * b - 4 * a * c;
2399
+ if (discriminant < 0)
2400
+ return [];
2401
+ const sqrtDiscriminant = Math.sqrt(discriminant);
2402
+ const t1 = (-b + sqrtDiscriminant) / (2 * a);
2403
+ const t2 = (-b - sqrtDiscriminant) / (2 * a);
2404
+ return [t1, t2].filter((t) => t >= 0 && t <= 1);
2405
+ }
2406
+ getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
2407
+ const { p1, cp1, cp2, p2 } = this;
2408
+ const dxRoots = this._solveQuadratic(
2409
+ 3 * (cp1.x - p1.x),
2410
+ 6 * (cp2.x - cp1.x),
2411
+ 3 * (p2.x - cp2.x)
2412
+ );
2413
+ const dyRoots = this._solveQuadratic(
2414
+ 3 * (cp1.y - p1.y),
2415
+ 6 * (cp2.y - cp1.y),
2416
+ 3 * (p2.y - cp2.y)
2417
+ );
2418
+ const tValues = [0, 1, ...dxRoots, ...dyRoots];
2419
+ const samplePoints = (tValues2, precision) => {
2420
+ for (const t of tValues2) {
2421
+ for (let i = 0; i <= precision; i++) {
2422
+ const delta = i / precision - 0.5;
2423
+ const refinedT = Math.min(1, Math.max(0, t + delta));
2424
+ const point = this.getPoint(refinedT);
2425
+ min.x = Math.min(min.x, point.x);
2426
+ min.y = Math.min(min.y, point.y);
2427
+ max.x = Math.max(max.x, point.x);
2428
+ max.y = Math.max(max.y, point.y);
2429
+ }
2430
+ }
2431
+ };
2432
+ samplePoints(tValues, 10);
2433
+ return { min, max };
2434
+ }
2435
+ toCommands() {
2436
+ const { p1, cp1, cp2, p2 } = this;
2437
+ return [
2438
+ { type: "M", x: p1.x, y: p1.y },
2439
+ { type: "C", x1: cp1.x, y1: cp1.y, x2: cp2.x, y2: cp2.y, x: p2.x, y: p2.y }
2440
+ ];
2441
+ }
2442
+ drawTo(ctx) {
2443
+ const { p1, cp1, cp2, p2 } = this;
2444
+ ctx.lineTo(p1.x, p1.y);
2445
+ ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, p2.x, p2.y);
2446
+ return this;
2447
+ }
2448
+ copy(source) {
2449
+ super.copy(source);
2450
+ this.p1.copy(source.p1);
2451
+ this.cp1.copy(source.cp1);
2452
+ this.cp2.copy(source.cp2);
2453
+ this.p2.copy(source.p2);
2454
+ return this;
2455
+ }
2456
+ }
2457
+
2458
+ class EllipseCurve extends RoundCurve {
2459
+ constructor(cx = 0, cy = 0, rx = 1, ry = 1, rotate = 0, startAngle = 0, endAngle = Math.PI * 2, clockwise = false) {
2460
+ super(
2461
+ new Vector2(cx, cy),
2462
+ new Vector2(rx, ry),
2463
+ new Vector2(),
2464
+ rotate,
2465
+ startAngle,
2466
+ endAngle,
2467
+ clockwise
2468
+ );
2469
+ }
2470
+ drawTo(ctx) {
2471
+ ctx.ellipse(
2472
+ this.cx,
2473
+ this.cy,
2474
+ this.rx,
2475
+ this.ry,
2476
+ this.rotate,
2477
+ this.startAngle,
2478
+ this.endAngle,
2479
+ !this.clockwise
2480
+ );
2481
+ return this;
2482
+ }
1603
2483
  }
1604
2484
 
1605
2485
  class LineCurve extends Curve {
1606
- constructor(start = new Vector2(), end = new Vector2()) {
2486
+ constructor(p1, p2) {
1607
2487
  super();
1608
- this.start = start;
1609
- this.end = end;
2488
+ this.p1 = p1;
2489
+ this.p2 = p2;
2490
+ }
2491
+ static from(p1x, p1y, p2x, p2y) {
2492
+ return new LineCurve(
2493
+ new Vector2(p1x, p1y),
2494
+ new Vector2(p2x, p2y)
2495
+ );
1610
2496
  }
1611
2497
  getPoint(t, output = new Vector2()) {
1612
2498
  if (t === 1) {
1613
- output.copy(this.end);
2499
+ output.copy(this.p2);
1614
2500
  } else {
1615
- output.copy(this.end).sub(this.start).scale(t).add(this.start);
2501
+ output.copy(this.p2).sub(this.p1).scale(t).add(this.p1);
1616
2502
  }
1617
2503
  return output;
1618
2504
  }
@@ -1620,330 +2506,247 @@ class LineCurve extends Curve {
1620
2506
  return this.getPoint(u, output);
1621
2507
  }
1622
2508
  getTangent(_t, output = new Vector2()) {
1623
- return output.subVectors(this.end, this.start).normalize();
2509
+ return output.subVectors(this.p2, this.p1).normalize();
1624
2510
  }
1625
2511
  getTangentAt(u, output = new Vector2()) {
1626
2512
  return this.getTangent(u, output);
1627
2513
  }
1628
- getControlPoints() {
1629
- return [
1630
- this.start,
1631
- this.end
1632
- ];
2514
+ getControlPointRefs() {
2515
+ return [this.p1, this.p2];
2516
+ }
2517
+ getAdaptivePointArray(output = []) {
2518
+ output.push(
2519
+ this.p1.x,
2520
+ this.p1.y,
2521
+ this.p2.x,
2522
+ this.p2.y
2523
+ );
2524
+ return output;
1633
2525
  }
1634
2526
  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);
2527
+ const { p1, p2 } = this;
2528
+ min.x = Math.min(min.x, p1.x, p2.x);
2529
+ min.y = Math.min(min.y, p1.y, p2.y);
2530
+ max.x = Math.max(max.x, p1.x, p2.x);
2531
+ max.y = Math.max(max.y, p1.y, p2.y);
1640
2532
  return { min, max };
1641
2533
  }
1642
2534
  toCommands() {
1643
- const { start, end } = this;
2535
+ const { p1, p2 } = this;
1644
2536
  return [
1645
- { type: "M", x: start.x, y: start.y },
1646
- { type: "L", x: end.x, y: end.y }
2537
+ { type: "M", x: p1.x, y: p1.y },
2538
+ { type: "L", x: p2.x, y: p2.y }
1647
2539
  ];
1648
2540
  }
1649
2541
  drawTo(ctx) {
1650
- const { start, end } = this;
1651
- ctx.lineTo(start.x, start.y);
1652
- ctx.lineTo(end.x, end.y);
2542
+ const { p1, p2 } = this;
2543
+ ctx.lineTo(p1.x, p1.y);
2544
+ ctx.lineTo(p2.x, p2.y);
1653
2545
  return this;
1654
2546
  }
1655
2547
  copy(source) {
1656
2548
  super.copy(source);
1657
- this.start.copy(source.start);
1658
- this.end.copy(source.end);
2549
+ this.p1.copy(source.p1);
2550
+ this.p2.copy(source.p2);
1659
2551
  return this;
1660
2552
  }
1661
2553
  }
1662
2554
 
1663
- class HeartCurve extends Curve {
1664
- constructor(center, size, start = 0, end = 1) {
1665
- 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;
1715
- } else {
1716
- index = 4;
1717
- this.curveT = (val - 5 * Math.PI / 8 - 1.5) / PI_1_2;
1718
- }
1719
- return this.curves[index];
1720
- }
1721
- getTangent(t, output) {
1722
- return this.getCurve(t).getTangent(this.curveT, output);
1723
- }
1724
- getNormal(t, output) {
1725
- return this.getCurve(t).getNormal(this.curveT, output);
1726
- }
1727
- getControlPoints() {
1728
- return this.curves.flatMap((curve) => curve.getControlPoints());
1729
- }
1730
- getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1731
- this.curves.forEach((curve) => curve.getMinMax(min, max));
1732
- return { min, max };
1733
- }
1734
- toCommands() {
1735
- return this.curves.flatMap((curve) => curve.toCommands());
1736
- }
1737
- drawTo(ctx) {
1738
- this.curves.forEach((curve) => curve.drawTo(ctx));
1739
- return this;
1740
- }
2555
+ class PloygonCurve extends CompositeCurve {
2556
+ //
1741
2557
  }
1742
2558
 
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(
2559
+ class EquilateralPloygonCurve extends PloygonCurve {
2560
+ constructor(cx = 0, cy = 0, radius = 1, sideCount = 3) {
2561
+ const points = [];
2562
+ for (let i = 0; i < sideCount; i++) {
2563
+ const radian = i * 2 * Math.PI / sideCount - 0.5 * Math.PI;
2564
+ points.push(
1761
2565
  new Vector2(
1762
- this.radius * Math.cos(radian),
1763
- this.radius * Math.sin(radian)
1764
- ).add(this.center)
2566
+ radius * Math.cos(radian),
2567
+ radius * Math.sin(radian)
2568
+ ).add({ x: cx, y: cy })
1765
2569
  );
1766
2570
  }
1767
- for (let i = 0; i < this.number; i++) {
1768
- this.curves.push(new LineCurve(this.points[i], this.points[(i + 1) % this.number]));
2571
+ const curves = [];
2572
+ for (let i = 0; i < sideCount; i++) {
2573
+ curves.push(
2574
+ new LineCurve(
2575
+ points[i],
2576
+ points[(i + 1) % sideCount]
2577
+ )
2578
+ );
1769
2579
  }
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;
2580
+ super(curves);
2581
+ this.cx = cx;
2582
+ this.cy = cy;
2583
+ this.radius = radius;
2584
+ this.sideCount = sideCount;
1805
2585
  }
1806
2586
  }
1807
2587
 
1808
2588
  class QuadraticBezierCurve extends Curve {
1809
- constructor(start = new Vector2(), control = new Vector2(), end = new Vector2()) {
2589
+ constructor(p1, cp, p2) {
1810
2590
  super();
1811
- this.start = start;
1812
- this.control = control;
1813
- this.end = end;
2591
+ this.p1 = p1;
2592
+ this.cp = cp;
2593
+ this.p2 = p2;
2594
+ }
2595
+ static from(p1x, p1y, cpx, cpy, p2x, p2y) {
2596
+ return new QuadraticBezierCurve(
2597
+ new Vector2(p1x, p1y),
2598
+ new Vector2(cpx, cpy),
2599
+ new Vector2(p2x, p2y)
2600
+ );
1814
2601
  }
1815
2602
  getPoint(t, output = new Vector2()) {
1816
- const { start, control, end } = this;
2603
+ const { p1, cp, p2 } = this;
1817
2604
  output.set(
1818
- quadraticBezier(t, start.x, control.x, end.x),
1819
- quadraticBezier(t, start.y, control.y, end.y)
2605
+ quadraticBezier(t, p1.x, cp.x, p2.x),
2606
+ quadraticBezier(t, p1.y, cp.y, p2.y)
1820
2607
  );
1821
2608
  return output;
1822
2609
  }
1823
- getControlPoints() {
1824
- return [
1825
- this.start,
1826
- this.control,
1827
- this.end
1828
- ];
2610
+ getControlPointRefs() {
2611
+ return [this.p1, this.cp, this.p2];
2612
+ }
2613
+ getAdaptivePointArray(output = []) {
2614
+ return getAdaptiveQuadraticBezierCurvePoints(
2615
+ this.p1.x,
2616
+ this.p1.y,
2617
+ this.cp.x,
2618
+ this.cp.y,
2619
+ this.p2.x,
2620
+ this.p2.y,
2621
+ 0.5,
2622
+ output
2623
+ );
1829
2624
  }
1830
2625
  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);
2626
+ const { p1, cp, p2 } = this;
2627
+ const x1 = 0.5 * (p1.x + cp.x);
2628
+ const y1 = 0.5 * (p1.y + cp.y);
2629
+ const x2 = 0.5 * (p1.x + p2.x);
2630
+ const y2 = 0.5 * (p1.y + p2.y);
2631
+ min.x = Math.min(min.x, p1.x, p2.x, x1, x2);
2632
+ min.y = Math.min(min.y, p1.y, p2.y, y1, y2);
2633
+ max.x = Math.max(max.x, p1.x, p2.x, x1, x2);
2634
+ max.y = Math.max(max.y, p1.y, p2.y, y1, y2);
1840
2635
  return { min, max };
1841
2636
  }
1842
2637
  toCommands() {
1843
- const { start, control, end } = this;
2638
+ const { p1, cp, p2 } = this;
1844
2639
  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 }
2640
+ { type: "M", x: p1.x, y: p1.y },
2641
+ { type: "Q", x1: cp.x, y1: cp.y, x: p2.x, y: p2.y }
1847
2642
  ];
1848
2643
  }
1849
2644
  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);
2645
+ const { p1, cp, p2 } = this;
2646
+ ctx.lineTo(p1.x, p1.y);
2647
+ ctx.quadraticCurveTo(cp.x, cp.y, p2.x, p2.y);
1853
2648
  return this;
1854
2649
  }
1855
2650
  copy(source) {
1856
2651
  super.copy(source);
1857
- this.start.copy(source.start);
1858
- this.control.copy(source.control);
1859
- this.end.copy(source.end);
2652
+ this.p1.copy(source.p1);
2653
+ this.cp.copy(source.cp);
2654
+ this.p2.copy(source.p2);
1860
2655
  return this;
1861
2656
  }
1862
2657
  }
1863
2658
 
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;
2659
+ class RectangleCurve extends PloygonCurve {
2660
+ constructor(x = 0, y = 0, width = 0, height = 0) {
1892
2661
  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)
2662
+ new Vector2(x, y),
2663
+ new Vector2(x + width, y),
2664
+ new Vector2(x + width, y + height),
2665
+ new Vector2(x, y + height)
1897
2666
  ];
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());
2667
+ super([
2668
+ new LineCurve(points[0], points[1]),
2669
+ new LineCurve(points[1], points[2]),
2670
+ new LineCurve(points[2], points[3]),
2671
+ new LineCurve(points[3], points[0])
2672
+ ]);
2673
+ this.x = x;
2674
+ this.y = y;
2675
+ this.width = width;
2676
+ this.height = height;
1937
2677
  }
1938
- getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
1939
- this.curves.forEach((curve) => curve.getMinMax(min, max));
1940
- return { min, max };
2678
+ drawTo(ctx) {
2679
+ ctx.rect(this.x, this.y, this.width, this.height);
2680
+ return this;
2681
+ }
2682
+ fillTriangulate(options = {}) {
2683
+ let {
2684
+ vertices = [],
2685
+ indices = [],
2686
+ verticesStride = 2,
2687
+ verticesOffset = 0,
2688
+ indicesOffset = 0
2689
+ } = options;
2690
+ const { x, y, width, height } = this;
2691
+ const points = [
2692
+ x,
2693
+ y,
2694
+ x + width,
2695
+ y,
2696
+ x + width,
2697
+ y + height,
2698
+ x,
2699
+ y + height
2700
+ ];
2701
+ let count = 0;
2702
+ verticesOffset *= verticesStride;
2703
+ vertices[verticesOffset + count] = points[0];
2704
+ vertices[verticesOffset + count + 1] = points[1];
2705
+ count += verticesStride;
2706
+ vertices[verticesOffset + count] = points[2];
2707
+ vertices[verticesOffset + count + 1] = points[3];
2708
+ count += verticesStride;
2709
+ vertices[verticesOffset + count] = points[6];
2710
+ vertices[verticesOffset + count + 1] = points[7];
2711
+ count += verticesStride;
2712
+ vertices[verticesOffset + count] = points[4];
2713
+ vertices[verticesOffset + count + 1] = points[5];
2714
+ count += verticesStride;
2715
+ const verticesIndex = verticesOffset / verticesStride;
2716
+ indices[indicesOffset++] = verticesIndex;
2717
+ indices[indicesOffset++] = verticesIndex + 1;
2718
+ indices[indicesOffset++] = verticesIndex + 2;
2719
+ indices[indicesOffset++] = verticesIndex + 1;
2720
+ indices[indicesOffset++] = verticesIndex + 3;
2721
+ indices[indicesOffset++] = verticesIndex + 2;
2722
+ return { vertices, indices };
1941
2723
  }
1942
- toCommands() {
1943
- return this.curves.flatMap((curve) => curve.toCommands());
2724
+ }
2725
+
2726
+ class RoundRectangleCurve extends RoundCurve {
2727
+ constructor(x = 0, y = 0, width = 1, height = 1, radius = 1) {
2728
+ const halfWidth = width / 2;
2729
+ const halfHeight = height / 2;
2730
+ const cx = x + halfWidth;
2731
+ const cy = y + halfHeight;
2732
+ const rx = Math.max(0, Math.min(radius, Math.min(halfWidth, halfHeight)));
2733
+ const ry = rx;
2734
+ const dx = halfWidth - rx;
2735
+ const dy = halfHeight - ry;
2736
+ super(
2737
+ new Vector2(cx, cy),
2738
+ new Vector2(rx, ry),
2739
+ new Vector2(dx, dy)
2740
+ );
2741
+ this.x = x;
2742
+ this.y = y;
2743
+ this.width = width;
2744
+ this.height = height;
2745
+ this.radius = radius;
1944
2746
  }
1945
2747
  drawTo(ctx) {
1946
- this.curves.forEach((curve) => curve.drawTo(ctx));
2748
+ const { x, y, width, height, radius } = this;
2749
+ ctx.roundRect(x, y, width, height, radius);
1947
2750
  return this;
1948
2751
  }
1949
2752
  }
@@ -1968,7 +2771,7 @@ class SplineCurve extends Curve {
1968
2771
  );
1969
2772
  return output;
1970
2773
  }
1971
- getControlPoints() {
2774
+ getControlPointRefs() {
1972
2775
  return this.points;
1973
2776
  }
1974
2777
  copy(source) {
@@ -1981,22 +2784,16 @@ class SplineCurve extends Curve {
1981
2784
  }
1982
2785
  }
1983
2786
 
1984
- class CurvePath extends Curve {
1985
- curves = [];
2787
+ class CurvePath extends CompositeCurve {
1986
2788
  startPoint;
1987
2789
  currentPoint;
1988
2790
  autoClose = false;
1989
- _cacheLengths = [];
1990
2791
  constructor(points) {
1991
2792
  super();
1992
2793
  if (points) {
1993
2794
  this.addPoints(points);
1994
2795
  }
1995
2796
  }
1996
- addCurve(curve) {
1997
- this.curves.push(curve);
1998
- return this;
1999
- }
2000
2797
  addPoints(points) {
2001
2798
  this.moveTo(points[0].x, points[0].y);
2002
2799
  for (let i = 1, len = points.length; i < len; i++) {
@@ -2006,82 +2803,26 @@ class CurvePath extends Curve {
2006
2803
  return this;
2007
2804
  }
2008
2805
  addCommands(commands) {
2009
- addPathCommandsToPath2D(commands, this);
2806
+ svgPathCommandsAddToPath2D(commands, this);
2010
2807
  return this;
2011
2808
  }
2012
2809
  addData(data) {
2013
- this.addCommands(pathDataToPathCommands(data));
2810
+ this.addCommands(svgPathDataToCommands(data));
2014
2811
  return this;
2015
2812
  }
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++;
2813
+ getUnevenPointArray(count = 40, output = []) {
2814
+ super.getUnevenPointArray(count, output);
2815
+ if (this.autoClose && output.length >= 4 && (output[0] !== output[output.length - 2] && output[1] !== output[output.length - 1])) {
2816
+ output.push(output[0], output[1]);
2028
2817
  }
2029
2818
  return output;
2030
2819
  }
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
- }
2820
+ getSpacedPointArray(count = 40, output = []) {
2821
+ super.getSpacedPointArray(count, output);
2822
+ if (this.autoClose && output.length >= 4 && (output[0] !== output[output.length - 2] && output[1] !== output[output.length - 1])) {
2823
+ output.push(output[0], output[1]);
2080
2824
  }
2081
- if (this.autoClose && points.length > 1 && !points[points.length - 1].equals(points[0])) {
2082
- points.push(points[0]);
2083
- }
2084
- return points;
2825
+ return output;
2085
2826
  }
2086
2827
  _setCurrentPoint(point) {
2087
2828
  this.currentPoint = new Vector2(point.x, point.y);
@@ -2090,12 +2831,21 @@ class CurvePath extends Curve {
2090
2831
  }
2091
2832
  return this;
2092
2833
  }
2834
+ _connetLineTo(curve) {
2835
+ if (this.curves.length > 0) {
2836
+ const first = curve.getPoint(0);
2837
+ if (!this.currentPoint || !first.equals(this.currentPoint)) {
2838
+ this.lineTo(first.x, first.y);
2839
+ }
2840
+ }
2841
+ return this;
2842
+ }
2093
2843
  closePath() {
2094
2844
  const start = this.startPoint;
2095
2845
  if (start) {
2096
2846
  const end = this.currentPoint;
2097
2847
  if (end && !start.equals(end)) {
2098
- this.curves.push(new LineCurve(end.clone(), start));
2848
+ this.curves.push(new LineCurve(end, start));
2099
2849
  end.copy(start);
2100
2850
  }
2101
2851
  this.startPoint = void 0;
@@ -2111,9 +2861,11 @@ class CurvePath extends Curve {
2111
2861
  const start = this.currentPoint;
2112
2862
  if (!start?.equals({ x, y })) {
2113
2863
  this.curves.push(
2114
- new LineCurve(
2115
- start?.clone() ?? new Vector2(),
2116
- new Vector2(x, y)
2864
+ LineCurve.from(
2865
+ start?.x ?? 0,
2866
+ start?.y ?? 0,
2867
+ x,
2868
+ y
2117
2869
  )
2118
2870
  );
2119
2871
  }
@@ -2124,11 +2876,15 @@ class CurvePath extends Curve {
2124
2876
  const start = this.currentPoint;
2125
2877
  if (!start?.equals({ x, y })) {
2126
2878
  this.curves.push(
2127
- new CubicBezierCurve(
2128
- start?.clone() ?? new Vector2(),
2129
- new Vector2(cp1x, cp1y),
2130
- new Vector2(cp2x, cp2y),
2131
- new Vector2(x, y)
2879
+ CubicBezierCurve.from(
2880
+ start?.x ?? 0,
2881
+ start?.y ?? 0,
2882
+ cp1x,
2883
+ cp1y,
2884
+ cp2x,
2885
+ cp2y,
2886
+ x,
2887
+ y
2132
2888
  )
2133
2889
  );
2134
2890
  }
@@ -2139,10 +2895,13 @@ class CurvePath extends Curve {
2139
2895
  const start = this.currentPoint;
2140
2896
  if (!start?.equals({ x, y })) {
2141
2897
  this.curves.push(
2142
- new QuadraticBezierCurve(
2143
- start?.clone() ?? new Vector2(),
2144
- new Vector2(cpx, cpy),
2145
- new Vector2(x, y)
2898
+ QuadraticBezierCurve.from(
2899
+ start?.x ?? 0,
2900
+ start?.y ?? 0,
2901
+ cpx,
2902
+ cpy,
2903
+ x,
2904
+ y
2146
2905
  )
2147
2906
  );
2148
2907
  }
@@ -2150,12 +2909,23 @@ class CurvePath extends Curve {
2150
2909
  return this;
2151
2910
  }
2152
2911
  arc(x, y, radius, startAngle, endAngle, counterclockwise) {
2153
- this.ellipse(x, y, radius, radius, 0, startAngle, endAngle, counterclockwise);
2912
+ const curve = new ArcCurve(
2913
+ x,
2914
+ y,
2915
+ radius,
2916
+ startAngle,
2917
+ endAngle,
2918
+ !counterclockwise
2919
+ );
2920
+ this._connetLineTo(curve);
2921
+ this.curves.push(curve);
2922
+ this._setCurrentPoint(curve.getPoint(1));
2154
2923
  return this;
2155
2924
  }
2156
2925
  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);
2926
+ x += this.currentPoint?.x ?? 0;
2927
+ y += this.currentPoint?.y ?? 0;
2928
+ this.arc(x, y, radius, startAngle, endAngle, counterclockwise);
2159
2929
  return this;
2160
2930
  }
2161
2931
  // TODO
@@ -2166,7 +2936,8 @@ class CurvePath extends Curve {
2166
2936
  }
2167
2937
  ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise = true) {
2168
2938
  const curve = new EllipseCurve(
2169
- new Vector2(x, y),
2939
+ x,
2940
+ y,
2170
2941
  radiusX,
2171
2942
  radiusY,
2172
2943
  rotation,
@@ -2174,29 +2945,28 @@ class CurvePath extends Curve {
2174
2945
  endAngle,
2175
2946
  !counterclockwise
2176
2947
  );
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
- }
2948
+ this._connetLineTo(curve);
2183
2949
  this.curves.push(curve);
2184
2950
  this._setCurrentPoint(curve.getPoint(1));
2185
2951
  return this;
2186
2952
  }
2187
2953
  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);
2954
+ x += this.currentPoint?.x ?? 0;
2955
+ y += this.currentPoint?.y ?? 0;
2956
+ this.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
2190
2957
  return this;
2191
2958
  }
2192
- 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
- );
2959
+ rect(x, y, width, height) {
2960
+ const curve = new RectangleCurve(x, y, width, height);
2961
+ this._connetLineTo(curve);
2962
+ this.curves.push(curve);
2963
+ this._setCurrentPoint({ x, y });
2964
+ return this;
2965
+ }
2966
+ roundRect(x, y, width, height, radii) {
2967
+ const curve = new RoundRectangleCurve(x, y, width, height, radii);
2968
+ this._connetLineTo(curve);
2969
+ this.curves.push(curve);
2200
2970
  this._setCurrentPoint({ x, y });
2201
2971
  return this;
2202
2972
  }
@@ -2206,17 +2976,6 @@ class CurvePath extends Curve {
2206
2976
  this._setCurrentPoint(points[points.length - 1]);
2207
2977
  return this;
2208
2978
  }
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
2979
  drawTo(ctx) {
2221
2980
  const point = this.curves[0]?.getPoint(0);
2222
2981
  if (point) {
@@ -2267,20 +3026,22 @@ function getIntersectionPoint(p1, p2, q1, q2) {
2267
3026
  );
2268
3027
  }
2269
3028
 
2270
- class Path2D {
2271
- currentPath = new CurvePath();
2272
- paths = [this.currentPath];
3029
+ class Path2D extends CompositeCurve {
3030
+ currentCurve = new CurvePath();
2273
3031
  style;
2274
3032
  get startPoint() {
2275
- return this.currentPath.startPoint;
3033
+ return this.currentCurve.startPoint;
2276
3034
  }
2277
3035
  get currentPoint() {
2278
- return this.currentPath.currentPoint;
3036
+ return this.currentCurve.currentPoint;
2279
3037
  }
2280
3038
  get strokeWidth() {
2281
3039
  return this.style.strokeWidth ?? ((this.style.stroke ?? "none") === "none" ? 0 : 1);
2282
3040
  }
2283
3041
  constructor(path, style = {}) {
3042
+ super();
3043
+ this.curves.push(this.currentCurve);
3044
+ this.style = style;
2284
3045
  if (path) {
2285
3046
  if (path instanceof Path2D) {
2286
3047
  this.addPath(path);
@@ -2290,99 +3051,94 @@ class Path2D {
2290
3051
  this.addData(path);
2291
3052
  }
2292
3053
  }
2293
- this.style = style;
2294
3054
  }
2295
3055
  addPath(path) {
2296
3056
  if (path instanceof Path2D) {
2297
- this.paths.push(...path.paths.map((v) => v.clone()));
3057
+ this.curves.push(...path.curves.map((v) => v.clone()));
2298
3058
  } else {
2299
- this.paths.push(path);
3059
+ this.curves.push(path);
2300
3060
  }
2301
3061
  return this;
2302
3062
  }
2303
3063
  closePath() {
2304
3064
  const startPoint = this.startPoint;
2305
3065
  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);
3066
+ this.currentCurve.closePath();
3067
+ if (this.currentCurve.curves.length > 0) {
3068
+ this.currentCurve = new CurvePath().moveTo(startPoint.x, startPoint.y);
3069
+ this.curves.push(this.currentCurve);
2310
3070
  }
2311
3071
  }
2312
3072
  return this;
2313
3073
  }
2314
3074
  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);
3075
+ if (!this.currentCurve.currentPoint?.equals({ x, y })) {
3076
+ if (this.currentCurve.curves.length) {
3077
+ this.currentCurve = new CurvePath();
3078
+ this.curves.push(this.currentCurve);
2322
3079
  }
3080
+ this.currentCurve.moveTo(x, y);
2323
3081
  }
2324
3082
  return this;
2325
3083
  }
2326
3084
  lineTo(x, y) {
2327
- this.currentPath.lineTo(x, y);
3085
+ this.currentCurve.lineTo(x, y);
2328
3086
  return this;
2329
3087
  }
2330
3088
  bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
2331
- this.currentPath.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
3089
+ this.currentCurve.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
2332
3090
  return this;
2333
3091
  }
2334
3092
  quadraticCurveTo(cpx, cpy, x, y) {
2335
- this.currentPath.quadraticCurveTo(cpx, cpy, x, y);
3093
+ this.currentCurve.quadraticCurveTo(cpx, cpy, x, y);
2336
3094
  return this;
2337
3095
  }
2338
3096
  arc(x, y, radius, startAngle, endAngle, counterclockwise) {
2339
- this.currentPath.arc(x, y, radius, startAngle, endAngle, counterclockwise);
3097
+ this.currentCurve.arc(x, y, radius, startAngle, endAngle, counterclockwise);
2340
3098
  return this;
2341
3099
  }
2342
3100
  arcTo(x1, y1, x2, y2, radius) {
2343
- this.currentPath.arcTo(x1, y1, x2, y2, radius);
3101
+ this.currentCurve.arcTo(x1, y1, x2, y2, radius);
2344
3102
  return this;
2345
3103
  }
2346
3104
  ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
2347
- this.currentPath.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
3105
+ this.currentCurve.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
3106
+ return this;
3107
+ }
3108
+ rect(x, y, width, height) {
3109
+ this.currentCurve.rect(x, y, width, height);
2348
3110
  return this;
2349
3111
  }
2350
- rect(x, y, w, h) {
2351
- this.currentPath.rect(x, y, w, h);
3112
+ roundRect(x, y, width, height, radii) {
3113
+ this.currentCurve.roundRect(x, y, width, height, radii);
2352
3114
  return this;
2353
3115
  }
2354
3116
  addCommands(commands) {
2355
- addPathCommandsToPath2D(commands, this);
3117
+ svgPathCommandsAddToPath2D(commands, this);
2356
3118
  return this;
2357
3119
  }
2358
3120
  addData(data) {
2359
- this.addCommands(pathDataToPathCommands(data));
3121
+ this.addCommands(svgPathDataToCommands(data));
2360
3122
  return this;
2361
3123
  }
2362
3124
  splineThru(points) {
2363
- this.currentPath.splineThru(points);
3125
+ this.currentCurve.splineThru(points);
2364
3126
  return this;
2365
3127
  }
2366
- getControlPoints() {
2367
- return this.paths.flatMap((path) => path.getControlPoints());
2368
- }
2369
- getCurves() {
2370
- return this.paths.flatMap((path) => path.curves);
2371
- }
2372
3128
  scale(sx, sy = sx, target = { x: 0, y: 0 }) {
2373
- this.getControlPoints().forEach((point) => {
3129
+ this.getControlPointRefs().forEach((point) => {
2374
3130
  point.scale(sx, sy, target);
2375
3131
  });
2376
3132
  return this;
2377
3133
  }
2378
3134
  skew(ax, ay = 0, target = { x: 0, y: 0 }) {
2379
- this.getControlPoints().forEach((point) => {
3135
+ this.getControlPointRefs().forEach((point) => {
2380
3136
  point.skew(ax, ay, target);
2381
3137
  });
2382
3138
  return this;
2383
3139
  }
2384
3140
  rotate(a, target = { x: 0, y: 0 }) {
2385
- this.getControlPoints().forEach((point) => {
3141
+ this.getControlPointRefs().forEach((point) => {
2386
3142
  point.rotate(a, target);
2387
3143
  });
2388
3144
  return this;
@@ -2391,12 +3147,14 @@ class Path2D {
2391
3147
  if (b === 0) {
2392
3148
  return this;
2393
3149
  }
2394
- const curves = this.getCurves();
3150
+ const curves = this.curves;
2395
3151
  const _list = [];
2396
3152
  const _isClockwise = [];
2397
3153
  const _points = [];
2398
3154
  curves.forEach((curve, index) => {
2399
- const points = curve.getControlPoints();
3155
+ if (!curve.getLength())
3156
+ return;
3157
+ const points = curve.getControlPointRefs();
2400
3158
  const isClockwise = curve.isClockwise();
2401
3159
  _points[index] = points;
2402
3160
  _isClockwise[index] = isClockwise;
@@ -2418,6 +3176,8 @@ class Path2D {
2418
3176
  });
2419
3177
  });
2420
3178
  curves.forEach((curve, index) => {
3179
+ if (!curve.getLength())
3180
+ return;
2421
3181
  const isClockwise = _isClockwise[index];
2422
3182
  const points = _points[index];
2423
3183
  points.forEach((point) => {
@@ -2444,20 +3204,20 @@ class Path2D {
2444
3204
  });
2445
3205
  return this;
2446
3206
  }
2447
- matrix(matrix) {
2448
- this.getCurves().forEach((curve) => curve.matrix(matrix));
3207
+ applyTransform(transform) {
3208
+ this.curves.forEach((curve) => curve.applyTransform(transform));
2449
3209
  return this;
2450
3210
  }
2451
3211
  getMinMax(min = Vector2.MAX, max = Vector2.MIN, withStyle = true) {
2452
3212
  const strokeWidth = this.strokeWidth;
2453
- this.getCurves().forEach((curve) => {
3213
+ this.curves.forEach((curve) => {
2454
3214
  curve.getMinMax(min, max);
2455
3215
  if (withStyle) {
2456
3216
  if (strokeWidth > 1) {
2457
3217
  const halfStrokeWidth = strokeWidth / 2;
2458
3218
  const isClockwise = curve.isClockwise();
2459
3219
  const points = [];
2460
- for (let t = 0; t <= 1; t += 1 / curve.arcLengthDivisions) {
3220
+ for (let t = 0; t <= 1; t += 1 / curve.arcLengthDivision) {
2461
3221
  const point = curve.getPoint(t);
2462
3222
  const normal = curve.getNormal(t);
2463
3223
  const dist1 = normal.clone().scale(isClockwise ? halfStrokeWidth : -halfStrokeWidth);
@@ -2490,7 +3250,7 @@ class Path2D {
2490
3250
  ctx.beginPath();
2491
3251
  ctx.save();
2492
3252
  setCanvasContext(ctx, style);
2493
- this.paths.forEach((path) => {
3253
+ this.curves.forEach((path) => {
2494
3254
  path.drawTo(ctx);
2495
3255
  });
2496
3256
  if (fill !== "none") {
@@ -2508,9 +3268,8 @@ class Path2D {
2508
3268
  ctx.beginPath();
2509
3269
  ctx.save();
2510
3270
  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);
3271
+ this.getControlPointRefs().forEach((point) => {
3272
+ drawPoint(ctx, point.x, point.y, { radius: 4 });
2514
3273
  });
2515
3274
  if (fill !== "none") {
2516
3275
  ctx.fill();
@@ -2522,10 +3281,10 @@ class Path2D {
2522
3281
  return this;
2523
3282
  }
2524
3283
  toCommands() {
2525
- return this.paths.flatMap((path) => path.toCommands());
3284
+ return this.curves.flatMap((v) => v.toCommands());
2526
3285
  }
2527
3286
  toData() {
2528
- return this.paths.map((path) => path.toData()).join(" ");
3287
+ return this.curves.filter((v) => v.curves.length).map((v) => v.toData()).join(" ");
2529
3288
  }
2530
3289
  toSVGPathString() {
2531
3290
  const style = {
@@ -2550,10 +3309,34 @@ class Path2D {
2550
3309
  }
2551
3310
  return `<path d="${this.toData()}" style="${cssText}"></path>`;
2552
3311
  }
3312
+ copy(source) {
3313
+ this.currentCurve = source.currentCurve.clone();
3314
+ this.curves = source.curves.map((path) => path.clone());
3315
+ this.style = { ...source.style };
3316
+ return this;
3317
+ }
3318
+ clone() {
3319
+ return new this.constructor().copy(this);
3320
+ }
3321
+ }
3322
+
3323
+ class Path2DSet {
3324
+ constructor(paths = []) {
3325
+ this.paths = paths;
3326
+ }
3327
+ getBoundingBox(withStyle = true) {
3328
+ if (!this.paths.length) {
3329
+ return void 0;
3330
+ }
3331
+ const min = Vector2.MAX;
3332
+ const max = Vector2.MIN;
3333
+ this.paths.forEach((path) => path.getMinMax(min, max, withStyle));
3334
+ return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
3335
+ }
2553
3336
  toSVGString() {
2554
3337
  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>`;
3338
+ const content = this.paths.map((path) => path.toSVGPathString()).join("");
3339
+ return `<svg viewBox="${x} ${y} ${width} ${height}" width="${width}px" height="${height}px" xmlns="http://www.w3.org/2000/svg">${content}</svg>`;
2557
3340
  }
2558
3341
  toSVGUrl() {
2559
3342
  return `data:image/svg+xml;base64,${btoa(this.toSVGString())}`;
@@ -2573,18 +3356,38 @@ class Path2D {
2573
3356
  if (ctx) {
2574
3357
  ctx.scale(pixelRatio, pixelRatio);
2575
3358
  ctx.translate(-left, -top);
2576
- this.drawTo(ctx, style);
3359
+ this.paths.forEach((path) => {
3360
+ path.drawTo(ctx, style);
3361
+ });
2577
3362
  }
2578
3363
  return canvas;
2579
3364
  }
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);
3365
+ }
3366
+
3367
+ const dataUri = "data:image/svg+xml;";
3368
+ const base64DataUri = `${dataUri}base64,`;
3369
+ const utf8DataUri = `${dataUri}charset=utf8,`;
3370
+ function svgToDom(svg) {
3371
+ if (typeof svg === "string") {
3372
+ let xml;
3373
+ if (svg.startsWith(base64DataUri)) {
3374
+ svg = svg.substring(base64DataUri.length, svg.length);
3375
+ xml = atob(svg);
3376
+ } else if (svg.startsWith(utf8DataUri)) {
3377
+ svg = svg.substring(utf8DataUri.length, svg.length);
3378
+ xml = decodeURIComponent(svg);
3379
+ } else {
3380
+ xml = svg;
3381
+ }
3382
+ const doc = new DOMParser().parseFromString(xml, "text/xml");
3383
+ const error = doc.querySelector("parsererror");
3384
+ if (error) {
3385
+ throw new Error(`${error.textContent ?? "parser error"}
3386
+ ${xml}`);
3387
+ }
3388
+ return doc.documentElement;
3389
+ } else {
3390
+ return svg;
2588
3391
  }
2589
3392
  }
2590
3393
 
@@ -3065,7 +3868,7 @@ function parseNode(node, style, paths = [], stylesheets = {}) {
3065
3868
  const transformStack = [];
3066
3869
  const transform = getNodeTransform(node, currentTransform, transformStack);
3067
3870
  if (path) {
3068
- path.matrix(currentTransform);
3871
+ path.applyTransform(currentTransform);
3069
3872
  paths.push(path);
3070
3873
  path.style = style;
3071
3874
  }
@@ -3087,73 +3890,8 @@ function parseNode(node, style, paths = [], stylesheets = {}) {
3087
3890
  return paths;
3088
3891
  }
3089
3892
 
3090
- const dataUri = "data:image/svg+xml;";
3091
- const base64DataUri = `${dataUri}base64,`;
3092
- const utf8DataUri = `${dataUri}charset=utf8,`;
3093
- function parseSVGToDOM(svg) {
3094
- if (typeof svg === "string") {
3095
- let xml;
3096
- if (svg.startsWith(base64DataUri)) {
3097
- svg = svg.substring(base64DataUri.length, svg.length);
3098
- xml = atob(svg);
3099
- } else if (svg.startsWith(utf8DataUri)) {
3100
- svg = svg.substring(utf8DataUri.length, svg.length);
3101
- xml = decodeURIComponent(svg);
3102
- } else {
3103
- xml = svg;
3104
- }
3105
- const doc = new DOMParser().parseFromString(xml, "text/xml");
3106
- const error = doc.querySelector("parsererror");
3107
- if (error) {
3108
- throw new Error(`${error.textContent ?? "parser error"}
3109
- ${xml}`);
3110
- }
3111
- return doc.documentElement;
3112
- } else {
3113
- return svg;
3114
- }
3115
- }
3116
- 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;
3893
+ function svgToPath2DSet(svg) {
3894
+ return new Path2DSet(parseNode(svgToDom(svg), {}));
3157
3895
  }
3158
3896
 
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 };
3897
+ export { ArcCurve, BoundingBox, CompositeCurve, CubicBezierCurve, Curve, CurvePath, EllipseCurve, EquilateralPloygonCurve, LineCurve, Matrix3, Path2D, Path2DSet, PloygonCurve, QuadraticBezierCurve, RectangleCurve, RoundRectangleCurve, SplineCurve, Vector2, catmullRom, cubicBezier, drawPoint, fillTriangulate, getAdaptiveCubicBezierCurvePoints, getAdaptiveQuadraticBezierCurvePoints, parseArcCommand, parsePathDataArgs, quadraticBezier, setCanvasContext, strokeTriangulate, svgPathCommandsAddToPath2D, svgPathCommandsToData, svgPathDataToCommands, svgToDom, svgToPath2DSet };