tex2typst 0.3.16 → 0.3.18

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));
@@ -1666,7 +1674,8 @@ var BINARY_COMMANDS = [
1666
1674
  "dbinom",
1667
1675
  "dfrac",
1668
1676
  "tbinom",
1669
- "overset"
1677
+ "overset",
1678
+ "underset"
1670
1679
  ];
1671
1680
  var IGNORED_COMMANDS = [
1672
1681
  "bigl",
@@ -1701,7 +1710,7 @@ function eat_whitespaces(tokens, start) {
1701
1710
  }
1702
1711
  function eat_parenthesis(tokens, start) {
1703
1712
  const firstToken = tokens[start];
1704
- if (firstToken.type === 0 /* ELEMENT */ && ["(", ")", "[", "]", "|", "\\{", "\\}", "."].includes(firstToken.value)) {
1713
+ if (firstToken.type === 0 /* ELEMENT */ && ["(", ")", "[", "]", "|", "\\{", "\\}", ".", "\\|"].includes(firstToken.value)) {
1705
1714
  return firstToken;
1706
1715
  } else if (firstToken.type === 1 /* COMMAND */ && ["lfloor", "rfloor", "lceil", "rceil", "langle", "rangle"].includes(firstToken.value.slice(1))) {
1707
1716
  return firstToken;
@@ -1767,7 +1776,7 @@ var rules_map = /* @__PURE__ */ new Map([
1767
1776
  ],
1768
1777
  [String.raw`%[^\n]*`, (s) => new TexToken(3 /* COMMENT */, s.text().substring(1))],
1769
1778
  [String.raw`[{}_^&]`, (s) => new TexToken(6 /* CONTROL */, s.text())],
1770
- [String.raw`\\[\\,:; ]`, (s) => new TexToken(6 /* CONTROL */, s.text())],
1779
+ [String.raw`\\[\\,:;! ]`, (s) => new TexToken(6 /* CONTROL */, s.text())],
1771
1780
  [String.raw`\r?\n`, (_s) => new TexToken(5 /* NEWLINE */, "\n")],
1772
1781
  [String.raw`\s+`, (s) => new TexToken(4 /* SPACE */, s.text())],
1773
1782
  [String.raw`\\[{}%$&#_|]`, (s) => new TexToken(0 /* ELEMENT */, s.text())],
@@ -1973,6 +1982,7 @@ var LatexParser = class {
1973
1982
  case "}":
1974
1983
  throw new LatexParserError("Unmatched '}'");
1975
1984
  case "\\\\":
1985
+ case "\\!":
1976
1986
  case "\\,":
1977
1987
  case "\\:":
1978
1988
  case "\\;":
@@ -2108,7 +2118,17 @@ var LatexParser = class {
2108
2118
  assert(tokens[pos + 2].eq(RIGHT_CURLY_BRACKET));
2109
2119
  const envName = tokens[pos + 1].value;
2110
2120
  pos += 3;
2111
- pos += eat_whitespaces(tokens, pos).length;
2121
+ const args = [];
2122
+ while (pos < tokens.length) {
2123
+ const whitespaceCount = eat_whitespaces(tokens, pos).length;
2124
+ pos += whitespaceCount;
2125
+ if (pos >= tokens.length || !tokens[pos].eq(LEFT_CURLY_BRACKET)) {
2126
+ break;
2127
+ }
2128
+ const [arg, newPos] = this.parseNextArg(tokens, pos);
2129
+ args.push(arg);
2130
+ pos = newPos;
2131
+ }
2112
2132
  const exprInsideStart = pos;
2113
2133
  const endIdx = find_closing_end_command(tokens, start);
2114
2134
  if (endIdx === -1) {
@@ -2128,7 +2148,7 @@ var LatexParser = class {
2128
2148
  exprInside.pop();
2129
2149
  }
2130
2150
  const body = this.parseAligned(exprInside);
2131
- const res = new TexNode("beginend", envName, [], body);
2151
+ const res = new TexNode("beginend", envName, args, body);
2132
2152
  return [res, pos];
2133
2153
  }
2134
2154
  parseAligned(tokens) {
@@ -2275,14 +2295,15 @@ var TypstWriterError = class extends Error {
2275
2295
  }
2276
2296
  };
2277
2297
  var TypstWriter = class {
2278
- constructor(opt) {
2298
+ constructor(options) {
2279
2299
  this.buffer = "";
2280
2300
  this.queue = [];
2281
2301
  this.insideFunctionDepth = 0;
2282
- this.nonStrict = opt.nonStrict;
2283
- this.preferShorthands = opt.preferShorthands;
2284
- this.keepSpaces = opt.keepSpaces;
2285
- this.inftyToOo = opt.inftyToOo;
2302
+ this.nonStrict = options.nonStrict;
2303
+ this.preferShorthands = options.preferShorthands;
2304
+ this.keepSpaces = options.keepSpaces;
2305
+ this.inftyToOo = options.inftyToOo;
2306
+ this.optimize = options.optimize;
2286
2307
  }
2287
2308
  writeBuffer(token) {
2288
2309
  const str = token.toString();
@@ -2407,21 +2428,9 @@ var TypstWriter = class {
2407
2428
  }
2408
2429
  case "fraction": {
2409
2430
  const [numerator, denominator] = node.args;
2410
- if (numerator.type === "group") {
2411
- this.queue.push(TYPST_LEFT_PARENTHESIS);
2412
- this.serialize(numerator);
2413
- this.queue.push(TYPST_RIGHT_PARENTHESIS);
2414
- } else {
2415
- this.serialize(numerator);
2416
- }
2431
+ this.appendWithBracketsIfNeeded(numerator);
2417
2432
  this.queue.push(new TypstToken(2 /* ELEMENT */, "/"));
2418
- if (denominator.type === "group") {
2419
- this.queue.push(TYPST_LEFT_PARENTHESIS);
2420
- this.serialize(denominator);
2421
- this.queue.push(TYPST_RIGHT_PARENTHESIS);
2422
- } else {
2423
- this.serialize(denominator);
2424
- }
2433
+ this.appendWithBracketsIfNeeded(denominator);
2425
2434
  break;
2426
2435
  }
2427
2436
  case "align": {
@@ -2506,7 +2515,7 @@ var TypstWriter = class {
2506
2515
  }
2507
2516
  }
2508
2517
  appendWithBracketsIfNeeded(node) {
2509
- let need_to_wrap = ["group", "supsub", "empty"].includes(node.type);
2518
+ let need_to_wrap = ["group", "supsub", "fraction", "empty"].includes(node.type);
2510
2519
  if (node.type === "group") {
2511
2520
  if (node.args.length === 0) {
2512
2521
  need_to_wrap = true;
@@ -2561,9 +2570,11 @@ var TypstWriter = class {
2561
2570
  res = res.replace(/round\(\)/g, 'round("")');
2562
2571
  return res;
2563
2572
  };
2564
- const all_passes = [smartFloorPass, smartCeilPass, smartRoundPass];
2565
- for (const pass of all_passes) {
2566
- this.buffer = pass(this.buffer);
2573
+ if (this.optimize) {
2574
+ const all_passes = [smartFloorPass, smartCeilPass, smartRoundPass];
2575
+ for (const pass of all_passes) {
2576
+ this.buffer = pass(this.buffer);
2577
+ }
2567
2578
  }
2568
2579
  return this.buffer;
2569
2580
  }
@@ -2585,8 +2596,6 @@ function tex_token_to_typst(token) {
2585
2596
  return token;
2586
2597
  } else if (token === "/") {
2587
2598
  return "\\/";
2588
- } else if (token === "\\|") {
2589
- return "parallel";
2590
2599
  } else if (token === "\\\\") {
2591
2600
  return "\\";
2592
2601
  } else if (["\\$", "\\#", "\\&", "\\_"].includes(token)) {
@@ -2603,39 +2612,48 @@ function tex_token_to_typst(token) {
2603
2612
  }
2604
2613
  function convert_overset(node, options) {
2605
2614
  const [sup, base] = node.args;
2606
- const is_def = (n) => {
2607
- if (n.eq(new TexNode("text", "def"))) {
2608
- return true;
2609
- }
2610
- if (n.type === "ordgroup" && n.args.length === 3) {
2611
- const [a1, a2, a3] = n.args;
2612
- const d = new TexNode("element", "d");
2613
- const e = new TexNode("element", "e");
2614
- const f = new TexNode("element", "f");
2615
- if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
2615
+ if (options.optimize) {
2616
+ const is_def = (n) => {
2617
+ if (n.eq(new TexNode("text", "def"))) {
2616
2618
  return true;
2617
2619
  }
2620
+ if (n.type === "ordgroup" && n.args.length === 3) {
2621
+ const [a1, a2, a3] = n.args;
2622
+ const d = new TexNode("element", "d");
2623
+ const e = new TexNode("element", "e");
2624
+ const f = new TexNode("element", "f");
2625
+ if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
2626
+ return true;
2627
+ }
2628
+ }
2629
+ return false;
2630
+ };
2631
+ const is_eq = (n) => n.eq(new TexNode("element", "="));
2632
+ if (is_def(sup) && is_eq(base)) {
2633
+ return new TypstNode("symbol", "eq.def");
2618
2634
  }
2619
- return false;
2620
- };
2621
- const is_eq = (n) => n.eq(new TexNode("element", "="));
2622
- if (is_def(sup) && is_eq(base)) {
2623
- return new TypstNode("symbol", "eq.def");
2624
2635
  }
2625
2636
  const limits_call = new TypstNode(
2626
2637
  "funcCall",
2627
2638
  "limits",
2628
2639
  [convert_tex_node_to_typst(base, options)]
2629
2640
  );
2630
- return new TypstNode(
2631
- "supsub",
2632
- "",
2633
- [],
2634
- {
2635
- base: limits_call,
2636
- sup: convert_tex_node_to_typst(sup, options)
2637
- }
2641
+ return new TypstNode("supsub", "", [], {
2642
+ base: limits_call,
2643
+ sup: convert_tex_node_to_typst(sup, options)
2644
+ });
2645
+ }
2646
+ function convert_underset(node, options) {
2647
+ const [sub, base] = node.args;
2648
+ const limits_call = new TypstNode(
2649
+ "funcCall",
2650
+ "limits",
2651
+ [convert_tex_node_to_typst(base, options)]
2638
2652
  );
2653
+ return new TypstNode("supsub", "", [], {
2654
+ base: limits_call,
2655
+ sub: convert_tex_node_to_typst(sub, options)
2656
+ });
2639
2657
  }
2640
2658
  function convert_tex_node_to_typst(node, options = {}) {
2641
2659
  switch (node.type) {
@@ -2695,39 +2713,51 @@ function convert_tex_node_to_typst(node, options = {}) {
2695
2713
  return new TypstNode("supsub", "", [], data);
2696
2714
  }
2697
2715
  case "leftright": {
2698
- const [left, body, right] = node.args;
2716
+ const [left, _body, right] = node.args;
2717
+ const [typ_left, typ_body, typ_right] = node.args.map((n) => convert_tex_node_to_typst(n, options));
2718
+ if (options.optimize) {
2719
+ if (left.content === "\\|" && right.content === "\\|") {
2720
+ return new TypstNode("funcCall", "norm", [typ_body]);
2721
+ }
2722
+ if ([
2723
+ "[]",
2724
+ "()",
2725
+ "\\{\\}",
2726
+ "\\lfloor\\rfloor",
2727
+ "\\lceil\\rceil",
2728
+ "\\lfloor\\rceil"
2729
+ ].includes(left.content + right.content)) {
2730
+ return new TypstNode("group", "", [typ_left, typ_body, typ_right]);
2731
+ }
2732
+ }
2699
2733
  const group = new TypstNode(
2700
2734
  "group",
2701
2735
  "",
2702
- node.args.map((n) => convert_tex_node_to_typst(n, options))
2736
+ [typ_left, typ_body, typ_right]
2703
2737
  );
2704
- if ([
2705
- "[]",
2706
- "()",
2707
- "\\{\\}",
2708
- "\\lfloor\\rfloor",
2709
- "\\lceil\\rceil",
2710
- "\\lfloor\\rceil"
2711
- ].includes(left.content + right.content)) {
2712
- return group;
2713
- }
2738
+ const escape_curly_or_paren = function(s) {
2739
+ if (["(", ")", "{", "["].includes(s)) {
2740
+ return "\\" + s;
2741
+ } else {
2742
+ return s;
2743
+ }
2744
+ };
2714
2745
  if (right.content === ".") {
2715
- group.args.pop();
2716
- return group;
2746
+ typ_left.content = escape_curly_or_paren(typ_left.content);
2747
+ group.args = [typ_left, typ_body];
2717
2748
  } else if (left.content === ".") {
2718
- group.args.shift();
2719
- return new TypstNode("funcCall", "lr", [group]);
2749
+ typ_right.content = escape_curly_or_paren(typ_right.content);
2750
+ group.args = [typ_body, typ_right];
2720
2751
  }
2721
- return new TypstNode(
2722
- "funcCall",
2723
- "lr",
2724
- [group]
2725
- );
2752
+ return new TypstNode("funcCall", "lr", [group]);
2726
2753
  }
2727
2754
  case "binaryFunc": {
2728
2755
  if (node.content === "\\overset") {
2729
2756
  return convert_overset(node, options);
2730
2757
  }
2758
+ if (node.content === "\\underset") {
2759
+ return convert_underset(node, options);
2760
+ }
2731
2761
  if (node.content === "\\frac") {
2732
2762
  if (options.fracToSlash) {
2733
2763
  return new TypstNode(
@@ -2817,6 +2847,49 @@ function convert_tex_node_to_typst(node, options = {}) {
2817
2847
  if (node.content === "cases") {
2818
2848
  return new TypstNode("cases", "", [], data);
2819
2849
  }
2850
+ if (node.content === "array") {
2851
+ const res = new TypstNode("matrix", "", [], data);
2852
+ const options2 = { "delim": TYPST_NONE };
2853
+ const align_args = node.args;
2854
+ if (align_args.length > 0) {
2855
+ const align_node = align_args[0];
2856
+ const align_str = (() => {
2857
+ if (align_node.type === "element") return align_node.content;
2858
+ if (align_node.type === "ordgroup") {
2859
+ return align_node.args.map((n) => n.type === "element" ? n.content : "").join("");
2860
+ }
2861
+ return "";
2862
+ })();
2863
+ if (align_str) {
2864
+ const alignMap = { l: "#left", c: "#center", r: "#right" };
2865
+ const chars = Array.from(align_str);
2866
+ const alignments = chars.map((c) => alignMap[c]).filter(Boolean).map((s) => new TypstNode("symbol", s));
2867
+ const vlinePositions = [];
2868
+ let columnIndex = 0;
2869
+ for (const c of chars) {
2870
+ if (c === "|") {
2871
+ vlinePositions.push(columnIndex);
2872
+ } else if (c === "l" || c === "c" || c === "r") {
2873
+ columnIndex++;
2874
+ }
2875
+ }
2876
+ if (vlinePositions.length > 0) {
2877
+ if (vlinePositions.length === 1) {
2878
+ options2["augment"] = new TypstNode("symbol", `#${vlinePositions[0]}`);
2879
+ } else {
2880
+ options2["augment"] = new TypstNode("symbol", `#(vline: (${vlinePositions.join(", ")}))`);
2881
+ }
2882
+ }
2883
+ if (alignments.length > 0) {
2884
+ const first_align = alignments[0].content;
2885
+ const all_same = alignments.every((item) => item.content === first_align);
2886
+ options2["align"] = all_same ? alignments[0] : new TypstNode("symbol", "#center");
2887
+ }
2888
+ }
2889
+ }
2890
+ res.setOptions(options2);
2891
+ return res;
2892
+ }
2820
2893
  if (node.content.endsWith("matrix")) {
2821
2894
  let delim;
2822
2895
  switch (node.content) {
@@ -2881,7 +2954,9 @@ var TYPST_UNARY_FUNCTIONS = [
2881
2954
  "cal",
2882
2955
  "frak",
2883
2956
  "floor",
2884
- "ceil"
2957
+ "ceil",
2958
+ "norm",
2959
+ "limits"
2885
2960
  ];
2886
2961
  var TYPST_BINARY_FUNCTIONS = [
2887
2962
  "frac",
@@ -2898,8 +2973,6 @@ function apply_escape_if_needed2(c) {
2898
2973
  function typst_token_to_tex(token) {
2899
2974
  if (/^[a-zA-Z0-9]$/.test(token)) {
2900
2975
  return token;
2901
- } else if (token === "thin") {
2902
- return "\\,";
2903
2976
  } else if (reverseSymbolMap.has(token)) {
2904
2977
  return "\\" + reverseSymbolMap.get(token);
2905
2978
  }
@@ -2964,15 +3037,21 @@ function convert_typst_node_to_tex(node) {
2964
3037
  return new TexNode("ordgroup", "", node.args.map(convert_typst_node_to_tex));
2965
3038
  }
2966
3039
  }
3040
+ if (node.content === "norm") {
3041
+ const arg0 = node.args[0];
3042
+ const tex_node_type = node.isOverHigh() ? "leftright" : "ordgroup";
3043
+ return new TexNode(tex_node_type, "", [
3044
+ new TexNode("symbol", "\\|"),
3045
+ convert_typst_node_to_tex(arg0),
3046
+ new TexNode("symbol", "\\|")
3047
+ ]);
3048
+ }
2967
3049
  if (node.content === "floor" || node.content === "ceil") {
2968
- let left = "\\l" + node.content;
2969
- let right = "\\r" + node.content;
3050
+ const left = "\\l" + node.content;
3051
+ const right = "\\r" + node.content;
2970
3052
  const arg0 = node.args[0];
2971
- if (arg0.isOverHigh()) {
2972
- left = "\\left" + left;
2973
- right = "\\right" + right;
2974
- }
2975
- return new TexNode("ordgroup", "", [
3053
+ const tex_node_type = node.isOverHigh() ? "leftright" : "ordgroup";
3054
+ return new TexNode(tex_node_type, "", [
2976
3055
  new TexNode("symbol", left),
2977
3056
  convert_typst_node_to_tex(arg0),
2978
3057
  new TexNode("symbol", right)
@@ -3010,7 +3089,6 @@ function convert_typst_node_to_tex(node) {
3010
3089
  }
3011
3090
  case "supsub": {
3012
3091
  const { base, sup, sub } = node.data;
3013
- const base_tex = convert_typst_node_to_tex(base);
3014
3092
  let sup_tex;
3015
3093
  let sub_tex;
3016
3094
  if (sup) {
@@ -3019,6 +3097,18 @@ function convert_typst_node_to_tex(node) {
3019
3097
  if (sub) {
3020
3098
  sub_tex = convert_typst_node_to_tex(sub);
3021
3099
  }
3100
+ if (base.eq(new TypstNode("funcCall", "limits"))) {
3101
+ const body_in_limits = convert_typst_node_to_tex(base.args[0]);
3102
+ if (sup_tex !== void 0 && sub_tex === void 0) {
3103
+ return new TexNode("binaryFunc", "\\overset", [sup_tex, body_in_limits]);
3104
+ } else if (sup_tex === void 0 && sub_tex !== void 0) {
3105
+ return new TexNode("binaryFunc", "\\underset", [sub_tex, body_in_limits]);
3106
+ } else {
3107
+ const underset_call = new TexNode("binaryFunc", "\\underset", [sub_tex, body_in_limits]);
3108
+ return new TexNode("binaryFunc", "\\overset", [sup_tex, underset_call]);
3109
+ }
3110
+ }
3111
+ const base_tex = convert_typst_node_to_tex(base);
3022
3112
  const res = new TexNode("supsub", "", [], {
3023
3113
  base: base_tex,
3024
3114
  sup: sup_tex,
@@ -3648,6 +3738,7 @@ function tex2typst(tex, options) {
3648
3738
  keepSpaces: false,
3649
3739
  fracToSlash: true,
3650
3740
  inftyToOo: false,
3741
+ optimize: true,
3651
3742
  nonAsciiWrapper: "",
3652
3743
  customTexMacros: {}
3653
3744
  };