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 +2 -2
- package/dist/index.js +230 -172
- package/dist/tex-parser.d.ts +0 -1
- package/dist/tex-tokenizer.d.ts +4 -0
- package/dist/tex2typst.min.js +11 -11
- package/dist/types.d.ts +13 -7
- package/dist/typst-parser.d.ts +0 -1
- package/dist/typst-tokenizer.d.ts +2 -0
- package/dist/typst-writer.d.ts +3 -1
- package/package.json +1 -1
- package/src/convert.ts +129 -69
- package/src/index.ts +1 -1
- package/src/map.ts +2 -0
- package/src/tex-parser.ts +6 -137
- package/src/tex-tokenizer.ts +138 -0
- package/src/types.ts +20 -7
- package/src/typst-parser.ts +1 -74
- package/src/typst-tokenizer.ts +76 -0
- package/src/typst-writer.ts +36 -18
- 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));
|
|
@@ -1625,8 +1633,8 @@ var JSLex = class {
|
|
|
1625
1633
|
}
|
|
1626
1634
|
};
|
|
1627
1635
|
|
|
1628
|
-
// src/tex-
|
|
1629
|
-
var
|
|
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
|
|
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`\\[
|
|
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 (
|
|
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 (
|
|
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(
|
|
2303
|
+
constructor(options) {
|
|
2289
2304
|
this.buffer = "";
|
|
2290
2305
|
this.queue = [];
|
|
2291
2306
|
this.insideFunctionDepth = 0;
|
|
2292
|
-
this.nonStrict =
|
|
2293
|
-
this.preferShorthands =
|
|
2294
|
-
this.keepSpaces =
|
|
2295
|
-
this.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(
|
|
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.
|
|
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
|
|
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
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
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
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
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
|
-
|
|
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)) {
|
|
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
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
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,
|
|
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
|
-
|
|
2746
|
+
[typ_left, typ_body, typ_right]
|
|
2701
2747
|
);
|
|
2702
|
-
|
|
2703
|
-
"[]
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
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
|
-
|
|
2714
|
-
|
|
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
|
-
|
|
2717
|
-
|
|
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
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
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
|
-
|
|
3010
|
-
|
|
3057
|
+
const left = "\\l" + node.content;
|
|
3058
|
+
const right = "\\r" + node.content;
|
|
3011
3059
|
const arg0 = node.args[0];
|
|
3012
|
-
|
|
3013
|
-
|
|
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-
|
|
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
|
};
|
package/dist/tex-parser.d.ts
CHANGED