tex2typst 0.3.0-beta-4 → 0.3.0-beta-6

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.js CHANGED
@@ -1100,8 +1100,17 @@ class TexNode {
1100
1100
  }
1101
1101
  return tokens;
1102
1102
  }
1103
- case "ordgroup":
1104
- return this.args.map((n) => n.serialize()).flat();
1103
+ case "ordgroup": {
1104
+ let tokens = this.args.map((n) => n.serialize()).flat();
1105
+ if (this.content === "parenthesis") {
1106
+ const is_over_high = this.isOverHigh();
1107
+ const left_delim = is_over_high ? "\\left(" : "(";
1108
+ const right_delim = is_over_high ? "\\right)" : ")";
1109
+ tokens.unshift(new TexToken(0 /* ELEMENT */, left_delim));
1110
+ tokens.push(new TexToken(0 /* ELEMENT */, right_delim));
1111
+ }
1112
+ return tokens;
1113
+ }
1105
1114
  case "unaryFunc": {
1106
1115
  let tokens = [];
1107
1116
  tokens.push(new TexToken(1 /* COMMAND */, this.content));
@@ -1199,6 +1208,30 @@ class TexNode {
1199
1208
  throw new Error("[TexNode.serialize] Unimplemented type: " + this.type);
1200
1209
  }
1201
1210
  }
1211
+ isOverHigh() {
1212
+ switch (this.type) {
1213
+ case "element":
1214
+ case "symbol":
1215
+ case "text":
1216
+ case "control":
1217
+ case "empty":
1218
+ return false;
1219
+ case "binaryFunc":
1220
+ if (this.content === "\\frac") {
1221
+ return true;
1222
+ }
1223
+ case "unaryFunc":
1224
+ case "ordgroup":
1225
+ return this.args.some((n) => n.isOverHigh());
1226
+ case "supsub": {
1227
+ return this.data.base.isOverHigh();
1228
+ }
1229
+ case "beginend":
1230
+ return true;
1231
+ default:
1232
+ return false;
1233
+ }
1234
+ }
1202
1235
  }
1203
1236
  class TypstToken {
1204
1237
  type;
@@ -1873,7 +1906,7 @@ function parseTex(tex, customTexMacros) {
1873
1906
  return parser.parse(tokens);
1874
1907
  }
1875
1908
 
1876
- // src/writer.ts
1909
+ // src/typst-writer.ts
1877
1910
  var TYPST_INTRINSIC_SYMBOLS = [
1878
1911
  "dim",
1879
1912
  "id",
@@ -1886,34 +1919,6 @@ var TYPST_INTRINSIC_SYMBOLS = [
1886
1919
  function is_delimiter(c) {
1887
1920
  return c.type === "atom" && ["(", ")", "[", "]", "{", "}", "|", "⌊", "⌋", "⌈", "⌉"].includes(c.content);
1888
1921
  }
1889
- function convert_overset(node) {
1890
- const [sup, base] = node.args;
1891
- const is_def = (n) => {
1892
- if (n.eq(new TexNode("text", "def"))) {
1893
- return true;
1894
- }
1895
- if (n.type === "ordgroup" && n.args.length === 3) {
1896
- const [a1, a2, a3] = n.args;
1897
- const d = new TexNode("element", "d");
1898
- const e = new TexNode("element", "e");
1899
- const f = new TexNode("element", "f");
1900
- if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
1901
- return true;
1902
- }
1903
- }
1904
- return false;
1905
- };
1906
- const is_eq = (n) => n.eq(new TexNode("element", "="));
1907
- if (is_def(sup) && is_eq(base)) {
1908
- return new TypstNode("symbol", "eq.def");
1909
- }
1910
- const op_call = new TypstNode("funcCall", "op", [convertTree(base)]);
1911
- op_call.setOptions({ limits: "#true" });
1912
- return new TypstNode("supsub", "", [], {
1913
- base: op_call,
1914
- sup: convertTree(sup)
1915
- });
1916
- }
1917
1922
  var TYPST_LEFT_PARENTHESIS = new TypstToken(1 /* ELEMENT */, "(");
1918
1923
  var TYPST_RIGHT_PARENTHESIS = new TypstToken(1 /* ELEMENT */, ")");
1919
1924
  var TYPST_COMMA = new TypstToken(1 /* ELEMENT */, ",");
@@ -1949,7 +1954,8 @@ class TypstWriter {
1949
1954
  let no_need_space = false;
1950
1955
  no_need_space ||= /[\(\[\|]$/.test(this.buffer) && /^\w/.test(str);
1951
1956
  no_need_space ||= /^[})\]\|]$/.test(str);
1952
- no_need_space ||= /^[(_^,;!]$/.test(str);
1957
+ no_need_space ||= /[^=]$/.test(this.buffer) && str === "(";
1958
+ no_need_space ||= /^[_^,;!]$/.test(str);
1953
1959
  no_need_space ||= str === "'";
1954
1960
  no_need_space ||= /[0-9]$/.test(this.buffer) && /^[0-9]/.test(str);
1955
1961
  no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
@@ -1958,6 +1964,7 @@ class TypstWriter {
1958
1964
  no_need_space ||= this.buffer === "";
1959
1965
  no_need_space ||= /^\s/.test(str);
1960
1966
  no_need_space ||= this.buffer.endsWith("&") && str === "=";
1967
+ no_need_space ||= this.buffer.endsWith("/") || str === "/";
1961
1968
  no_need_space ||= /[\s_^{\(]$/.test(this.buffer);
1962
1969
  if (!no_need_space) {
1963
1970
  this.buffer += " ";
@@ -2046,6 +2053,25 @@ class TypstWriter {
2046
2053
  this.insideFunctionDepth--;
2047
2054
  break;
2048
2055
  }
2056
+ case "fraction": {
2057
+ const [numerator, denominator] = node.args;
2058
+ if (numerator.type === "group") {
2059
+ this.queue.push(TYPST_LEFT_PARENTHESIS);
2060
+ this.serialize(numerator);
2061
+ this.queue.push(TYPST_RIGHT_PARENTHESIS);
2062
+ } else {
2063
+ this.serialize(numerator);
2064
+ }
2065
+ this.queue.push(new TypstToken(1 /* ELEMENT */, "/"));
2066
+ if (denominator.type === "group") {
2067
+ this.queue.push(TYPST_LEFT_PARENTHESIS);
2068
+ this.serialize(denominator);
2069
+ this.queue.push(TYPST_RIGHT_PARENTHESIS);
2070
+ } else {
2071
+ this.serialize(denominator);
2072
+ }
2073
+ break;
2074
+ }
2049
2075
  case "align": {
2050
2076
  const matrix = node.data;
2051
2077
  matrix.forEach((row, i) => {
@@ -2158,18 +2184,69 @@ class TypstWriter {
2158
2184
  return this.buffer;
2159
2185
  }
2160
2186
  }
2161
- function convertTree(node) {
2187
+
2188
+ // src/convert.ts
2189
+ function tex_token_to_typst(token) {
2190
+ if (/^[a-zA-Z0-9]$/.test(token)) {
2191
+ return token;
2192
+ } else if (token === "/") {
2193
+ return "\\/";
2194
+ } else if (token === "\\|") {
2195
+ return "parallel";
2196
+ } else if (token === "\\\\") {
2197
+ return "\\";
2198
+ } else if (["\\$", "\\#", "\\&", "\\_"].includes(token)) {
2199
+ return token;
2200
+ } else if (token.startsWith("\\")) {
2201
+ const symbol = token.slice(1);
2202
+ if (symbolMap.has(symbol)) {
2203
+ return symbolMap.get(symbol);
2204
+ } else {
2205
+ return symbol;
2206
+ }
2207
+ }
2208
+ return token;
2209
+ }
2210
+ function convert_overset(node, options) {
2211
+ const [sup, base] = node.args;
2212
+ const is_def = (n) => {
2213
+ if (n.eq(new TexNode("text", "def"))) {
2214
+ return true;
2215
+ }
2216
+ if (n.type === "ordgroup" && n.args.length === 3) {
2217
+ const [a1, a2, a3] = n.args;
2218
+ const d = new TexNode("element", "d");
2219
+ const e = new TexNode("element", "e");
2220
+ const f = new TexNode("element", "f");
2221
+ if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
2222
+ return true;
2223
+ }
2224
+ }
2225
+ return false;
2226
+ };
2227
+ const is_eq = (n) => n.eq(new TexNode("element", "="));
2228
+ if (is_def(sup) && is_eq(base)) {
2229
+ return new TypstNode("symbol", "eq.def");
2230
+ }
2231
+ const op_call = new TypstNode("funcCall", "op", [convert_tex_node_to_typst(base, options)]);
2232
+ op_call.setOptions({ limits: "#true" });
2233
+ return new TypstNode("supsub", "", [], {
2234
+ base: op_call,
2235
+ sup: convert_tex_node_to_typst(sup, options)
2236
+ });
2237
+ }
2238
+ function convert_tex_node_to_typst(node, options = {}) {
2162
2239
  switch (node.type) {
2163
2240
  case "empty":
2164
2241
  return new TypstNode("empty", "");
2165
2242
  case "whitespace":
2166
2243
  return new TypstNode("whitespace", node.content);
2167
2244
  case "ordgroup":
2168
- return new TypstNode("group", "", node.args.map(convertTree));
2245
+ return new TypstNode("group", "", node.args.map((n) => convert_tex_node_to_typst(n, options)));
2169
2246
  case "element":
2170
- return new TypstNode("atom", convertToken(node.content));
2247
+ return new TypstNode("atom", tex_token_to_typst(node.content));
2171
2248
  case "symbol":
2172
- return new TypstNode("symbol", convertToken(node.content));
2249
+ return new TypstNode("symbol", tex_token_to_typst(node.content));
2173
2250
  case "text":
2174
2251
  return new TypstNode("text", node.content);
2175
2252
  case "comment":
@@ -2177,27 +2254,27 @@ function convertTree(node) {
2177
2254
  case "supsub": {
2178
2255
  let { base, sup, sub } = node.data;
2179
2256
  if (base && base.type === "unaryFunc" && base.content === "\\overbrace" && sup) {
2180
- return new TypstNode("funcCall", "overbrace", [convertTree(base.args[0]), convertTree(sup)]);
2257
+ return new TypstNode("funcCall", "overbrace", [convert_tex_node_to_typst(base.args[0], options), convert_tex_node_to_typst(sup, options)]);
2181
2258
  } else if (base && base.type === "unaryFunc" && base.content === "\\underbrace" && sub) {
2182
- return new TypstNode("funcCall", "underbrace", [convertTree(base.args[0]), convertTree(sub)]);
2259
+ return new TypstNode("funcCall", "underbrace", [convert_tex_node_to_typst(base.args[0], options), convert_tex_node_to_typst(sub, options)]);
2183
2260
  }
2184
2261
  const data = {
2185
- base: convertTree(base)
2262
+ base: convert_tex_node_to_typst(base, options)
2186
2263
  };
2187
2264
  if (data.base.type === "empty") {
2188
2265
  data.base = new TypstNode("text", "");
2189
2266
  }
2190
2267
  if (sup) {
2191
- data.sup = convertTree(sup);
2268
+ data.sup = convert_tex_node_to_typst(sup, options);
2192
2269
  }
2193
2270
  if (sub) {
2194
- data.sub = convertTree(sub);
2271
+ data.sub = convert_tex_node_to_typst(sub, options);
2195
2272
  }
2196
2273
  return new TypstNode("supsub", "", [], data);
2197
2274
  }
2198
2275
  case "leftright": {
2199
2276
  const [left, body, right] = node.args;
2200
- const group = new TypstNode("group", "", node.args.map(convertTree));
2277
+ const group = new TypstNode("group", "", node.args.map((n) => convert_tex_node_to_typst(n, options)));
2201
2278
  if ([
2202
2279
  "[]",
2203
2280
  "()",
@@ -2219,14 +2296,19 @@ function convertTree(node) {
2219
2296
  }
2220
2297
  case "binaryFunc": {
2221
2298
  if (node.content === "\\overset") {
2222
- return convert_overset(node);
2299
+ return convert_overset(node, options);
2300
+ }
2301
+ if (node.content === "\\frac") {
2302
+ if (options.fracToSlash) {
2303
+ return new TypstNode("fraction", "", node.args.map((n) => convert_tex_node_to_typst(n, options)));
2304
+ }
2223
2305
  }
2224
- return new TypstNode("funcCall", convertToken(node.content), node.args.map(convertTree));
2306
+ return new TypstNode("funcCall", tex_token_to_typst(node.content), node.args.map((n) => convert_tex_node_to_typst(n, options)));
2225
2307
  }
2226
2308
  case "unaryFunc": {
2227
- const arg0 = convertTree(node.args[0]);
2309
+ const arg0 = convert_tex_node_to_typst(node.args[0], options);
2228
2310
  if (node.content === "\\sqrt" && node.data) {
2229
- const data = convertTree(node.data);
2311
+ const data = convert_tex_node_to_typst(node.data, options);
2230
2312
  return new TypstNode("funcCall", "root", [data, arg0]);
2231
2313
  }
2232
2314
  if (node.content === "\\mathbf") {
@@ -2248,11 +2330,11 @@ function convertTree(node) {
2248
2330
  return new TypstNode("funcCall", "op", [new TypstNode("text", text)]);
2249
2331
  }
2250
2332
  }
2251
- return new TypstNode("funcCall", convertToken(node.content), node.args.map(convertTree));
2333
+ return new TypstNode("funcCall", tex_token_to_typst(node.content), node.args.map((n) => convert_tex_node_to_typst(n, options)));
2252
2334
  }
2253
2335
  case "beginend": {
2254
2336
  const matrix = node.data;
2255
- const data = matrix.map((row) => row.map(convertTree));
2337
+ const data = matrix.map((row) => row.map((n) => convert_tex_node_to_typst(n, options)));
2256
2338
  if (node.content.startsWith("align")) {
2257
2339
  return new TypstNode("align", "", [], data);
2258
2340
  } else {
@@ -2262,7 +2344,7 @@ function convertTree(node) {
2262
2344
  }
2263
2345
  }
2264
2346
  case "unknownMacro":
2265
- return new TypstNode("unknown", convertToken(node.content));
2347
+ return new TypstNode("unknown", tex_token_to_typst(node.content));
2266
2348
  case "control":
2267
2349
  if (node.content === "\\\\") {
2268
2350
  return new TypstNode("symbol", "\\");
@@ -2275,26 +2357,205 @@ function convertTree(node) {
2275
2357
  throw new TypstWriterError(`Unimplemented node type: ${node.type}`, node);
2276
2358
  }
2277
2359
  }
2278
- function convertToken(token) {
2360
+ var TYPST_UNARY_FUNCTIONS = [
2361
+ "sqrt",
2362
+ "bold",
2363
+ "arrow",
2364
+ "upright",
2365
+ "lr",
2366
+ "op",
2367
+ "macron",
2368
+ "dot",
2369
+ "dot.double",
2370
+ "hat",
2371
+ "tilde",
2372
+ "overline",
2373
+ "underline",
2374
+ "bb",
2375
+ "cal",
2376
+ "frak"
2377
+ ];
2378
+ var TYPST_BINARY_FUNCTIONS = [
2379
+ "frac",
2380
+ "root",
2381
+ "overbrace",
2382
+ "underbrace"
2383
+ ];
2384
+ function apply_escape_if_needed2(c) {
2385
+ if (["{", "}", "%"].includes(c)) {
2386
+ return "\\" + c;
2387
+ }
2388
+ return c;
2389
+ }
2390
+ function typst_token_to_tex(token) {
2279
2391
  if (/^[a-zA-Z0-9]$/.test(token)) {
2280
2392
  return token;
2281
- } else if (token === "/") {
2282
- return "\\/";
2283
- } else if (token === "\\|") {
2284
- return "parallel";
2285
- } else if (token === "\\\\") {
2286
- return "\\";
2287
- } else if (["\\$", "\\#", "\\&", "\\_"].includes(token)) {
2288
- return token;
2289
- } else if (token.startsWith("\\")) {
2290
- const symbol = token.slice(1);
2291
- if (symbolMap.has(symbol)) {
2292
- return symbolMap.get(symbol);
2293
- } else {
2294
- return symbol;
2393
+ } else if (token === "thin") {
2394
+ return "\\,";
2395
+ } else if (reverseSymbolMap.has(token)) {
2396
+ return "\\" + reverseSymbolMap.get(token);
2397
+ }
2398
+ return "\\" + token;
2399
+ }
2400
+ function convert_typst_node_to_tex(node) {
2401
+ if (node.eq(new TypstNode("symbol", "eq.def"))) {
2402
+ return new TexNode("binaryFunc", "\\overset", [
2403
+ new TexNode("text", "def"),
2404
+ new TexNode("element", "=")
2405
+ ]);
2406
+ }
2407
+ switch (node.type) {
2408
+ case "empty":
2409
+ return new TexNode("empty", "");
2410
+ case "whitespace":
2411
+ return new TexNode("whitespace", node.content);
2412
+ case "atom":
2413
+ return new TexNode("element", node.content);
2414
+ case "symbol":
2415
+ switch (node.content) {
2416
+ case "comma":
2417
+ return new TexNode("element", ",");
2418
+ case "hyph":
2419
+ case "hyph.minus":
2420
+ return new TexNode("text", "-");
2421
+ default:
2422
+ return new TexNode("symbol", typst_token_to_tex(node.content));
2423
+ }
2424
+ case "text":
2425
+ return new TexNode("text", node.content);
2426
+ case "comment":
2427
+ return new TexNode("comment", node.content);
2428
+ case "group": {
2429
+ const args = node.args.map(convert_typst_node_to_tex);
2430
+ return new TexNode("ordgroup", node.content, args);
2431
+ }
2432
+ case "funcCall": {
2433
+ if (TYPST_UNARY_FUNCTIONS.includes(node.content)) {
2434
+ if (node.content === "lr") {
2435
+ const body = node.args[0];
2436
+ if (body.type === "group") {
2437
+ let left_delim = body.args[0].content;
2438
+ let right_delim = body.args[body.args.length - 1].content;
2439
+ left_delim = apply_escape_if_needed2(left_delim);
2440
+ right_delim = apply_escape_if_needed2(right_delim);
2441
+ return new TexNode("ordgroup", "", [
2442
+ new TexNode("element", "\\left" + left_delim),
2443
+ ...body.args.slice(1, body.args.length - 1).map(convert_typst_node_to_tex),
2444
+ new TexNode("element", "\\right" + right_delim)
2445
+ ]);
2446
+ }
2447
+ }
2448
+ const command = typst_token_to_tex(node.content);
2449
+ return new TexNode("unaryFunc", command, node.args.map(convert_typst_node_to_tex));
2450
+ } else if (TYPST_BINARY_FUNCTIONS.includes(node.content)) {
2451
+ if (node.content === "root") {
2452
+ const [degree, radicand] = node.args;
2453
+ const data = convert_typst_node_to_tex(degree);
2454
+ return new TexNode("unaryFunc", "\\sqrt", [convert_typst_node_to_tex(radicand)], data);
2455
+ }
2456
+ if (node.content === "overbrace" || node.content === "underbrace") {
2457
+ const [body, label] = node.args;
2458
+ const base = new TexNode("unaryFunc", "\\" + node.content, [convert_typst_node_to_tex(body)]);
2459
+ const script = convert_typst_node_to_tex(label);
2460
+ const data = node.content === "overbrace" ? { base, sup: script } : { base, sub: script };
2461
+ return new TexNode("supsub", "", [], data);
2462
+ }
2463
+ const command = typst_token_to_tex(node.content);
2464
+ return new TexNode("binaryFunc", command, node.args.map(convert_typst_node_to_tex));
2465
+ } else {
2466
+ return new TexNode("ordgroup", "", [
2467
+ new TexNode("symbol", typst_token_to_tex(node.content)),
2468
+ new TexNode("element", "("),
2469
+ ...node.args.map(convert_typst_node_to_tex),
2470
+ new TexNode("element", ")")
2471
+ ]);
2472
+ }
2295
2473
  }
2474
+ case "supsub": {
2475
+ const { base, sup, sub } = node.data;
2476
+ const base_tex = convert_typst_node_to_tex(base);
2477
+ let sup_tex;
2478
+ let sub_tex;
2479
+ if (sup) {
2480
+ sup_tex = convert_typst_node_to_tex(sup);
2481
+ }
2482
+ if (sub) {
2483
+ sub_tex = convert_typst_node_to_tex(sub);
2484
+ }
2485
+ const res = new TexNode("supsub", "", [], {
2486
+ base: base_tex,
2487
+ sup: sup_tex,
2488
+ sub: sub_tex
2489
+ });
2490
+ return res;
2491
+ }
2492
+ case "matrix": {
2493
+ const typst_data = node.data;
2494
+ const tex_data = typst_data.map((row) => row.map(convert_typst_node_to_tex));
2495
+ const matrix = new TexNode("beginend", "matrix", [], tex_data);
2496
+ let left_delim = "\\left(";
2497
+ let right_delim = "\\right)";
2498
+ if (node.options) {
2499
+ if ("delim" in node.options) {
2500
+ switch (node.options.delim) {
2501
+ case "#none":
2502
+ return matrix;
2503
+ case "[":
2504
+ left_delim = "\\left[";
2505
+ right_delim = "\\right]";
2506
+ break;
2507
+ case "]":
2508
+ left_delim = "\\left]";
2509
+ right_delim = "\\right[";
2510
+ break;
2511
+ case "{":
2512
+ left_delim = "\\left\\{";
2513
+ right_delim = "\\right\\}";
2514
+ break;
2515
+ case "}":
2516
+ left_delim = "\\left\\}";
2517
+ right_delim = "\\right\\{";
2518
+ break;
2519
+ case "|":
2520
+ left_delim = "\\left|";
2521
+ right_delim = "\\right|";
2522
+ break;
2523
+ case ")":
2524
+ left_delim = "\\left)";
2525
+ right_delim = "\\right(";
2526
+ case "(":
2527
+ default:
2528
+ left_delim = "\\left(";
2529
+ right_delim = "\\right)";
2530
+ break;
2531
+ }
2532
+ }
2533
+ }
2534
+ return new TexNode("ordgroup", "", [
2535
+ new TexNode("element", left_delim),
2536
+ matrix,
2537
+ new TexNode("element", right_delim)
2538
+ ]);
2539
+ }
2540
+ case "control": {
2541
+ switch (node.content) {
2542
+ case "\\":
2543
+ return new TexNode("control", "\\\\");
2544
+ case "&":
2545
+ return new TexNode("control", "&");
2546
+ default:
2547
+ throw new Error("[convert_typst_node_to_tex] Unimplemented control: " + node.content);
2548
+ }
2549
+ }
2550
+ case "fraction": {
2551
+ const [numerator, denominator] = node.args;
2552
+ const num_tex = convert_typst_node_to_tex(numerator);
2553
+ const den_tex = convert_typst_node_to_tex(denominator);
2554
+ return new TexNode("binaryFunc", "\\frac", [num_tex, den_tex]);
2555
+ }
2556
+ default:
2557
+ throw new Error("[convert_typst_node_to_tex] Unimplemented type: " + node.type);
2296
2558
  }
2297
- return token;
2298
2559
  }
2299
2560
 
2300
2561
  // src/typst-parser.ts
@@ -2793,37 +3054,6 @@ function parseTypst(typst) {
2793
3054
  }
2794
3055
 
2795
3056
  // src/tex-writer.ts
2796
- var TYPST_UNARY_FUNCTIONS = [
2797
- "sqrt",
2798
- "bold",
2799
- "arrow",
2800
- "upright",
2801
- "lr",
2802
- "op",
2803
- "macron",
2804
- "dot",
2805
- "dot.double",
2806
- "hat",
2807
- "tilde",
2808
- "overline",
2809
- "underline",
2810
- "bb",
2811
- "cal",
2812
- "frak"
2813
- ];
2814
- var TYPST_BINARY_FUNCTIONS = [
2815
- "frac",
2816
- "root",
2817
- "overbrace",
2818
- "underbrace"
2819
- ];
2820
- function apply_escape_if_needed2(c) {
2821
- if (["{", "}", "%"].includes(c)) {
2822
- return "\\" + c;
2823
- }
2824
- return c;
2825
- }
2826
-
2827
3057
  class TexWriter {
2828
3058
  buffer = "";
2829
3059
  queue = [];
@@ -2875,180 +3105,6 @@ class TexWriter {
2875
3105
  return this.buffer;
2876
3106
  }
2877
3107
  }
2878
- function convert_typst_node_to_tex(node) {
2879
- if (node.eq(new TypstNode("symbol", "eq.def"))) {
2880
- return new TexNode("binaryFunc", "\\overset", [
2881
- new TexNode("text", "def"),
2882
- new TexNode("element", "=")
2883
- ]);
2884
- }
2885
- switch (node.type) {
2886
- case "empty":
2887
- return new TexNode("empty", "");
2888
- case "whitespace":
2889
- return new TexNode("whitespace", node.content);
2890
- case "atom":
2891
- return new TexNode("element", node.content);
2892
- case "symbol":
2893
- switch (node.content) {
2894
- case "comma":
2895
- return new TexNode("element", ",");
2896
- case "hyph":
2897
- case "hyph.minus":
2898
- return new TexNode("text", "-");
2899
- default:
2900
- return new TexNode("symbol", typst_token_to_tex(node.content));
2901
- }
2902
- case "text":
2903
- return new TexNode("text", node.content);
2904
- case "comment":
2905
- return new TexNode("comment", node.content);
2906
- case "group": {
2907
- const args = node.args.map(convert_typst_node_to_tex);
2908
- if (node.content === "parenthesis") {
2909
- args.unshift(new TexNode("element", "("));
2910
- args.push(new TexNode("element", ")"));
2911
- }
2912
- return new TexNode("ordgroup", "", args);
2913
- }
2914
- case "funcCall": {
2915
- if (TYPST_UNARY_FUNCTIONS.includes(node.content)) {
2916
- if (node.content === "lr") {
2917
- const body = node.args[0];
2918
- if (body.type === "group") {
2919
- let left_delim = body.args[0].content;
2920
- let right_delim = body.args[body.args.length - 1].content;
2921
- left_delim = apply_escape_if_needed2(left_delim);
2922
- right_delim = apply_escape_if_needed2(right_delim);
2923
- return new TexNode("ordgroup", "", [
2924
- new TexNode("element", "\\left" + left_delim),
2925
- ...body.args.slice(1, body.args.length - 1).map(convert_typst_node_to_tex),
2926
- new TexNode("element", "\\right" + right_delim)
2927
- ]);
2928
- }
2929
- }
2930
- const command = typst_token_to_tex(node.content);
2931
- return new TexNode("unaryFunc", command, node.args.map(convert_typst_node_to_tex));
2932
- } else if (TYPST_BINARY_FUNCTIONS.includes(node.content)) {
2933
- if (node.content === "root") {
2934
- const [degree, radicand] = node.args;
2935
- const data = convert_typst_node_to_tex(degree);
2936
- return new TexNode("unaryFunc", "\\sqrt", [convert_typst_node_to_tex(radicand)], data);
2937
- }
2938
- if (node.content === "overbrace" || node.content === "underbrace") {
2939
- const [body, label] = node.args;
2940
- const base = new TexNode("unaryFunc", "\\" + node.content, [convert_typst_node_to_tex(body)]);
2941
- const script = convert_typst_node_to_tex(label);
2942
- const data = node.content === "overbrace" ? { base, sup: script } : { base, sub: script };
2943
- return new TexNode("supsub", "", [], data);
2944
- }
2945
- const command = typst_token_to_tex(node.content);
2946
- return new TexNode("binaryFunc", command, node.args.map(convert_typst_node_to_tex));
2947
- } else {
2948
- return new TexNode("ordgroup", "", [
2949
- new TexNode("symbol", typst_token_to_tex(node.content)),
2950
- new TexNode("element", "("),
2951
- ...node.args.map(convert_typst_node_to_tex),
2952
- new TexNode("element", ")")
2953
- ]);
2954
- }
2955
- }
2956
- case "supsub": {
2957
- const { base, sup, sub } = node.data;
2958
- const base_tex = convert_typst_node_to_tex(base);
2959
- let sup_tex;
2960
- let sub_tex;
2961
- if (sup) {
2962
- sup_tex = convert_typst_node_to_tex(sup);
2963
- }
2964
- if (sub) {
2965
- sub_tex = convert_typst_node_to_tex(sub);
2966
- }
2967
- const res = new TexNode("supsub", "", [], {
2968
- base: base_tex,
2969
- sup: sup_tex,
2970
- sub: sub_tex
2971
- });
2972
- return res;
2973
- }
2974
- case "matrix": {
2975
- const typst_data = node.data;
2976
- const tex_data = typst_data.map((row) => row.map(convert_typst_node_to_tex));
2977
- const matrix = new TexNode("beginend", "matrix", [], tex_data);
2978
- let left_delim = "\\left(";
2979
- let right_delim = "\\right)";
2980
- if (node.options) {
2981
- if ("delim" in node.options) {
2982
- switch (node.options.delim) {
2983
- case "#none":
2984
- return matrix;
2985
- case "[":
2986
- left_delim = "\\left[";
2987
- right_delim = "\\right]";
2988
- break;
2989
- case "]":
2990
- left_delim = "\\left]";
2991
- right_delim = "\\right[";
2992
- break;
2993
- case "{":
2994
- left_delim = "\\left\\{";
2995
- right_delim = "\\right\\}";
2996
- break;
2997
- case "}":
2998
- left_delim = "\\left\\}";
2999
- right_delim = "\\right\\{";
3000
- break;
3001
- case "|":
3002
- left_delim = "\\left|";
3003
- right_delim = "\\right|";
3004
- break;
3005
- case ")":
3006
- left_delim = "\\left)";
3007
- right_delim = "\\right(";
3008
- case "(":
3009
- default:
3010
- left_delim = "\\left(";
3011
- right_delim = "\\right)";
3012
- break;
3013
- }
3014
- }
3015
- }
3016
- return new TexNode("ordgroup", "", [
3017
- new TexNode("element", left_delim),
3018
- matrix,
3019
- new TexNode("element", right_delim)
3020
- ]);
3021
- }
3022
- case "control": {
3023
- switch (node.content) {
3024
- case "\\":
3025
- return new TexNode("control", "\\\\");
3026
- case "&":
3027
- return new TexNode("control", "&");
3028
- default:
3029
- throw new Error("[convert_typst_node_to_tex] Unimplemented control: " + node.content);
3030
- }
3031
- }
3032
- case "fraction": {
3033
- const [numerator, denominator] = node.args;
3034
- const num_tex = convert_typst_node_to_tex(numerator);
3035
- const den_tex = convert_typst_node_to_tex(denominator);
3036
- return new TexNode("binaryFunc", "\\frac", [num_tex, den_tex]);
3037
- }
3038
- default:
3039
- throw new Error("[convert_typst_node_to_tex] Unimplemented type: " + node.type);
3040
- }
3041
- }
3042
- function typst_token_to_tex(token) {
3043
- if (/^[a-zA-Z0-9]$/.test(token)) {
3044
- return token;
3045
- } else if (token === "thin") {
3046
- return "\\,";
3047
- } else if (reverseSymbolMap.has(token)) {
3048
- return "\\" + reverseSymbolMap.get(token);
3049
- }
3050
- return "\\" + token;
3051
- }
3052
3108
 
3053
3109
  // src/index.ts
3054
3110
  function tex2typst(tex, options) {
@@ -3056,6 +3112,7 @@ function tex2typst(tex, options) {
3056
3112
  nonStrict: true,
3057
3113
  preferTypstIntrinsic: true,
3058
3114
  keepSpaces: false,
3115
+ fracToSlash: true,
3059
3116
  customTexMacros: {}
3060
3117
  };
3061
3118
  if (options) {
@@ -3070,7 +3127,7 @@ function tex2typst(tex, options) {
3070
3127
  }
3071
3128
  }
3072
3129
  const texTree = parseTex(tex, opt.customTexMacros);
3073
- const typstTree = convertTree(texTree);
3130
+ const typstTree = convert_tex_node_to_typst(texTree, opt);
3074
3131
  const writer = new TypstWriter(opt.nonStrict, opt.preferTypstIntrinsic, opt.keepSpaces);
3075
3132
  writer.serialize(typstTree);
3076
3133
  return writer.finalize();