tex2typst 0.3.27 → 0.3.28

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.d.ts CHANGED
@@ -12,7 +12,6 @@ export interface Tex2TypstOptions {
12
12
  fracToSlash?: boolean; /** default is true */
13
13
  inftyToOo?: boolean; /** default is false */
14
14
  optimize?: boolean; /** default is true */
15
- nonAsciiWrapper?: string; /** default is "" */
16
15
  customTexMacros?: { [key: string]: string; };
17
16
  }
18
17
 
package/dist/index.js CHANGED
@@ -278,6 +278,9 @@ function array_split(array, sep) {
278
278
  res.push(current_slice);
279
279
  return res;
280
280
  }
281
+ function array_join(arrays, sep) {
282
+ return arrays.flatMap((arr, i) => i !== arrays.length - 1 ? [...arr, sep] : arr);
283
+ }
281
284
  function array_intersperse(array, sep) {
282
285
  return array.flatMap((x, i) => i !== array.length - 1 ? [x, sep] : [x]);
283
286
  }
@@ -564,7 +567,12 @@ var TEX_UNARY_COMMANDS = [
564
567
  "overrightarrow",
565
568
  "hspace",
566
569
  "substack",
567
- "set"
570
+ "set",
571
+ "displaylines",
572
+ "mathinner",
573
+ "mathrel",
574
+ "mathbin",
575
+ "mathop"
568
576
  ];
569
577
  var TEX_BINARY_COMMANDS = [
570
578
  "frac",
@@ -708,7 +716,7 @@ function eat_parenthesis(tokens, start) {
708
716
  const firstToken = tokens[start];
709
717
  if (firstToken.type === 1 /* ELEMENT */ && ["(", ")", "[", "]", "|", "\\{", "\\}", ".", "\\|"].includes(firstToken.value)) {
710
718
  return firstToken;
711
- } else if (firstToken.type === 2 /* COMMAND */ && ["lfloor", "rfloor", "lceil", "rceil", "langle", "rangle"].includes(firstToken.value.slice(1))) {
719
+ } else if (firstToken.type === 2 /* COMMAND */ && ["lfloor", "rfloor", "lceil", "rceil", "langle", "rangle", "lparen", "rparen", "lbrace", "rbrace"].includes(firstToken.value.slice(1))) {
712
720
  return firstToken;
713
721
  } else {
714
722
  return null;
@@ -721,44 +729,32 @@ function eat_primes(tokens, start) {
721
729
  }
722
730
  return pos - start;
723
731
  }
724
- function find_closing_match(tokens, start, leftToken, rightToken) {
725
- assert(tokens[start].eq(leftToken));
726
- let count = 1;
727
- let pos = start + 1;
728
- while (count > 0) {
729
- if (pos >= tokens.length) {
730
- return -1;
731
- }
732
- if (tokens[pos].eq(leftToken)) {
733
- count += 1;
734
- } else if (tokens[pos].eq(rightToken)) {
735
- count -= 1;
736
- }
737
- pos += 1;
738
- }
739
- return pos - 1;
740
- }
741
732
  var LEFT_COMMAND = new TexToken(2 /* COMMAND */, "\\left");
742
733
  var RIGHT_COMMAND = new TexToken(2 /* COMMAND */, "\\right");
743
- function find_closing_right_command(tokens, start) {
744
- return find_closing_match(tokens, start, LEFT_COMMAND, RIGHT_COMMAND);
745
- }
746
734
  var BEGIN_COMMAND = new TexToken(2 /* COMMAND */, "\\begin");
747
735
  var END_COMMAND = new TexToken(2 /* COMMAND */, "\\end");
748
- function find_closing_end_command(tokens, start) {
749
- return find_closing_match(tokens, start, BEGIN_COMMAND, END_COMMAND);
750
- }
751
- var LatexParserError = class extends Error {
736
+ var CONTROL_LINEBREAK = new TexToken(7 /* CONTROL */, "\\\\");
737
+ var LatexParserError = class _LatexParserError extends Error {
752
738
  constructor(message) {
753
739
  super(message);
754
740
  this.name = "LatexParserError";
755
741
  }
742
+ static UNMATCHED_LEFT_BRACE = new _LatexParserError("Unmatched '\\{'");
743
+ static UNMATCHED_RIGHT_BRACE = new _LatexParserError("Unmatched '\\}'");
744
+ static UNMATCHED_LEFT_BRACKET = new _LatexParserError("Unmatched '\\['");
745
+ static UNMATCHED_RIGHT_BRACKET = new _LatexParserError("Unmatched '\\]'");
746
+ static UNMATCHED_COMMAND_BEGIN = new _LatexParserError("Unmatched '\\begin'");
747
+ static UNMATCHED_COMMAND_END = new _LatexParserError("Unmatched '\\end'");
748
+ static UNMATCHED_COMMAND_LEFT = new _LatexParserError("Unmatched '\\left'");
749
+ static UNMATCHED_COMMAND_RIGHT = new _LatexParserError("Unmatched '\\right'");
756
750
  };
757
751
  var SUB_SYMBOL = new TexToken(7 /* CONTROL */, "_");
758
752
  var SUP_SYMBOL = new TexToken(7 /* CONTROL */, "^");
759
753
  var LatexParser = class {
760
754
  space_sensitive;
761
755
  newline_sensitive;
756
+ // how many levels of \begin{...} \end{...} are we currently in
757
+ alignmentDepth = 0;
762
758
  constructor(space_sensitive = false, newline_sensitive = true) {
763
759
  this.space_sensitive = space_sensitive;
764
760
  this.newline_sensitive = newline_sensitive;
@@ -767,22 +763,30 @@ var LatexParser = class {
767
763
  const token_displaystyle = new TexToken(2 /* COMMAND */, "\\displaystyle");
768
764
  const idx = array_find(tokens, token_displaystyle);
769
765
  if (idx === -1) {
770
- const [tree, _] = this.parseGroup(tokens, 0, tokens.length);
771
- return tree;
766
+ return this.parseGroup(tokens.slice(0));
772
767
  } else if (idx === 0) {
773
- const [tree, _] = this.parseGroup(tokens, 1, tokens.length);
768
+ const tree = this.parseGroup(tokens.slice(1));
774
769
  return new TexFuncCall(token_displaystyle, [tree]);
775
770
  } else {
776
- const [tree1, _1] = this.parseGroup(tokens, 0, idx);
777
- const [tree2, _2] = this.parseGroup(tokens, idx + 1, tokens.length);
771
+ const tree1 = this.parseGroup(tokens.slice(0, idx));
772
+ const tree2 = this.parseGroup(tokens.slice(idx + 1, tokens.length));
778
773
  const display = new TexFuncCall(token_displaystyle, [tree2]);
779
774
  return new TexGroup([tree1, display]);
780
775
  }
781
776
  }
782
- parseGroup(tokens, start, end) {
777
+ parseGroup(tokens) {
778
+ const [tree, _] = this.parseClosure(tokens, 0, null);
779
+ return tree;
780
+ }
781
+ // return pos: (position of closingToken) + 1
782
+ // pos will be -1 if closingToken is not found
783
+ parseClosure(tokens, start, closingToken) {
783
784
  const results = [];
784
785
  let pos = start;
785
- while (pos < end) {
786
+ while (pos < tokens.length) {
787
+ if (closingToken !== null && tokens[pos].eq(closingToken)) {
788
+ break;
789
+ }
786
790
  const [res, newPos] = this.parseNextExpr(tokens, pos);
787
791
  pos = newPos;
788
792
  if (res.head.type === 5 /* SPACE */ || res.head.type === 6 /* NEWLINE */) {
@@ -793,18 +797,18 @@ var LatexParser = class {
793
797
  continue;
794
798
  }
795
799
  }
796
- if (res.head.eq(new TexToken(7 /* CONTROL */, "&"))) {
797
- throw new LatexParserError("Unexpected & outside of an alignment");
798
- }
799
800
  results.push(res);
800
801
  }
802
+ if (pos >= tokens.length && closingToken !== null) {
803
+ return [EMPTY_NODE, -1];
804
+ }
801
805
  let node;
802
806
  if (results.length === 1) {
803
807
  node = results[0];
804
808
  } else {
805
809
  node = new TexGroup(results);
806
810
  }
807
- return [node, end + 1];
811
+ return [node, pos + 1];
808
812
  }
809
813
  parseNextExpr(tokens, start) {
810
814
  let [base, pos] = this.parseNextExprWithoutSupSub(tokens, start);
@@ -813,25 +817,28 @@ var LatexParser = class {
813
817
  let num_prime = 0;
814
818
  num_prime += eat_primes(tokens, pos);
815
819
  pos += num_prime;
816
- if (pos < tokens.length && tokens[pos].eq(SUB_SYMBOL)) {
817
- [sub, pos] = this.parseNextExprWithoutSupSub(tokens, pos + 1);
818
- num_prime += eat_primes(tokens, pos);
819
- pos += num_prime;
820
- if (pos < tokens.length && tokens[pos].eq(SUP_SYMBOL)) {
820
+ if (pos < tokens.length) {
821
+ const next_token = tokens[pos];
822
+ if (next_token.eq(SUB_SYMBOL)) {
823
+ [sub, pos] = this.parseNextExprWithoutSupSub(tokens, pos + 1);
824
+ num_prime += eat_primes(tokens, pos);
825
+ pos += num_prime;
826
+ if (pos < tokens.length && tokens[pos].eq(SUP_SYMBOL)) {
827
+ [sup, pos] = this.parseNextExprWithoutSupSub(tokens, pos + 1);
828
+ if (eat_primes(tokens, pos) > 0) {
829
+ throw new LatexParserError("Double superscript");
830
+ }
831
+ }
832
+ } else if (next_token.eq(SUP_SYMBOL)) {
821
833
  [sup, pos] = this.parseNextExprWithoutSupSub(tokens, pos + 1);
822
834
  if (eat_primes(tokens, pos) > 0) {
823
835
  throw new LatexParserError("Double superscript");
824
836
  }
825
- }
826
- } else if (pos < tokens.length && tokens[pos].eq(SUP_SYMBOL)) {
827
- [sup, pos] = this.parseNextExprWithoutSupSub(tokens, pos + 1);
828
- if (eat_primes(tokens, pos) > 0) {
829
- throw new LatexParserError("Double superscript");
830
- }
831
- if (pos < tokens.length && tokens[pos].eq(SUB_SYMBOL)) {
832
- [sub, pos] = this.parseNextExprWithoutSupSub(tokens, pos + 1);
833
- if (eat_primes(tokens, pos) > 0) {
834
- throw new LatexParserError("Double superscript");
837
+ if (pos < tokens.length && tokens[pos].eq(SUB_SYMBOL)) {
838
+ [sub, pos] = this.parseNextExprWithoutSupSub(tokens, pos + 1);
839
+ if (eat_primes(tokens, pos) > 0) {
840
+ throw new LatexParserError("Double superscript");
841
+ }
835
842
  }
836
843
  }
837
844
  }
@@ -859,7 +866,7 @@ var LatexParser = class {
859
866
  }
860
867
  parseNextExprWithoutSupSub(tokens, start) {
861
868
  if (start >= tokens.length) {
862
- return [EMPTY_NODE, start];
869
+ throw new LatexParserError("Unexpected end of input");
863
870
  }
864
871
  const firstToken = tokens[start];
865
872
  switch (firstToken.type) {
@@ -876,8 +883,12 @@ var LatexParser = class {
876
883
  }
877
884
  if (firstToken.eq(BEGIN_COMMAND)) {
878
885
  return this.parseBeginEndExpr(tokens, start);
886
+ } else if (firstToken.eq(END_COMMAND)) {
887
+ throw LatexParserError.UNMATCHED_COMMAND_END;
879
888
  } else if (firstToken.eq(LEFT_COMMAND)) {
880
889
  return this.parseLeftRightExpr(tokens, start);
890
+ } else if (firstToken.eq(RIGHT_COMMAND)) {
891
+ throw LatexParserError.UNMATCHED_COMMAND_RIGHT;
881
892
  } else {
882
893
  return this.parseCommandExpr(tokens, start);
883
894
  }
@@ -885,13 +896,13 @@ var LatexParser = class {
885
896
  const controlChar = firstToken.value;
886
897
  switch (controlChar) {
887
898
  case "{":
888
- const posClosingBracket = find_closing_match(tokens, start, LEFT_CURLY_BRACKET, RIGHT_CURLY_BRACKET);
889
- if (posClosingBracket === -1) {
890
- throw new LatexParserError("Unmatched '{'");
899
+ const [group, newPos] = this.parseClosure(tokens, start + 1, RIGHT_CURLY_BRACKET);
900
+ if (newPos === -1) {
901
+ throw LatexParserError.UNMATCHED_LEFT_BRACE;
891
902
  }
892
- return this.parseGroup(tokens, start + 1, posClosingBracket);
903
+ return [group, newPos];
893
904
  case "}":
894
- throw new LatexParserError("Unmatched '}'");
905
+ throw LatexParserError.UNMATCHED_RIGHT_BRACE;
895
906
  case "\\\\":
896
907
  case "\\!":
897
908
  case "\\,":
@@ -904,6 +915,9 @@ var LatexParser = class {
904
915
  case "^":
905
916
  return [EMPTY_NODE, start];
906
917
  case "&":
918
+ if (this.alignmentDepth <= 0) {
919
+ throw new LatexParserError("Unexpected & outside of an alignment");
920
+ }
907
921
  return [firstToken.toNode(), start + 1];
908
922
  default:
909
923
  throw new LatexParserError("Unknown control sequence");
@@ -917,9 +931,6 @@ var LatexParser = class {
917
931
  const command_token = tokens[start];
918
932
  const command = command_token.value;
919
933
  let pos = start + 1;
920
- if (["left", "right", "begin", "end"].includes(command.slice(1))) {
921
- throw new LatexParserError("Unexpected command: " + command);
922
- }
923
934
  const paramNum = get_command_param_num(command.slice(1));
924
935
  switch (paramNum) {
925
936
  case 0:
@@ -929,13 +940,11 @@ var LatexParser = class {
929
940
  throw new LatexParserError("Expecting argument for " + command);
930
941
  }
931
942
  if (command === "\\sqrt" && pos < tokens.length && tokens[pos].eq(LEFT_SQUARE_BRACKET)) {
932
- const posLeftSquareBracket = pos;
933
- const posRightSquareBracket = find_closing_match(tokens, pos, LEFT_SQUARE_BRACKET, RIGHT_SQUARE_BRACKET);
934
- if (posRightSquareBracket === -1) {
935
- throw new LatexParserError("No matching right square bracket for [");
943
+ const [exponent, newPos1] = this.parseClosure(tokens, pos + 1, RIGHT_SQUARE_BRACKET);
944
+ if (newPos1 === -1) {
945
+ throw LatexParserError.UNMATCHED_LEFT_BRACKET;
936
946
  }
937
- const [exponent, _] = this.parseGroup(tokens, posLeftSquareBracket + 1, posRightSquareBracket);
938
- const [arg12, newPos2] = this.parseNextArg(tokens, posRightSquareBracket + 1);
947
+ const [arg12, newPos2] = this.parseNextArg(tokens, newPos1);
939
948
  return [new TexFuncCall(command_token, [arg12], exponent), newPos2];
940
949
  } else if (command === "\\text") {
941
950
  if (pos + 2 >= tokens.length) {
@@ -946,6 +955,14 @@ var LatexParser = class {
946
955
  assert(tokens[pos + 2].eq(RIGHT_CURLY_BRACKET));
947
956
  const literal = tokens[pos + 1];
948
957
  return [new TexText(literal), pos + 3];
958
+ } else if (command === "\\displaylines") {
959
+ assert(tokens[pos].eq(LEFT_CURLY_BRACKET));
960
+ const [matrix, newPos2] = this.parseAligned(tokens, pos + 1, RIGHT_CURLY_BRACKET);
961
+ if (newPos2 === -1) {
962
+ throw LatexParserError.UNMATCHED_LEFT_BRACE;
963
+ }
964
+ const group = new TexGroup(array_join(matrix, CONTROL_LINEBREAK.toNode()));
965
+ return [new TexFuncCall(command_token, [group]), newPos2];
949
966
  }
950
967
  let [arg1, newPos] = this.parseNextArg(tokens, pos);
951
968
  return [new TexFuncCall(command_token, [arg1]), newPos];
@@ -987,30 +1004,27 @@ var LatexParser = class {
987
1004
  let pos = start + 1;
988
1005
  pos += eat_whitespaces(tokens, pos).length;
989
1006
  if (pos >= tokens.length) {
990
- throw new LatexParserError("Expecting delimiter after \\left");
1007
+ throw new LatexParserError("Expecting a delimiter after \\left");
991
1008
  }
992
1009
  const leftDelimiter = eat_parenthesis(tokens, pos);
993
1010
  if (leftDelimiter === null) {
994
1011
  throw new LatexParserError("Invalid delimiter after \\left");
995
1012
  }
996
1013
  pos++;
997
- const exprInsideStart = pos;
998
- const idx = find_closing_right_command(tokens, start);
1014
+ const [body, idx] = this.parseClosure(tokens, pos, RIGHT_COMMAND);
999
1015
  if (idx === -1) {
1000
- throw new LatexParserError("No matching \\right");
1016
+ throw LatexParserError.UNMATCHED_COMMAND_LEFT;
1001
1017
  }
1002
- const exprInsideEnd = idx;
1003
- pos = idx + 1;
1018
+ pos = idx;
1004
1019
  pos += eat_whitespaces(tokens, pos).length;
1005
1020
  if (pos >= tokens.length) {
1006
- throw new LatexParserError("Expecting \\right after \\left");
1021
+ throw new LatexParserError("Expecting a delimiter after \\right");
1007
1022
  }
1008
1023
  const rightDelimiter = eat_parenthesis(tokens, pos);
1009
1024
  if (rightDelimiter === null) {
1010
1025
  throw new LatexParserError("Invalid delimiter after \\right");
1011
1026
  }
1012
1027
  pos++;
1013
- const [body, _] = this.parseGroup(tokens, exprInsideStart, exprInsideEnd);
1014
1028
  const left = leftDelimiter.value === "." ? null : leftDelimiter;
1015
1029
  const right = rightDelimiter.value === "." ? null : rightDelimiter;
1016
1030
  const res = new TexLeftRight({ body, left, right });
@@ -1029,37 +1043,36 @@ var LatexParser = class {
1029
1043
  pos += eat_whitespaces(tokens, pos).length;
1030
1044
  [data, pos] = this.parseNextArg(tokens, pos);
1031
1045
  }
1032
- pos += eat_whitespaces(tokens, pos).length;
1033
- const exprInsideStart = pos;
1034
- const endIdx = find_closing_end_command(tokens, start);
1046
+ const [body, endIdx] = this.parseAligned(tokens, pos, END_COMMAND);
1035
1047
  if (endIdx === -1) {
1036
- throw new LatexParserError("No matching \\end");
1048
+ throw LatexParserError.UNMATCHED_COMMAND_BEGIN;
1037
1049
  }
1038
- const exprInsideEnd = endIdx;
1039
- pos = endIdx + 1;
1050
+ pos = endIdx;
1040
1051
  assert(tokens[pos].eq(LEFT_CURLY_BRACKET));
1041
1052
  assert(tokens[pos + 1].type === 3 /* LITERAL */);
1042
1053
  assert(tokens[pos + 2].eq(RIGHT_CURLY_BRACKET));
1043
1054
  if (tokens[pos + 1].value !== envName) {
1044
- throw new LatexParserError("Mismatched \\begin and \\end environments");
1055
+ throw new LatexParserError("\\begin and \\end environments mismatch");
1045
1056
  }
1046
1057
  pos += 3;
1047
- const exprInside = tokens.slice(exprInsideStart, exprInsideEnd);
1048
- while (exprInside.length > 0 && [5 /* SPACE */, 6 /* NEWLINE */].includes(exprInside[exprInside.length - 1].type)) {
1049
- exprInside.pop();
1050
- }
1051
- const body = this.parseAligned(exprInside);
1052
1058
  const res = new TexBeginEnd(new TexToken(3 /* LITERAL */, envName), body, data);
1053
1059
  return [res, pos];
1054
1060
  }
1055
- parseAligned(tokens) {
1056
- let pos = 0;
1061
+ // return pos: (position of closingToken) + 1
1062
+ // pos will be -1 if closingToken is not found
1063
+ parseAligned(tokens, start, closingToken) {
1064
+ this.alignmentDepth++;
1065
+ let pos = start;
1066
+ pos += eat_whitespaces(tokens, pos).length;
1057
1067
  const allRows = [];
1058
1068
  let row = [];
1059
1069
  allRows.push(row);
1060
1070
  let group = new TexGroup([]);
1061
1071
  row.push(group);
1062
1072
  while (pos < tokens.length) {
1073
+ if (tokens[pos].eq(closingToken)) {
1074
+ break;
1075
+ }
1063
1076
  const [res, newPos] = this.parseNextExpr(tokens, pos);
1064
1077
  pos = newPos;
1065
1078
  if (res.head.type === 5 /* SPACE */ || res.head.type === 6 /* NEWLINE */) {
@@ -1082,7 +1095,20 @@ var LatexParser = class {
1082
1095
  group.items.push(res);
1083
1096
  }
1084
1097
  }
1085
- return allRows;
1098
+ if (pos >= tokens.length) {
1099
+ return [[], -1];
1100
+ }
1101
+ if (allRows.length > 0 && allRows[allRows.length - 1].length > 0) {
1102
+ const last_cell = allRows[allRows.length - 1][allRows[allRows.length - 1].length - 1];
1103
+ if (last_cell.type === "ordgroup") {
1104
+ const last_cell_items = last_cell.items;
1105
+ while (last_cell_items.length > 0 && [5 /* SPACE */, 6 /* NEWLINE */].includes(last_cell_items[last_cell_items.length - 1].head.type)) {
1106
+ last_cell_items.pop();
1107
+ }
1108
+ }
1109
+ }
1110
+ this.alignmentDepth--;
1111
+ return [allRows, pos + 1];
1086
1112
  }
1087
1113
  };
1088
1114
  function passIgnoreWhitespaceBeforeScriptMark(tokens) {
@@ -1111,7 +1137,7 @@ function passExpandCustomTexMacros(tokens, customTexMacros) {
1111
1137
  }
1112
1138
  return out_tokens;
1113
1139
  }
1114
- function parseTex(tex, customTexMacros) {
1140
+ function parseTex(tex, customTexMacros = {}) {
1115
1141
  const parser = new LatexParser();
1116
1142
  let tokens = tokenize_tex(tex);
1117
1143
  tokens = passIgnoreWhitespaceBeforeScriptMark(tokens);
@@ -1686,6 +1712,7 @@ var symbolMap = /* @__PURE__ */ new Map([
1686
1712
  ["mathbb", "bb"],
1687
1713
  ["mathbf", "bold"],
1688
1714
  ["mathcal", "cal"],
1715
+ ["mathscr", "scr"],
1689
1716
  ["mathit", "italic"],
1690
1717
  ["mathfrak", "frak"],
1691
1718
  ["mathrm", "upright"],
@@ -1886,7 +1913,6 @@ var symbolMap = /* @__PURE__ */ new Map([
1886
1913
  ["zeta", "zeta"],
1887
1914
  ["intop", "limits(integral)"],
1888
1915
  // extended
1889
- ["mathscr", "scr"],
1890
1916
  ["LaTeX", "#LaTeX"],
1891
1917
  ["TeX", "#TeX"]
1892
1918
  ]);
@@ -2891,12 +2917,6 @@ function convert_tex_node_to_typst(abstractNode, options = {}) {
2891
2917
  }
2892
2918
  case "text": {
2893
2919
  const node2 = abstractNode;
2894
- if (/[^\x00-\x7F]+/.test(node2.head.value) && options.nonAsciiWrapper !== "") {
2895
- return new TypstFuncCall(
2896
- new TypstToken(1 /* SYMBOL */, options.nonAsciiWrapper),
2897
- [new TypstToken(4 /* TEXT */, node2.head.value).toNode()]
2898
- );
2899
- }
2900
2920
  return new TypstToken(4 /* TEXT */, node2.head.value).toNode();
2901
2921
  }
2902
2922
  case "ordgroup":
@@ -3026,6 +3046,30 @@ function convert_tex_node_to_typst(abstractNode, options = {}) {
3026
3046
  if (node2.head.value === "\\substack") {
3027
3047
  return arg0;
3028
3048
  }
3049
+ if (node2.head.value === "\\displaylines") {
3050
+ return arg0;
3051
+ }
3052
+ if (node2.head.value === "\\mathinner") {
3053
+ return arg0;
3054
+ }
3055
+ if (node2.head.value === "\\mathrel") {
3056
+ return new TypstFuncCall(
3057
+ new TypstToken(1 /* SYMBOL */, "class"),
3058
+ [new TypstToken(4 /* TEXT */, "relation").toNode(), arg0]
3059
+ );
3060
+ }
3061
+ if (node2.head.value === "\\mathbin") {
3062
+ return new TypstFuncCall(
3063
+ new TypstToken(1 /* SYMBOL */, "class"),
3064
+ [new TypstToken(4 /* TEXT */, "binary").toNode(), arg0]
3065
+ );
3066
+ }
3067
+ if (node2.head.value === "\\mathop") {
3068
+ return new TypstFuncCall(
3069
+ new TypstToken(1 /* SYMBOL */, "class"),
3070
+ [new TypstToken(4 /* TEXT */, "large").toNode(), arg0]
3071
+ );
3072
+ }
3029
3073
  if (node2.head.value === "\\set") {
3030
3074
  return new TypstLeftright(
3031
3075
  null,
@@ -3304,6 +3348,31 @@ function convert_typst_node_to_tex(abstractNode) {
3304
3348
  assert(arg0.head.type === 4 /* TEXT */);
3305
3349
  return new TexFuncCall(typst_token_to_tex(node.head), [new TexToken(3 /* LITERAL */, arg0.head.value).toNode()]);
3306
3350
  }
3351
+ case "class": {
3352
+ const arg0 = node.args[0];
3353
+ assert(arg0.head.type === 4 /* TEXT */);
3354
+ let command;
3355
+ switch (arg0.head.value) {
3356
+ // \mathrel{X} <- class("relation", X)
3357
+ case "relation":
3358
+ command = "\\mathrel";
3359
+ break;
3360
+ // \mathbin{X} <- class("binary", X)
3361
+ case "binary":
3362
+ command = "\\mathbin";
3363
+ break;
3364
+ // \mathop{X} <- class("large", X)
3365
+ case "large":
3366
+ command = "\\mathop";
3367
+ break;
3368
+ default:
3369
+ throw new Error(`Unimplemented class: ${arg0.head.value}`);
3370
+ }
3371
+ return new TexFuncCall(
3372
+ new TexToken(2 /* COMMAND */, command),
3373
+ [convert_typst_node_to_tex(node.args[1])]
3374
+ );
3375
+ }
3307
3376
  // general case
3308
3377
  default: {
3309
3378
  const func_name_tex = typst_token_to_tex(node.head);
@@ -3521,7 +3590,7 @@ function _find_closing_match(tokens, start, leftBrackets, rightBrackets) {
3521
3590
  }
3522
3591
  return pos - 1;
3523
3592
  }
3524
- function find_closing_match2(tokens, start) {
3593
+ function find_closing_match(tokens, start) {
3525
3594
  return _find_closing_match(
3526
3595
  tokens,
3527
3596
  start,
@@ -3741,7 +3810,7 @@ var TypstParser = class {
3741
3810
  let node;
3742
3811
  let end;
3743
3812
  if (tokens[start].eq(LEFT_PARENTHESES)) {
3744
- const pos_closing = find_closing_match2(tokens, start);
3813
+ const pos_closing = find_closing_match(tokens, start);
3745
3814
  [node, end] = this.parseGroup(tokens, start + 1, pos_closing);
3746
3815
  } else {
3747
3816
  [node, end] = this.parseNextExprWithoutSupSub(tokens, start);
@@ -3757,7 +3826,7 @@ var TypstParser = class {
3757
3826
  const firstToken = tokens[start];
3758
3827
  const node = firstToken.toNode();
3759
3828
  if (firstToken.eq(LEFT_PARENTHESES)) {
3760
- const pos_closing = find_closing_match2(tokens, start);
3829
+ const pos_closing = find_closing_match(tokens, start);
3761
3830
  return this.parseGroup(tokens, start + 1, pos_closing, true);
3762
3831
  }
3763
3832
  if (firstToken.type === 2 /* ELEMENT */ && !isalpha(firstToken.value[0])) {
@@ -3801,13 +3870,13 @@ var TypstParser = class {
3801
3870
  }
3802
3871
  // start: the position of the left parentheses
3803
3872
  parseArguments(tokens, start) {
3804
- const end = find_closing_match2(tokens, start);
3873
+ const end = find_closing_match(tokens, start);
3805
3874
  return [this.parseArgumentsWithSeparator(tokens, start + 1, end, COMMA), end + 1];
3806
3875
  }
3807
3876
  // start: the position of the left parentheses
3808
3877
  parseLrArguments(tokens, start) {
3809
3878
  const lr_token = tokens[start];
3810
- const end = find_closing_match2(tokens, start);
3879
+ const end = find_closing_match(tokens, start);
3811
3880
  if (tokens[start + 1].isOneOf(TypstToken.LEFT_DELIMITERS)) {
3812
3881
  const inner_start = start + 1;
3813
3882
  const inner_end = find_closing_delim(tokens, inner_start);
@@ -3826,7 +3895,7 @@ var TypstParser = class {
3826
3895
  }
3827
3896
  // start: the position of the left parentheses
3828
3897
  parseMatrix(tokens, start, rowSepToken, cellSepToken) {
3829
- const end = find_closing_match2(tokens, start);
3898
+ const end = find_closing_match(tokens, start);
3830
3899
  tokens = tokens.slice(0, end);
3831
3900
  const matrix = [];
3832
3901
  let named_params = {};
@@ -3940,7 +4009,6 @@ function tex2typst(tex, options) {
3940
4009
  fracToSlash: true,
3941
4010
  inftyToOo: false,
3942
4011
  optimize: true,
3943
- nonAsciiWrapper: "",
3944
4012
  customTexMacros: {}
3945
4013
  };
3946
4014
  if (options !== void 0) {