tex2typst 0.3.17 → 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 +2 -2
- package/dist/index.js +118 -68
- package/dist/tex2typst.min.js +11 -11
- package/dist/types.d.ts +14 -7
- package/dist/typst-writer.d.ts +3 -1
- package/package.json +1 -1
- package/src/convert.ts +123 -60
- package/src/index.ts +1 -0
- package/src/map.ts +2 -0
- package/src/tex-parser.ts +4 -2
- package/src/types.ts +21 -7
- package/src/typst-writer.ts +14 -11
- package/TODO.md +0 -1
- package/docs/api-reference.md +0 -64
- package/tools/make-shorthand-map.py +0 -33
- package/tools/make-symbol-map.py +0 -35
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
|
|
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
|
|
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`\\[
|
|
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 "\\;":
|
|
@@ -2285,14 +2295,15 @@ var TypstWriterError = class extends Error {
|
|
|
2285
2295
|
}
|
|
2286
2296
|
};
|
|
2287
2297
|
var TypstWriter = class {
|
|
2288
|
-
constructor(
|
|
2298
|
+
constructor(options) {
|
|
2289
2299
|
this.buffer = "";
|
|
2290
2300
|
this.queue = [];
|
|
2291
2301
|
this.insideFunctionDepth = 0;
|
|
2292
|
-
this.nonStrict =
|
|
2293
|
-
this.preferShorthands =
|
|
2294
|
-
this.keepSpaces =
|
|
2295
|
-
this.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;
|
|
2296
2307
|
}
|
|
2297
2308
|
writeBuffer(token) {
|
|
2298
2309
|
const str = token.toString();
|
|
@@ -2559,9 +2570,11 @@ var TypstWriter = class {
|
|
|
2559
2570
|
res = res.replace(/round\(\)/g, 'round("")');
|
|
2560
2571
|
return res;
|
|
2561
2572
|
};
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
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
|
+
}
|
|
2565
2578
|
}
|
|
2566
2579
|
return this.buffer;
|
|
2567
2580
|
}
|
|
@@ -2583,8 +2596,6 @@ function tex_token_to_typst(token) {
|
|
|
2583
2596
|
return token;
|
|
2584
2597
|
} else if (token === "/") {
|
|
2585
2598
|
return "\\/";
|
|
2586
|
-
} else if (token === "\\|") {
|
|
2587
|
-
return "parallel";
|
|
2588
2599
|
} else if (token === "\\\\") {
|
|
2589
2600
|
return "\\";
|
|
2590
2601
|
} else if (["\\$", "\\#", "\\&", "\\_"].includes(token)) {
|
|
@@ -2601,39 +2612,48 @@ function tex_token_to_typst(token) {
|
|
|
2601
2612
|
}
|
|
2602
2613
|
function convert_overset(node, options) {
|
|
2603
2614
|
const [sup, base] = node.args;
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
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)) {
|
|
2615
|
+
if (options.optimize) {
|
|
2616
|
+
const is_def = (n) => {
|
|
2617
|
+
if (n.eq(new TexNode("text", "def"))) {
|
|
2614
2618
|
return true;
|
|
2615
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");
|
|
2616
2634
|
}
|
|
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
2635
|
}
|
|
2623
2636
|
const limits_call = new TypstNode(
|
|
2624
2637
|
"funcCall",
|
|
2625
2638
|
"limits",
|
|
2626
2639
|
[convert_tex_node_to_typst(base, options)]
|
|
2627
2640
|
);
|
|
2628
|
-
return new TypstNode(
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
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)]
|
|
2636
2652
|
);
|
|
2653
|
+
return new TypstNode("supsub", "", [], {
|
|
2654
|
+
base: limits_call,
|
|
2655
|
+
sub: convert_tex_node_to_typst(sub, options)
|
|
2656
|
+
});
|
|
2637
2657
|
}
|
|
2638
2658
|
function convert_tex_node_to_typst(node, options = {}) {
|
|
2639
2659
|
switch (node.type) {
|
|
@@ -2693,39 +2713,51 @@ function convert_tex_node_to_typst(node, options = {}) {
|
|
|
2693
2713
|
return new TypstNode("supsub", "", [], data);
|
|
2694
2714
|
}
|
|
2695
2715
|
case "leftright": {
|
|
2696
|
-
const [left,
|
|
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
|
+
}
|
|
2697
2733
|
const group = new TypstNode(
|
|
2698
2734
|
"group",
|
|
2699
2735
|
"",
|
|
2700
|
-
|
|
2736
|
+
[typ_left, typ_body, typ_right]
|
|
2701
2737
|
);
|
|
2702
|
-
|
|
2703
|
-
"[]
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
].includes(left.content + right.content)) {
|
|
2710
|
-
return group;
|
|
2711
|
-
}
|
|
2738
|
+
const escape_curly_or_paren = function(s) {
|
|
2739
|
+
if (["(", ")", "{", "["].includes(s)) {
|
|
2740
|
+
return "\\" + s;
|
|
2741
|
+
} else {
|
|
2742
|
+
return s;
|
|
2743
|
+
}
|
|
2744
|
+
};
|
|
2712
2745
|
if (right.content === ".") {
|
|
2713
|
-
|
|
2714
|
-
|
|
2746
|
+
typ_left.content = escape_curly_or_paren(typ_left.content);
|
|
2747
|
+
group.args = [typ_left, typ_body];
|
|
2715
2748
|
} else if (left.content === ".") {
|
|
2716
|
-
|
|
2717
|
-
|
|
2749
|
+
typ_right.content = escape_curly_or_paren(typ_right.content);
|
|
2750
|
+
group.args = [typ_body, typ_right];
|
|
2718
2751
|
}
|
|
2719
|
-
return new TypstNode(
|
|
2720
|
-
"funcCall",
|
|
2721
|
-
"lr",
|
|
2722
|
-
[group]
|
|
2723
|
-
);
|
|
2752
|
+
return new TypstNode("funcCall", "lr", [group]);
|
|
2724
2753
|
}
|
|
2725
2754
|
case "binaryFunc": {
|
|
2726
2755
|
if (node.content === "\\overset") {
|
|
2727
2756
|
return convert_overset(node, options);
|
|
2728
2757
|
}
|
|
2758
|
+
if (node.content === "\\underset") {
|
|
2759
|
+
return convert_underset(node, options);
|
|
2760
|
+
}
|
|
2729
2761
|
if (node.content === "\\frac") {
|
|
2730
2762
|
if (options.fracToSlash) {
|
|
2731
2763
|
return new TypstNode(
|
|
@@ -2922,7 +2954,9 @@ var TYPST_UNARY_FUNCTIONS = [
|
|
|
2922
2954
|
"cal",
|
|
2923
2955
|
"frak",
|
|
2924
2956
|
"floor",
|
|
2925
|
-
"ceil"
|
|
2957
|
+
"ceil",
|
|
2958
|
+
"norm",
|
|
2959
|
+
"limits"
|
|
2926
2960
|
];
|
|
2927
2961
|
var TYPST_BINARY_FUNCTIONS = [
|
|
2928
2962
|
"frac",
|
|
@@ -2939,8 +2973,6 @@ function apply_escape_if_needed2(c) {
|
|
|
2939
2973
|
function typst_token_to_tex(token) {
|
|
2940
2974
|
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
2941
2975
|
return token;
|
|
2942
|
-
} else if (token === "thin") {
|
|
2943
|
-
return "\\,";
|
|
2944
2976
|
} else if (reverseSymbolMap.has(token)) {
|
|
2945
2977
|
return "\\" + reverseSymbolMap.get(token);
|
|
2946
2978
|
}
|
|
@@ -3005,15 +3037,21 @@ function convert_typst_node_to_tex(node) {
|
|
|
3005
3037
|
return new TexNode("ordgroup", "", node.args.map(convert_typst_node_to_tex));
|
|
3006
3038
|
}
|
|
3007
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
|
+
}
|
|
3008
3049
|
if (node.content === "floor" || node.content === "ceil") {
|
|
3009
|
-
|
|
3010
|
-
|
|
3050
|
+
const left = "\\l" + node.content;
|
|
3051
|
+
const right = "\\r" + node.content;
|
|
3011
3052
|
const arg0 = node.args[0];
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
right = "\\right" + right;
|
|
3015
|
-
}
|
|
3016
|
-
return new TexNode("ordgroup", "", [
|
|
3053
|
+
const tex_node_type = node.isOverHigh() ? "leftright" : "ordgroup";
|
|
3054
|
+
return new TexNode(tex_node_type, "", [
|
|
3017
3055
|
new TexNode("symbol", left),
|
|
3018
3056
|
convert_typst_node_to_tex(arg0),
|
|
3019
3057
|
new TexNode("symbol", right)
|
|
@@ -3051,7 +3089,6 @@ function convert_typst_node_to_tex(node) {
|
|
|
3051
3089
|
}
|
|
3052
3090
|
case "supsub": {
|
|
3053
3091
|
const { base, sup, sub } = node.data;
|
|
3054
|
-
const base_tex = convert_typst_node_to_tex(base);
|
|
3055
3092
|
let sup_tex;
|
|
3056
3093
|
let sub_tex;
|
|
3057
3094
|
if (sup) {
|
|
@@ -3060,6 +3097,18 @@ function convert_typst_node_to_tex(node) {
|
|
|
3060
3097
|
if (sub) {
|
|
3061
3098
|
sub_tex = convert_typst_node_to_tex(sub);
|
|
3062
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);
|
|
3063
3112
|
const res = new TexNode("supsub", "", [], {
|
|
3064
3113
|
base: base_tex,
|
|
3065
3114
|
sup: sup_tex,
|
|
@@ -3689,6 +3738,7 @@ function tex2typst(tex, options) {
|
|
|
3689
3738
|
keepSpaces: false,
|
|
3690
3739
|
fracToSlash: true,
|
|
3691
3740
|
inftyToOo: false,
|
|
3741
|
+
optimize: true,
|
|
3692
3742
|
nonAsciiWrapper: "",
|
|
3693
3743
|
customTexMacros: {}
|
|
3694
3744
|
};
|