tex2typst 0.2.11 → 0.2.13
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 +820 -31
- package/dist/map.d.ts +2 -1
- package/dist/tex2typst.min.js +1 -1
- package/dist/types.d.ts +3 -0
- package/package.json +3 -2
- package/src/map.ts +783 -32
- package/src/parser.ts +2 -1
- package/src/types.ts +2 -0
- package/src/writer.ts +67 -8
- package/tools/make-symbol-map.py +29 -0
package/src/parser.ts
CHANGED
|
@@ -41,6 +41,7 @@ const BINARY_COMMANDS = [
|
|
|
41
41
|
'dbinom',
|
|
42
42
|
'dfrac',
|
|
43
43
|
'tbinom',
|
|
44
|
+
'overset',
|
|
44
45
|
]
|
|
45
46
|
|
|
46
47
|
|
|
@@ -295,7 +296,7 @@ export function tokenize(latex: string): Token[] {
|
|
|
295
296
|
const firstTwoChars = latex.slice(pos, pos + 2);
|
|
296
297
|
if (['\\\\', '\\,'].includes(firstTwoChars)) {
|
|
297
298
|
token = new Token(TokenType.CONTROL, firstTwoChars);
|
|
298
|
-
} else if (['\\{','\\}', '\\%', '\\$', '\\&', '\\#', '\\_'].includes(firstTwoChars)) {
|
|
299
|
+
} else if (['\\{','\\}', '\\%', '\\$', '\\&', '\\#', '\\_', '\\|'].includes(firstTwoChars)) {
|
|
299
300
|
token = new Token(TokenType.ELEMENT, firstTwoChars);
|
|
300
301
|
} else {
|
|
301
302
|
const command = eat_command_name(latex, pos + 1);
|
package/src/types.ts
CHANGED
|
@@ -47,6 +47,8 @@ export interface TypstNode {
|
|
|
47
47
|
content: string;
|
|
48
48
|
args?: TypstNode[];
|
|
49
49
|
data?: TypstSupsubData | TypstArrayData;
|
|
50
|
+
// Some Typst functions accept additional options. e.g. mat() has option "delim", op() has option "limits"
|
|
51
|
+
options?: { [key: string]: string };
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export interface Tex2TypstOptions {
|
package/src/writer.ts
CHANGED
|
@@ -19,6 +19,54 @@ function is_delimiter(c: TypstNode): boolean {
|
|
|
19
19
|
return c.type === 'atom' && ['(', ')', '[', ']', '{', '}', '|', '⌊', '⌋', '⌈', '⌉'].includes(c.content);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function text_node_shallow_eq(a: TexNode, b: TexNode): boolean {
|
|
23
|
+
return (a.type === b.type) && (a.content === b.content);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// \overset{X}{Y} -> op(Y, limits: #true)^X
|
|
27
|
+
// and with special case \overset{\text{def}}{=} -> eq.def
|
|
28
|
+
function convert_overset(node: TexNode): TypstNode {
|
|
29
|
+
const [sup, base] = node.args!;
|
|
30
|
+
|
|
31
|
+
const is_def = (n: TexNode): boolean => {
|
|
32
|
+
if(n.type === 'text' && n.content === 'def') {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
// \overset{def}{=} is also considered as eq.def
|
|
36
|
+
if(n.type === 'ordgroup' && n.args!.length === 3) {
|
|
37
|
+
const [a1, a2, a3] = n.args!;
|
|
38
|
+
const d: TexNode = { type: 'element', content: 'd' };
|
|
39
|
+
const e: TexNode = { type: 'element', content: 'e' };
|
|
40
|
+
const f: TexNode = { type: 'element', content: 'f' };
|
|
41
|
+
if(text_node_shallow_eq(a1, d) && text_node_shallow_eq(a2, e) && text_node_shallow_eq(a3, f)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
};
|
|
47
|
+
const is_eq = (n: TexNode): boolean => (n.type === 'element' && n.content === '=');
|
|
48
|
+
if(is_def(sup) && is_eq(base)) {
|
|
49
|
+
return {
|
|
50
|
+
type: 'symbol',
|
|
51
|
+
content: 'eq.def',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const op_call: TypstNode = {
|
|
55
|
+
type: 'unaryFunc',
|
|
56
|
+
content: 'op',
|
|
57
|
+
args: [convertTree(base)],
|
|
58
|
+
options: { limits: '#true' },
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
type: 'supsub',
|
|
62
|
+
content: '',
|
|
63
|
+
data: {
|
|
64
|
+
base: op_call,
|
|
65
|
+
sup: convertTree(sup),
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
22
70
|
export class TypstWriterError extends Error {
|
|
23
71
|
node: TexNode | TypstNode;
|
|
24
72
|
|
|
@@ -148,6 +196,11 @@ export class TypstWriter {
|
|
|
148
196
|
this.insideFunctionDepth ++;
|
|
149
197
|
this.queue.push({ type: 'atom', content: '('});
|
|
150
198
|
this.append(arg0);
|
|
199
|
+
if(node.options) {
|
|
200
|
+
for (const [key, value] of Object.entries(node.options)) {
|
|
201
|
+
this.queue.push({ type: 'symbol', content: `, ${key}: ${value}`});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
151
204
|
this.queue.push({ type: 'atom', content: ')'});
|
|
152
205
|
this.insideFunctionDepth --;
|
|
153
206
|
break;
|
|
@@ -267,22 +320,22 @@ export class TypstWriter {
|
|
|
267
320
|
public finalize(): string {
|
|
268
321
|
this.flushQueue();
|
|
269
322
|
const smartFloorPass = function (input: string): string {
|
|
270
|
-
// Use regex to replace all "
|
|
271
|
-
let res = input.replace(
|
|
323
|
+
// Use regex to replace all "floor.l xxx floor.r" with "floor(xxx)"
|
|
324
|
+
let res = input.replace(/floor\.l\s*(.*?)\s*floor\.r/g, "floor($1)");
|
|
272
325
|
// Typst disallow "floor()" with empty argument, so add am empty string inside if it's empty.
|
|
273
326
|
res = res.replace(/floor\(\)/g, 'floor("")');
|
|
274
327
|
return res;
|
|
275
328
|
};
|
|
276
329
|
const smartCeilPass = function (input: string): string {
|
|
277
|
-
// Use regex to replace all "
|
|
278
|
-
let res = input.replace(
|
|
330
|
+
// Use regex to replace all "ceil.l xxx ceil.r" with "ceil(xxx)"
|
|
331
|
+
let res = input.replace(/ceil\.l\s*(.*?)\s*ceil\.r/g, "ceil($1)");
|
|
279
332
|
// Typst disallow "ceil()" with empty argument, so add an empty string inside if it's empty.
|
|
280
333
|
res = res.replace(/ceil\(\)/g, 'ceil("")');
|
|
281
334
|
return res;
|
|
282
335
|
}
|
|
283
336
|
const smartRoundPass = function (input: string): string {
|
|
284
|
-
// Use regex to replace all "
|
|
285
|
-
let res = input.replace(
|
|
337
|
+
// Use regex to replace all "floor.l xxx ceil.r" with "round(xxx)"
|
|
338
|
+
let res = input.replace(/floor\.l\s*(.*?)\s*ceil\.r/g, "round($1)");
|
|
286
339
|
// Typst disallow "round()" with empty argument, so add an empty string inside if it's empty.
|
|
287
340
|
res = res.replace(/round\(\)/g, 'round("")');
|
|
288
341
|
return res;
|
|
@@ -376,6 +429,9 @@ export function convertTree(node: TexNode): TypstNode {
|
|
|
376
429
|
};
|
|
377
430
|
}
|
|
378
431
|
case 'binaryFunc': {
|
|
432
|
+
if (node.content === '\\overset') {
|
|
433
|
+
return convert_overset(node);
|
|
434
|
+
}
|
|
379
435
|
return {
|
|
380
436
|
type: 'binaryFunc',
|
|
381
437
|
content: convertToken(node.content),
|
|
@@ -482,10 +538,13 @@ export function convertTree(node: TexNode): TypstNode {
|
|
|
482
538
|
function convertToken(token: string): string {
|
|
483
539
|
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
484
540
|
return token;
|
|
541
|
+
} else if (token === '/') {
|
|
542
|
+
return '\\/';
|
|
543
|
+
} else if (token === '\\|') {
|
|
544
|
+
// \| in LaTeX is double vertical bar looks like ||
|
|
545
|
+
return 'parallel';
|
|
485
546
|
} else if (token === '\\\\') {
|
|
486
547
|
return '\\';
|
|
487
|
-
} else if (token == '/') {
|
|
488
|
-
return '\\/';
|
|
489
548
|
} else if (['\\$', '\\#', '\\&', '\\_'].includes(token)) {
|
|
490
549
|
return token;
|
|
491
550
|
} else if (token.startsWith('\\')) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from bs4 import BeautifulSoup
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
if __name__ == '__main__':
|
|
6
|
+
symbol_map = {}
|
|
7
|
+
|
|
8
|
+
url = "https://typst.app/docs/reference/symbols/sym/"
|
|
9
|
+
html_text = requests.get(url).text
|
|
10
|
+
soup = BeautifulSoup(html_text, 'html.parser')
|
|
11
|
+
# <ul class="symbol-grid">
|
|
12
|
+
ul = soup.find('ul', class_='symbol-grid')
|
|
13
|
+
li_list = ul.find_all('li')
|
|
14
|
+
for li in li_list:
|
|
15
|
+
# e.g. <li id="symbol-brace.r.double" data-latex-name="\rBrace" data-codepoint="10628"><button>...</button></li>
|
|
16
|
+
# ==> latex = rBrace
|
|
17
|
+
# ==> typst = brace.r.double
|
|
18
|
+
# ==> unicode = 10628 = \u2984
|
|
19
|
+
latex = li.get('data-latex-name', None)
|
|
20
|
+
typst = li['id'][7:]
|
|
21
|
+
unicode = int(li['data-codepoint'])
|
|
22
|
+
if latex is not None:
|
|
23
|
+
# some latex macro can be associated with multiple typst
|
|
24
|
+
# e.g. \equiv can be mapped to equal or equiv.triple
|
|
25
|
+
# We only keep the first one
|
|
26
|
+
if latex not in symbol_map:
|
|
27
|
+
symbol_map[latex] = typst
|
|
28
|
+
# print(f" ['{latex[1:]}', '{typst}'],")
|
|
29
|
+
print(f'{latex[1:]} = "{typst}"')
|