tex2typst 0.3.17 → 0.3.19

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tex2typst.js
2
2
 
3
- JavaScript library for conversion between TeX/LaTeX and Typst math formula code.
3
+ JavaScript library for conversion between TeX/LaTeX and Typst math code.
4
4
 
5
5
  Despite the name `tex2typst` due to the initial goal of converting TeX to Typst, the library can also convert Typst to TeX since version 0.3.0.
6
6
 
@@ -44,7 +44,7 @@ console.log(tex_recovered);
44
44
 
45
45
  If you are using the library in a web page via a `<script>` tag, you don't need the line of `import`, function `tex2typst` and `typst2tex` should be available in the global scope.
46
46
 
47
- tex2typst.js supports some advanced options to customize the conversion. For more details, please refer to the [API Reference](docs/api-reference.md).
47
+ tex2typst.js supports some advanced options to customize the conversion. For details, please refer to the [API Reference](docs/api-reference.md).
48
48
 
49
49
  ## Open-source license
50
50
 
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // src/map.ts
2
2
  var symbolMap = /* @__PURE__ */ new Map([
3
3
  ["displaystyle", "display"],
4
+ ["|", "bar.v.double"],
5
+ ["!", "#h(-math.thin.amount)"],
4
6
  [",", "thin"],
5
7
  [":", "med"],
6
8
  [";", "thick"],
@@ -1185,6 +1187,12 @@ var TexNode = class {
1185
1187
  case "ordgroup": {
1186
1188
  return this.args.map((n) => n.serialize()).flat();
1187
1189
  }
1190
+ case "leftright": {
1191
+ let tokens = this.args.map((n) => n.serialize()).flat();
1192
+ tokens.splice(0, 0, new TexToken(1 /* COMMAND */, "\\left"));
1193
+ tokens.splice(tokens.length - 1, 0, new TexToken(1 /* COMMAND */, "\\right"));
1194
+ return tokens;
1195
+ }
1188
1196
  case "unaryFunc": {
1189
1197
  let tokens = [];
1190
1198
  tokens.push(new TexToken(1 /* COMMAND */, this.content));
@@ -1625,8 +1633,8 @@ var JSLex = class {
1625
1633
  }
1626
1634
  };
1627
1635
 
1628
- // src/tex-parser.ts
1629
- var UNARY_COMMANDS = [
1636
+ // src/tex-tokenizer.ts
1637
+ var TEX_UNARY_COMMANDS = [
1630
1638
  "sqrt",
1631
1639
  "text",
1632
1640
  "bar",
@@ -1659,90 +1667,16 @@ var UNARY_COMMANDS = [
1659
1667
  "overrightarrow",
1660
1668
  "hspace"
1661
1669
  ];
1662
- var BINARY_COMMANDS = [
1670
+ var TEX_BINARY_COMMANDS = [
1663
1671
  "frac",
1664
1672
  "tfrac",
1665
1673
  "binom",
1666
1674
  "dbinom",
1667
1675
  "dfrac",
1668
1676
  "tbinom",
1669
- "overset"
1670
- ];
1671
- var IGNORED_COMMANDS = [
1672
- "bigl",
1673
- "bigr",
1674
- "biggl",
1675
- "biggr",
1676
- "Bigl",
1677
- "Bigr",
1678
- "Biggl",
1679
- "Biggr"
1677
+ "overset",
1678
+ "underset"
1680
1679
  ];
1681
- var EMPTY_NODE = new TexNode("empty", "");
1682
- function get_command_param_num(command) {
1683
- if (UNARY_COMMANDS.includes(command)) {
1684
- return 1;
1685
- } else if (BINARY_COMMANDS.includes(command)) {
1686
- return 2;
1687
- } else {
1688
- return 0;
1689
- }
1690
- }
1691
- var LEFT_CURLY_BRACKET = new TexToken(6 /* CONTROL */, "{");
1692
- var RIGHT_CURLY_BRACKET = new TexToken(6 /* CONTROL */, "}");
1693
- var LEFT_SQUARE_BRACKET = new TexToken(0 /* ELEMENT */, "[");
1694
- var RIGHT_SQUARE_BRACKET = new TexToken(0 /* ELEMENT */, "]");
1695
- function eat_whitespaces(tokens, start) {
1696
- let pos = start;
1697
- while (pos < tokens.length && [4 /* SPACE */, 5 /* NEWLINE */].includes(tokens[pos].type)) {
1698
- pos++;
1699
- }
1700
- return tokens.slice(start, pos);
1701
- }
1702
- function eat_parenthesis(tokens, start) {
1703
- const firstToken = tokens[start];
1704
- if (firstToken.type === 0 /* ELEMENT */ && ["(", ")", "[", "]", "|", "\\{", "\\}", "."].includes(firstToken.value)) {
1705
- return firstToken;
1706
- } else if (firstToken.type === 1 /* COMMAND */ && ["lfloor", "rfloor", "lceil", "rceil", "langle", "rangle"].includes(firstToken.value.slice(1))) {
1707
- return firstToken;
1708
- } else {
1709
- return null;
1710
- }
1711
- }
1712
- function eat_primes(tokens, start) {
1713
- let pos = start;
1714
- while (pos < tokens.length && tokens[pos].eq(new TexToken(0 /* ELEMENT */, "'"))) {
1715
- pos += 1;
1716
- }
1717
- return pos - start;
1718
- }
1719
- function find_closing_match(tokens, start, leftToken, rightToken) {
1720
- assert(tokens[start].eq(leftToken));
1721
- let count = 1;
1722
- let pos = start + 1;
1723
- while (count > 0) {
1724
- if (pos >= tokens.length) {
1725
- return -1;
1726
- }
1727
- if (tokens[pos].eq(leftToken)) {
1728
- count += 1;
1729
- } else if (tokens[pos].eq(rightToken)) {
1730
- count -= 1;
1731
- }
1732
- pos += 1;
1733
- }
1734
- return pos - 1;
1735
- }
1736
- var LEFT_COMMAND = new TexToken(1 /* COMMAND */, "\\left");
1737
- var RIGHT_COMMAND = new TexToken(1 /* COMMAND */, "\\right");
1738
- function find_closing_right_command(tokens, start) {
1739
- return find_closing_match(tokens, start, LEFT_COMMAND, RIGHT_COMMAND);
1740
- }
1741
- var BEGIN_COMMAND = new TexToken(1 /* COMMAND */, "\\begin");
1742
- var END_COMMAND = new TexToken(1 /* COMMAND */, "\\end");
1743
- function find_closing_end_command(tokens, start) {
1744
- return find_closing_match(tokens, start, BEGIN_COMMAND, END_COMMAND);
1745
- }
1746
1680
  function unescape(str) {
1747
1681
  const chars = ["{", "}", "\\", "$", "&", "#", "_", "%"];
1748
1682
  for (const char of chars) {
@@ -1767,17 +1701,18 @@ var rules_map = /* @__PURE__ */ new Map([
1767
1701
  ],
1768
1702
  [String.raw`%[^\n]*`, (s) => new TexToken(3 /* COMMENT */, s.text().substring(1))],
1769
1703
  [String.raw`[{}_^&]`, (s) => new TexToken(6 /* CONTROL */, s.text())],
1770
- [String.raw`\\[\\,:; ]`, (s) => new TexToken(6 /* CONTROL */, s.text())],
1704
+ [String.raw`\\[\\,:;! ]`, (s) => new TexToken(6 /* CONTROL */, s.text())],
1771
1705
  [String.raw`\r?\n`, (_s) => new TexToken(5 /* NEWLINE */, "\n")],
1772
1706
  [String.raw`\s+`, (s) => new TexToken(4 /* SPACE */, s.text())],
1773
1707
  [String.raw`\\[{}%$&#_|]`, (s) => new TexToken(0 /* ELEMENT */, s.text())],
1708
+ // e.g. match `\frac13`, `\frac1 b`, `\frac a b`
1774
1709
  [String.raw`(\\[a-zA-Z]+)(\s*\d|\s+[a-zA-Z])\s*([0-9a-zA-Z])`, (s) => {
1775
1710
  const text = s.text();
1776
1711
  const regex = RegExp(String.raw`(\\[a-zA-Z]+)(\s*\d|\s+[a-zA-Z])\s*([0-9a-zA-Z])`);
1777
1712
  const match = text.match(regex);
1778
1713
  assert(match !== null);
1779
1714
  const command = match[1];
1780
- if (BINARY_COMMANDS.includes(command.substring(1))) {
1715
+ if (TEX_BINARY_COMMANDS.includes(command.substring(1))) {
1781
1716
  const arg1 = match[2].trimStart();
1782
1717
  const arg2 = match[3];
1783
1718
  return [
@@ -1790,13 +1725,14 @@ var rules_map = /* @__PURE__ */ new Map([
1790
1725
  return [];
1791
1726
  }
1792
1727
  }],
1728
+ // e.g. match `\sqrt3`, `\sqrt a`
1793
1729
  [String.raw`(\\[a-zA-Z]+)(\s*\d|\s+[a-zA-Z])`, (s) => {
1794
1730
  const text = s.text();
1795
1731
  const regex = RegExp(String.raw`(\\[a-zA-Z]+)(\s*\d|\s+[a-zA-Z])`);
1796
1732
  const match = text.match(regex);
1797
1733
  assert(match !== null);
1798
1734
  const command = match[1];
1799
- if (UNARY_COMMANDS.includes(command.substring(1))) {
1735
+ if (TEX_UNARY_COMMANDS.includes(command.substring(1))) {
1800
1736
  const arg1 = match[2].trimStart();
1801
1737
  return [
1802
1738
  new TexToken(1 /* COMMAND */, command),
@@ -1826,6 +1762,83 @@ function tokenize_tex(input) {
1826
1762
  const lexer = new JSLex(spec);
1827
1763
  return lexer.collect(input);
1828
1764
  }
1765
+
1766
+ // src/tex-parser.ts
1767
+ var IGNORED_COMMANDS = [
1768
+ "bigl",
1769
+ "bigr",
1770
+ "biggl",
1771
+ "biggr",
1772
+ "Bigl",
1773
+ "Bigr",
1774
+ "Biggl",
1775
+ "Biggr"
1776
+ ];
1777
+ var EMPTY_NODE = new TexNode("empty", "");
1778
+ function get_command_param_num(command) {
1779
+ if (TEX_UNARY_COMMANDS.includes(command)) {
1780
+ return 1;
1781
+ } else if (TEX_BINARY_COMMANDS.includes(command)) {
1782
+ return 2;
1783
+ } else {
1784
+ return 0;
1785
+ }
1786
+ }
1787
+ var LEFT_CURLY_BRACKET = new TexToken(6 /* CONTROL */, "{");
1788
+ var RIGHT_CURLY_BRACKET = new TexToken(6 /* CONTROL */, "}");
1789
+ var LEFT_SQUARE_BRACKET = new TexToken(0 /* ELEMENT */, "[");
1790
+ var RIGHT_SQUARE_BRACKET = new TexToken(0 /* ELEMENT */, "]");
1791
+ function eat_whitespaces(tokens, start) {
1792
+ let pos = start;
1793
+ while (pos < tokens.length && [4 /* SPACE */, 5 /* NEWLINE */].includes(tokens[pos].type)) {
1794
+ pos++;
1795
+ }
1796
+ return tokens.slice(start, pos);
1797
+ }
1798
+ function eat_parenthesis(tokens, start) {
1799
+ const firstToken = tokens[start];
1800
+ if (firstToken.type === 0 /* ELEMENT */ && ["(", ")", "[", "]", "|", "\\{", "\\}", ".", "\\|"].includes(firstToken.value)) {
1801
+ return firstToken;
1802
+ } else if (firstToken.type === 1 /* COMMAND */ && ["lfloor", "rfloor", "lceil", "rceil", "langle", "rangle"].includes(firstToken.value.slice(1))) {
1803
+ return firstToken;
1804
+ } else {
1805
+ return null;
1806
+ }
1807
+ }
1808
+ function eat_primes(tokens, start) {
1809
+ let pos = start;
1810
+ while (pos < tokens.length && tokens[pos].eq(new TexToken(0 /* ELEMENT */, "'"))) {
1811
+ pos += 1;
1812
+ }
1813
+ return pos - start;
1814
+ }
1815
+ function find_closing_match(tokens, start, leftToken, rightToken) {
1816
+ assert(tokens[start].eq(leftToken));
1817
+ let count = 1;
1818
+ let pos = start + 1;
1819
+ while (count > 0) {
1820
+ if (pos >= tokens.length) {
1821
+ return -1;
1822
+ }
1823
+ if (tokens[pos].eq(leftToken)) {
1824
+ count += 1;
1825
+ } else if (tokens[pos].eq(rightToken)) {
1826
+ count -= 1;
1827
+ }
1828
+ pos += 1;
1829
+ }
1830
+ return pos - 1;
1831
+ }
1832
+ var LEFT_COMMAND = new TexToken(1 /* COMMAND */, "\\left");
1833
+ var RIGHT_COMMAND = new TexToken(1 /* COMMAND */, "\\right");
1834
+ function find_closing_right_command(tokens, start) {
1835
+ return find_closing_match(tokens, start, LEFT_COMMAND, RIGHT_COMMAND);
1836
+ }
1837
+ var BEGIN_COMMAND = new TexToken(1 /* COMMAND */, "\\begin");
1838
+ var END_COMMAND = new TexToken(1 /* COMMAND */, "\\end");
1839
+ function find_closing_end_command(tokens, start) {
1840
+ return find_closing_match(tokens, start, BEGIN_COMMAND, END_COMMAND);
1841
+ }
1829
1842
  var LatexParserError = class extends Error {
1830
1843
  constructor(message) {
1831
1844
  super(message);
@@ -1973,6 +1986,7 @@ var LatexParser = class {
1973
1986
  case "}":
1974
1987
  throw new LatexParserError("Unmatched '}'");
1975
1988
  case "\\\\":
1989
+ case "\\!":
1976
1990
  case "\\,":
1977
1991
  case "\\:":
1978
1992
  case "\\;":
@@ -2264,6 +2278,7 @@ var TYPST_LEFT_PARENTHESIS = new TypstToken(2 /* ELEMENT */, "(");
2264
2278
  var TYPST_RIGHT_PARENTHESIS = new TypstToken(2 /* ELEMENT */, ")");
2265
2279
  var TYPST_COMMA = new TypstToken(2 /* ELEMENT */, ",");
2266
2280
  var TYPST_NEWLINE = new TypstToken(1 /* SYMBOL */, "\n");
2281
+ var SOFT_SPACE = new TypstToken(6 /* CONTROL */, " ");
2267
2282
  function typst_primitive_to_string(value) {
2268
2283
  switch (typeof value) {
2269
2284
  case "string":
@@ -2285,14 +2300,15 @@ var TypstWriterError = class extends Error {
2285
2300
  }
2286
2301
  };
2287
2302
  var TypstWriter = class {
2288
- constructor(opt) {
2303
+ constructor(options) {
2289
2304
  this.buffer = "";
2290
2305
  this.queue = [];
2291
2306
  this.insideFunctionDepth = 0;
2292
- this.nonStrict = opt.nonStrict;
2293
- this.preferShorthands = opt.preferShorthands;
2294
- this.keepSpaces = opt.keepSpaces;
2295
- this.inftyToOo = opt.inftyToOo;
2307
+ this.nonStrict = options.nonStrict;
2308
+ this.preferShorthands = options.preferShorthands;
2309
+ this.keepSpaces = options.keepSpaces;
2310
+ this.inftyToOo = options.inftyToOo;
2311
+ this.optimize = options.optimize;
2296
2312
  }
2297
2313
  writeBuffer(token) {
2298
2314
  const str = token.toString();
@@ -2386,7 +2402,7 @@ var TypstWriter = class {
2386
2402
  trailing_space_needed = this.appendWithBracketsIfNeeded(sup);
2387
2403
  }
2388
2404
  if (trailing_space_needed) {
2389
- this.queue.push(new TypstToken(6 /* CONTROL */, " "));
2405
+ this.queue.push(SOFT_SPACE);
2390
2406
  }
2391
2407
  break;
2392
2408
  }
@@ -2417,7 +2433,12 @@ var TypstWriter = class {
2417
2433
  }
2418
2434
  case "fraction": {
2419
2435
  const [numerator, denominator] = node.args;
2420
- this.appendWithBracketsIfNeeded(numerator);
2436
+ const pos = this.queue.length;
2437
+ const no_wrap = this.appendWithBracketsIfNeeded(numerator);
2438
+ const wrapped = !no_wrap;
2439
+ if (wrapped) {
2440
+ this.queue.splice(pos, 0, SOFT_SPACE);
2441
+ }
2421
2442
  this.queue.push(new TypstToken(2 /* ELEMENT */, "/"));
2422
2443
  this.appendWithBracketsIfNeeded(denominator);
2423
2444
  break;
@@ -2526,17 +2547,17 @@ var TypstWriter = class {
2526
2547
  return !need_to_wrap;
2527
2548
  }
2528
2549
  flushQueue() {
2529
- const SOFT_SPACE = new TypstToken(6 /* CONTROL */, " ");
2550
+ const dummy_token = new TypstToken(1 /* SYMBOL */, "");
2530
2551
  for (let i = 0; i < this.queue.length; i++) {
2531
2552
  let token = this.queue[i];
2532
2553
  if (token.eq(SOFT_SPACE)) {
2533
- if (i === this.queue.length - 1) {
2534
- this.queue[i].value = "";
2535
- } else if (this.queue[i + 1].isOneOf([TYPST_RIGHT_PARENTHESIS, TYPST_COMMA, TYPST_NEWLINE])) {
2536
- this.queue[i].value = "";
2554
+ const to_delete = i === 0 || i === this.queue.length - 1 || this.queue[i - 1].isOneOf([TYPST_NEWLINE]) || this.queue[i + 1].isOneOf([TYPST_RIGHT_PARENTHESIS, TYPST_COMMA, TYPST_NEWLINE]);
2555
+ if (to_delete) {
2556
+ this.queue[i] = dummy_token;
2537
2557
  }
2538
2558
  }
2539
2559
  }
2560
+ this.queue = this.queue.filter((token) => !token.eq(dummy_token));
2540
2561
  this.queue.forEach((token) => {
2541
2562
  this.writeBuffer(token);
2542
2563
  });
@@ -2559,9 +2580,11 @@ var TypstWriter = class {
2559
2580
  res = res.replace(/round\(\)/g, 'round("")');
2560
2581
  return res;
2561
2582
  };
2562
- const all_passes = [smartFloorPass, smartCeilPass, smartRoundPass];
2563
- for (const pass of all_passes) {
2564
- this.buffer = pass(this.buffer);
2583
+ if (this.optimize) {
2584
+ const all_passes = [smartFloorPass, smartCeilPass, smartRoundPass];
2585
+ for (const pass of all_passes) {
2586
+ this.buffer = pass(this.buffer);
2587
+ }
2565
2588
  }
2566
2589
  return this.buffer;
2567
2590
  }
@@ -2583,8 +2606,6 @@ function tex_token_to_typst(token) {
2583
2606
  return token;
2584
2607
  } else if (token === "/") {
2585
2608
  return "\\/";
2586
- } else if (token === "\\|") {
2587
- return "parallel";
2588
2609
  } else if (token === "\\\\") {
2589
2610
  return "\\";
2590
2611
  } else if (["\\$", "\\#", "\\&", "\\_"].includes(token)) {
@@ -2601,39 +2622,48 @@ function tex_token_to_typst(token) {
2601
2622
  }
2602
2623
  function convert_overset(node, options) {
2603
2624
  const [sup, base] = node.args;
2604
- const is_def = (n) => {
2605
- if (n.eq(new TexNode("text", "def"))) {
2606
- return true;
2607
- }
2608
- if (n.type === "ordgroup" && n.args.length === 3) {
2609
- const [a1, a2, a3] = n.args;
2610
- const d = new TexNode("element", "d");
2611
- const e = new TexNode("element", "e");
2612
- const f = new TexNode("element", "f");
2613
- if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
2625
+ if (options.optimize) {
2626
+ const is_def = (n) => {
2627
+ if (n.eq(new TexNode("text", "def"))) {
2614
2628
  return true;
2615
2629
  }
2630
+ if (n.type === "ordgroup" && n.args.length === 3) {
2631
+ const [a1, a2, a3] = n.args;
2632
+ const d = new TexNode("element", "d");
2633
+ const e = new TexNode("element", "e");
2634
+ const f = new TexNode("element", "f");
2635
+ if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
2636
+ return true;
2637
+ }
2638
+ }
2639
+ return false;
2640
+ };
2641
+ const is_eq = (n) => n.eq(new TexNode("element", "="));
2642
+ if (is_def(sup) && is_eq(base)) {
2643
+ return new TypstNode("symbol", "eq.def");
2616
2644
  }
2617
- return false;
2618
- };
2619
- const is_eq = (n) => n.eq(new TexNode("element", "="));
2620
- if (is_def(sup) && is_eq(base)) {
2621
- return new TypstNode("symbol", "eq.def");
2622
2645
  }
2623
2646
  const limits_call = new TypstNode(
2624
2647
  "funcCall",
2625
2648
  "limits",
2626
2649
  [convert_tex_node_to_typst(base, options)]
2627
2650
  );
2628
- return new TypstNode(
2629
- "supsub",
2630
- "",
2631
- [],
2632
- {
2633
- base: limits_call,
2634
- sup: convert_tex_node_to_typst(sup, options)
2635
- }
2651
+ return new TypstNode("supsub", "", [], {
2652
+ base: limits_call,
2653
+ sup: convert_tex_node_to_typst(sup, options)
2654
+ });
2655
+ }
2656
+ function convert_underset(node, options) {
2657
+ const [sub, base] = node.args;
2658
+ const limits_call = new TypstNode(
2659
+ "funcCall",
2660
+ "limits",
2661
+ [convert_tex_node_to_typst(base, options)]
2636
2662
  );
2663
+ return new TypstNode("supsub", "", [], {
2664
+ base: limits_call,
2665
+ sub: convert_tex_node_to_typst(sub, options)
2666
+ });
2637
2667
  }
2638
2668
  function convert_tex_node_to_typst(node, options = {}) {
2639
2669
  switch (node.type) {
@@ -2693,39 +2723,51 @@ function convert_tex_node_to_typst(node, options = {}) {
2693
2723
  return new TypstNode("supsub", "", [], data);
2694
2724
  }
2695
2725
  case "leftright": {
2696
- const [left, body, right] = node.args;
2726
+ const [left, _body, right] = node.args;
2727
+ const [typ_left, typ_body, typ_right] = node.args.map((n) => convert_tex_node_to_typst(n, options));
2728
+ if (options.optimize) {
2729
+ if (left.content === "\\|" && right.content === "\\|") {
2730
+ return new TypstNode("funcCall", "norm", [typ_body]);
2731
+ }
2732
+ if ([
2733
+ "[]",
2734
+ "()",
2735
+ "\\{\\}",
2736
+ "\\lfloor\\rfloor",
2737
+ "\\lceil\\rceil",
2738
+ "\\lfloor\\rceil"
2739
+ ].includes(left.content + right.content)) {
2740
+ return new TypstNode("group", "", [typ_left, typ_body, typ_right]);
2741
+ }
2742
+ }
2697
2743
  const group = new TypstNode(
2698
2744
  "group",
2699
2745
  "",
2700
- node.args.map((n) => convert_tex_node_to_typst(n, options))
2746
+ [typ_left, typ_body, typ_right]
2701
2747
  );
2702
- if ([
2703
- "[]",
2704
- "()",
2705
- "\\{\\}",
2706
- "\\lfloor\\rfloor",
2707
- "\\lceil\\rceil",
2708
- "\\lfloor\\rceil"
2709
- ].includes(left.content + right.content)) {
2710
- return group;
2711
- }
2748
+ const escape_curly_or_paren = function(s) {
2749
+ if (["(", ")", "{", "["].includes(s)) {
2750
+ return "\\" + s;
2751
+ } else {
2752
+ return s;
2753
+ }
2754
+ };
2712
2755
  if (right.content === ".") {
2713
- group.args.pop();
2714
- return group;
2756
+ typ_left.content = escape_curly_or_paren(typ_left.content);
2757
+ group.args = [typ_left, typ_body];
2715
2758
  } else if (left.content === ".") {
2716
- group.args.shift();
2717
- return new TypstNode("funcCall", "lr", [group]);
2759
+ typ_right.content = escape_curly_or_paren(typ_right.content);
2760
+ group.args = [typ_body, typ_right];
2718
2761
  }
2719
- return new TypstNode(
2720
- "funcCall",
2721
- "lr",
2722
- [group]
2723
- );
2762
+ return new TypstNode("funcCall", "lr", [group]);
2724
2763
  }
2725
2764
  case "binaryFunc": {
2726
2765
  if (node.content === "\\overset") {
2727
2766
  return convert_overset(node, options);
2728
2767
  }
2768
+ if (node.content === "\\underset") {
2769
+ return convert_underset(node, options);
2770
+ }
2729
2771
  if (node.content === "\\frac") {
2730
2772
  if (options.fracToSlash) {
2731
2773
  return new TypstNode(
@@ -2781,16 +2823,13 @@ function convert_tex_node_to_typst(node, options = {}) {
2781
2823
  );
2782
2824
  }
2783
2825
  if (node.content === "\\operatorname") {
2784
- const text = arg0.content;
2785
- if (TYPST_INTRINSIC_SYMBOLS.includes(text)) {
2786
- return new TypstNode("symbol", text);
2787
- } else {
2788
- return new TypstNode(
2789
- "funcCall",
2790
- "op",
2791
- [arg0]
2792
- );
2826
+ if (options.optimize) {
2827
+ const text = arg0.content;
2828
+ if (TYPST_INTRINSIC_SYMBOLS.includes(text)) {
2829
+ return new TypstNode("symbol", text);
2830
+ }
2793
2831
  }
2832
+ return new TypstNode("funcCall", "op", [arg0]);
2794
2833
  }
2795
2834
  if (node.content === "\\hspace") {
2796
2835
  const text = arg0.content;
@@ -2922,7 +2961,9 @@ var TYPST_UNARY_FUNCTIONS = [
2922
2961
  "cal",
2923
2962
  "frak",
2924
2963
  "floor",
2925
- "ceil"
2964
+ "ceil",
2965
+ "norm",
2966
+ "limits"
2926
2967
  ];
2927
2968
  var TYPST_BINARY_FUNCTIONS = [
2928
2969
  "frac",
@@ -2939,8 +2980,6 @@ function apply_escape_if_needed2(c) {
2939
2980
  function typst_token_to_tex(token) {
2940
2981
  if (/^[a-zA-Z0-9]$/.test(token)) {
2941
2982
  return token;
2942
- } else if (token === "thin") {
2943
- return "\\,";
2944
2983
  } else if (reverseSymbolMap.has(token)) {
2945
2984
  return "\\" + reverseSymbolMap.get(token);
2946
2985
  }
@@ -3005,15 +3044,21 @@ function convert_typst_node_to_tex(node) {
3005
3044
  return new TexNode("ordgroup", "", node.args.map(convert_typst_node_to_tex));
3006
3045
  }
3007
3046
  }
3047
+ if (node.content === "norm") {
3048
+ const arg0 = node.args[0];
3049
+ const tex_node_type = node.isOverHigh() ? "leftright" : "ordgroup";
3050
+ return new TexNode(tex_node_type, "", [
3051
+ new TexNode("symbol", "\\|"),
3052
+ convert_typst_node_to_tex(arg0),
3053
+ new TexNode("symbol", "\\|")
3054
+ ]);
3055
+ }
3008
3056
  if (node.content === "floor" || node.content === "ceil") {
3009
- let left = "\\l" + node.content;
3010
- let right = "\\r" + node.content;
3057
+ const left = "\\l" + node.content;
3058
+ const right = "\\r" + node.content;
3011
3059
  const arg0 = node.args[0];
3012
- if (arg0.isOverHigh()) {
3013
- left = "\\left" + left;
3014
- right = "\\right" + right;
3015
- }
3016
- return new TexNode("ordgroup", "", [
3060
+ const tex_node_type = node.isOverHigh() ? "leftright" : "ordgroup";
3061
+ return new TexNode(tex_node_type, "", [
3017
3062
  new TexNode("symbol", left),
3018
3063
  convert_typst_node_to_tex(arg0),
3019
3064
  new TexNode("symbol", right)
@@ -3051,7 +3096,6 @@ function convert_typst_node_to_tex(node) {
3051
3096
  }
3052
3097
  case "supsub": {
3053
3098
  const { base, sup, sub } = node.data;
3054
- const base_tex = convert_typst_node_to_tex(base);
3055
3099
  let sup_tex;
3056
3100
  let sub_tex;
3057
3101
  if (sup) {
@@ -3060,6 +3104,18 @@ function convert_typst_node_to_tex(node) {
3060
3104
  if (sub) {
3061
3105
  sub_tex = convert_typst_node_to_tex(sub);
3062
3106
  }
3107
+ if (base.eq(new TypstNode("funcCall", "limits"))) {
3108
+ const body_in_limits = convert_typst_node_to_tex(base.args[0]);
3109
+ if (sup_tex !== void 0 && sub_tex === void 0) {
3110
+ return new TexNode("binaryFunc", "\\overset", [sup_tex, body_in_limits]);
3111
+ } else if (sup_tex === void 0 && sub_tex !== void 0) {
3112
+ return new TexNode("binaryFunc", "\\underset", [sub_tex, body_in_limits]);
3113
+ } else {
3114
+ const underset_call = new TexNode("binaryFunc", "\\underset", [sub_tex, body_in_limits]);
3115
+ return new TexNode("binaryFunc", "\\overset", [sup_tex, underset_call]);
3116
+ }
3117
+ }
3118
+ const base_tex = convert_typst_node_to_tex(base);
3063
3119
  const res = new TexNode("supsub", "", [], {
3064
3120
  base: base_tex,
3065
3121
  sup: sup_tex,
@@ -3142,15 +3198,8 @@ function convert_typst_node_to_tex(node) {
3142
3198
  }
3143
3199
  }
3144
3200
 
3145
- // src/typst-parser.ts
3201
+ // src/typst-tokenizer.ts
3146
3202
  var TYPST_SHORTHANDS = Array.from(reverseShorthandMap.keys());
3147
- function eat_primes2(tokens, start) {
3148
- let pos = start;
3149
- while (pos < tokens.length && tokens[pos].eq(new TypstToken(2 /* ELEMENT */, "'"))) {
3150
- pos += 1;
3151
- }
3152
- return pos - start;
3153
- }
3154
3203
  function generate_regex_for_shorthands() {
3155
3204
  const regex_list = TYPST_SHORTHANDS.map((s) => {
3156
3205
  s = s.replaceAll("|", "\\|");
@@ -3214,6 +3263,15 @@ function tokenize_typst(input) {
3214
3263
  const lexer = new JSLex(spec2);
3215
3264
  return lexer.collect(input);
3216
3265
  }
3266
+
3267
+ // src/typst-parser.ts
3268
+ function eat_primes2(tokens, start) {
3269
+ let pos = start;
3270
+ while (pos < tokens.length && tokens[pos].eq(new TypstToken(2 /* ELEMENT */, "'"))) {
3271
+ pos += 1;
3272
+ }
3273
+ return pos - start;
3274
+ }
3217
3275
  function _find_closing_match(tokens, start, leftBrackets, rightBrackets) {
3218
3276
  assert(tokens[start].isOneOf(leftBrackets));
3219
3277
  let count = 1;
@@ -3684,11 +3742,11 @@ var TexWriter = class {
3684
3742
  function tex2typst(tex, options) {
3685
3743
  const opt = {
3686
3744
  nonStrict: true,
3687
- preferTypstIntrinsic: true,
3688
3745
  preferShorthands: true,
3689
3746
  keepSpaces: false,
3690
3747
  fracToSlash: true,
3691
3748
  inftyToOo: false,
3749
+ optimize: true,
3692
3750
  nonAsciiWrapper: "",
3693
3751
  customTexMacros: {}
3694
3752
  };
@@ -1,5 +1,4 @@
1
1
  import { TexNode, TexToken } from "./types";
2
- export declare function tokenize_tex(input: string): TexToken[];
3
2
  export declare class LatexParserError extends Error {
4
3
  constructor(message: string);
5
4
  }
@@ -0,0 +1,4 @@
1
+ import { TexToken } from "./types";
2
+ export declare const TEX_UNARY_COMMANDS: string[];
3
+ export declare const TEX_BINARY_COMMANDS: string[];
4
+ export declare function tokenize_tex(input: string): TexToken[];