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/src/convert.ts
CHANGED
|
@@ -22,9 +22,6 @@ function tex_token_to_typst(token: string): string {
|
|
|
22
22
|
return token;
|
|
23
23
|
} else if (token === '/') {
|
|
24
24
|
return '\\/';
|
|
25
|
-
} else if (token === '\\|') {
|
|
26
|
-
// \| in LaTeX is double vertical bar looks like ||
|
|
27
|
-
return 'parallel';
|
|
28
25
|
} else if (token === '\\\\') {
|
|
29
26
|
return '\\';
|
|
30
27
|
} else if (['\\$', '\\#', '\\&', '\\_'].includes(token)) {
|
|
@@ -49,40 +46,52 @@ function tex_token_to_typst(token: string): string {
|
|
|
49
46
|
function convert_overset(node: TexNode, options: Tex2TypstOptions): TypstNode {
|
|
50
47
|
const [sup, base] = node.args!;
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
// \overset{def}{=} is also considered as eq.def
|
|
57
|
-
if (n.type === 'ordgroup' && n.args!.length === 3) {
|
|
58
|
-
const [a1, a2, a3] = n.args!;
|
|
59
|
-
const d = new TexNode('element', 'd');
|
|
60
|
-
const e = new TexNode('element', 'e');
|
|
61
|
-
const f = new TexNode('element', 'f');
|
|
62
|
-
if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
|
|
49
|
+
if (options.optimize) {
|
|
50
|
+
const is_def = (n: TexNode): boolean => {
|
|
51
|
+
if (n.eq(new TexNode('text', 'def'))) {
|
|
63
52
|
return true;
|
|
64
53
|
}
|
|
54
|
+
// \overset{def}{=} is also considered as eq.def
|
|
55
|
+
if (n.type === 'ordgroup' && n.args!.length === 3) {
|
|
56
|
+
const [a1, a2, a3] = n.args!;
|
|
57
|
+
const d = new TexNode('element', 'd');
|
|
58
|
+
const e = new TexNode('element', 'e');
|
|
59
|
+
const f = new TexNode('element', 'f');
|
|
60
|
+
if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
};
|
|
66
|
+
const is_eq = (n: TexNode): boolean => n.eq(new TexNode('element', '='));
|
|
67
|
+
if (is_def(sup) && is_eq(base)) {
|
|
68
|
+
return new TypstNode('symbol', 'eq.def');
|
|
65
69
|
}
|
|
66
|
-
return false;
|
|
67
|
-
};
|
|
68
|
-
const is_eq = (n: TexNode): boolean => n.eq(new TexNode('element', '='));
|
|
69
|
-
if (is_def(sup) && is_eq(base)) {
|
|
70
|
-
return new TypstNode('symbol', 'eq.def');
|
|
71
70
|
}
|
|
72
71
|
const limits_call = new TypstNode(
|
|
73
72
|
'funcCall',
|
|
74
73
|
'limits',
|
|
75
74
|
[convert_tex_node_to_typst(base, options)]
|
|
76
75
|
);
|
|
77
|
-
return new TypstNode(
|
|
78
|
-
'supsub',
|
|
79
|
-
'',
|
|
80
|
-
[],
|
|
81
|
-
{
|
|
76
|
+
return new TypstNode('supsub', '', [], {
|
|
82
77
|
base: limits_call,
|
|
83
78
|
sup: convert_tex_node_to_typst(sup, options),
|
|
84
|
-
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// \underset{X}{Y} -> limits(Y)_X
|
|
83
|
+
function convert_underset(node: TexNode, options: Tex2TypstOptions): TypstNode {
|
|
84
|
+
const [sub, base] = node.args!;
|
|
85
|
+
|
|
86
|
+
const limits_call = new TypstNode(
|
|
87
|
+
'funcCall',
|
|
88
|
+
'limits',
|
|
89
|
+
[convert_tex_node_to_typst(base, options)]
|
|
85
90
|
);
|
|
91
|
+
return new TypstNode('supsub', '', [], {
|
|
92
|
+
base: limits_call,
|
|
93
|
+
sub: convert_tex_node_to_typst(sub, options),
|
|
94
|
+
});
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
|
|
@@ -117,7 +126,7 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
|
|
|
117
126
|
case 'supsub': {
|
|
118
127
|
let { base, sup, sub } = node.data as TexSupsubData;
|
|
119
128
|
|
|
120
|
-
//
|
|
129
|
+
// special hook for overbrace
|
|
121
130
|
if (base && base.type === 'unaryFunc' && base.content === '\\overbrace' && sup) {
|
|
122
131
|
return new TypstNode(
|
|
123
132
|
'funcCall',
|
|
@@ -150,40 +159,61 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
|
|
|
150
159
|
return new TypstNode('supsub', '', [], data);
|
|
151
160
|
}
|
|
152
161
|
case 'leftright': {
|
|
153
|
-
const [left,
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
const [left, _body, right] = node.args!;
|
|
163
|
+
const [typ_left, typ_body, typ_right] = node.args!.map((n) => convert_tex_node_to_typst(n, options));
|
|
164
|
+
|
|
165
|
+
if (options.optimize) {
|
|
166
|
+
// optimization off: "lr(bar.v.double a + 1/2 bar.v.double)"
|
|
167
|
+
// optimization on : "norm(a + 1/2)"
|
|
168
|
+
if (left.content === '\\|' && right.content === '\\|') {
|
|
169
|
+
return new TypstNode('funcCall', 'norm', [typ_body]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// These pairs will be handled by Typst compiler by default. No need to add lr()
|
|
173
|
+
if ([
|
|
174
|
+
"[]", "()", "\\{\\}",
|
|
175
|
+
"\\lfloor\\rfloor",
|
|
176
|
+
"\\lceil\\rceil",
|
|
177
|
+
"\\lfloor\\rceil",
|
|
178
|
+
].includes(left.content + right.content)) {
|
|
179
|
+
return new TypstNode('group', '', [typ_left, typ_body, typ_right]);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const group = new TypstNode(
|
|
156
184
|
'group',
|
|
157
185
|
'',
|
|
158
|
-
|
|
186
|
+
[typ_left, typ_body, typ_right]
|
|
159
187
|
);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
188
|
+
|
|
189
|
+
// "\left\{ a + \frac{1}{3} \right." -> "lr(\{ a + 1/3)"
|
|
190
|
+
// "\left. a + \frac{1}{3} \right\}" -> "lr( a + \frac{1}{3} \})"
|
|
191
|
+
// Note that: In lr(), if one side of delimiter doesn't present (i.e. derived from "\\left." or "\\right."),
|
|
192
|
+
// "(", ")", "{", "[", should be escaped with "\" to be the other side of delimiter.
|
|
193
|
+
// Simple "lr({ a+1/3)" doesn't compile in Typst.
|
|
194
|
+
const escape_curly_or_paren = function(s: string): string {
|
|
195
|
+
if (["(", ")", "{", "["].includes(s)) {
|
|
196
|
+
return "\\" + s;
|
|
197
|
+
} else {
|
|
198
|
+
return s;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
170
201
|
if (right.content === '.') {
|
|
171
|
-
|
|
172
|
-
|
|
202
|
+
typ_left.content = escape_curly_or_paren(typ_left.content);
|
|
203
|
+
group.args = [typ_left, typ_body];
|
|
173
204
|
} else if (left.content === '.') {
|
|
174
|
-
|
|
175
|
-
|
|
205
|
+
typ_right.content = escape_curly_or_paren(typ_right.content);
|
|
206
|
+
group.args = [typ_body, typ_right];
|
|
176
207
|
}
|
|
177
|
-
return new TypstNode(
|
|
178
|
-
'funcCall',
|
|
179
|
-
'lr',
|
|
180
|
-
[group]
|
|
181
|
-
);
|
|
208
|
+
return new TypstNode('funcCall', 'lr', [group]);
|
|
182
209
|
}
|
|
183
210
|
case 'binaryFunc': {
|
|
184
211
|
if (node.content === '\\overset') {
|
|
185
212
|
return convert_overset(node, options);
|
|
186
213
|
}
|
|
214
|
+
if (node.content === '\\underset') {
|
|
215
|
+
return convert_underset(node, options);
|
|
216
|
+
}
|
|
187
217
|
// \frac{a}{b} -> a / b
|
|
188
218
|
if (node.content === '\\frac') {
|
|
189
219
|
if (options.fracToSlash) {
|
|
@@ -409,6 +439,8 @@ const TYPST_UNARY_FUNCTIONS: string[] = [
|
|
|
409
439
|
'frak',
|
|
410
440
|
'floor',
|
|
411
441
|
'ceil',
|
|
442
|
+
'norm',
|
|
443
|
+
'limits',
|
|
412
444
|
];
|
|
413
445
|
|
|
414
446
|
const TYPST_BINARY_FUNCTIONS: string[] = [
|
|
@@ -428,8 +460,6 @@ function apply_escape_if_needed(c: string) {
|
|
|
428
460
|
function typst_token_to_tex(token: string): string {
|
|
429
461
|
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
430
462
|
return token;
|
|
431
|
-
} else if (token === 'thin') {
|
|
432
|
-
return '\\,';
|
|
433
463
|
} else if (reverseSymbolMap.has(token)) {
|
|
434
464
|
return '\\' + reverseSymbolMap.get(token)!;
|
|
435
465
|
}
|
|
@@ -491,6 +521,8 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
|
|
|
491
521
|
let left_delim = apply_escape_if_needed(data.leftDelim);
|
|
492
522
|
assert(data.rightDelim !== null, "leftDelim has value but rightDelim not");
|
|
493
523
|
let right_delim = apply_escape_if_needed(data.rightDelim!);
|
|
524
|
+
// TODO: should be TeXNode('leftright', ...)
|
|
525
|
+
// But currently writer will output `\left |` while people commonly prefer `\left|`.
|
|
494
526
|
return new TexNode('ordgroup', '', [
|
|
495
527
|
new TexNode('element', '\\left' + left_delim),
|
|
496
528
|
...node.args!.map(convert_typst_node_to_tex),
|
|
@@ -500,17 +532,29 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
|
|
|
500
532
|
return new TexNode('ordgroup', '', node.args!.map(convert_typst_node_to_tex));
|
|
501
533
|
}
|
|
502
534
|
}
|
|
535
|
+
// special hook for norm
|
|
536
|
+
// `\| a \|` <- `norm(a)`
|
|
537
|
+
// `\left\| a + \frac{1}{3} \right\|` <- `norm(a + 1/3)`
|
|
538
|
+
if (node.content === 'norm') {
|
|
539
|
+
const arg0 = node.args![0];
|
|
540
|
+
const tex_node_type = node.isOverHigh() ? 'leftright' : 'ordgroup';
|
|
541
|
+
return new TexNode(tex_node_type, '', [
|
|
542
|
+
new TexNode('symbol', "\\|"),
|
|
543
|
+
convert_typst_node_to_tex(arg0),
|
|
544
|
+
new TexNode('symbol', "\\|")
|
|
545
|
+
]);
|
|
546
|
+
}
|
|
503
547
|
// special hook for floor, ceil
|
|
504
|
-
//
|
|
548
|
+
// `\lfloor a \rfloor` <- `floor(a)`
|
|
549
|
+
// `\lceil a \rceil` <- `ceil(a)`
|
|
550
|
+
// `\left\lfloor a \right\rfloor` <- `floor(a)`
|
|
551
|
+
// `\left\lceil a \right\rceil` <- `ceil(a)`
|
|
505
552
|
if (node.content === 'floor' || node.content === 'ceil') {
|
|
506
|
-
|
|
507
|
-
|
|
553
|
+
const left = "\\l" + node.content;
|
|
554
|
+
const right = "\\r" + node.content;
|
|
508
555
|
const arg0 = node.args![0];
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
right = "\\right" + right;
|
|
512
|
-
}
|
|
513
|
-
return new TexNode('ordgroup', '', [
|
|
556
|
+
const tex_node_type = node.isOverHigh() ? 'leftright' : 'ordgroup';
|
|
557
|
+
return new TexNode(tex_node_type, '', [
|
|
514
558
|
new TexNode('symbol', left),
|
|
515
559
|
convert_typst_node_to_tex(arg0),
|
|
516
560
|
new TexNode('symbol', right)
|
|
@@ -552,15 +596,34 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
|
|
|
552
596
|
}
|
|
553
597
|
case 'supsub': {
|
|
554
598
|
const { base, sup, sub } = node.data as TypstSupsubData;
|
|
555
|
-
const base_tex = convert_typst_node_to_tex(base);
|
|
556
599
|
let sup_tex: TexNode | undefined;
|
|
557
600
|
let sub_tex: TexNode | undefined;
|
|
601
|
+
|
|
558
602
|
if (sup) {
|
|
559
603
|
sup_tex = convert_typst_node_to_tex(sup);
|
|
560
604
|
}
|
|
561
605
|
if (sub) {
|
|
562
606
|
sub_tex = convert_typst_node_to_tex(sub);
|
|
563
607
|
}
|
|
608
|
+
|
|
609
|
+
// special hook for limits
|
|
610
|
+
// `limits(+)^a` -> `\overset{a}{+}`
|
|
611
|
+
// `limits(+)_a` -> `\underset{a}{+}`
|
|
612
|
+
// `limits(+)_a^b` -> `\overset{b}{\underset{a}{+}}`
|
|
613
|
+
if (base.eq(new TypstNode('funcCall', 'limits'))) {
|
|
614
|
+
const body_in_limits = convert_typst_node_to_tex(base.args![0]);
|
|
615
|
+
if (sup_tex !== undefined && sub_tex === undefined) {
|
|
616
|
+
return new TexNode('binaryFunc', '\\overset', [sup_tex, body_in_limits]);
|
|
617
|
+
} else if (sup_tex === undefined && sub_tex !== undefined) {
|
|
618
|
+
return new TexNode('binaryFunc', '\\underset', [sub_tex, body_in_limits]);
|
|
619
|
+
} else {
|
|
620
|
+
const underset_call = new TexNode('binaryFunc', '\\underset', [sub_tex!, body_in_limits]);
|
|
621
|
+
return new TexNode('binaryFunc', '\\overset', [sup_tex!, underset_call]);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const base_tex = convert_typst_node_to_tex(base);
|
|
626
|
+
|
|
564
627
|
const res = new TexNode('supsub', '', [], {
|
|
565
628
|
base: base_tex,
|
|
566
629
|
sup: sup_tex,
|
package/src/index.ts
CHANGED
package/src/map.ts
CHANGED
package/src/tex-parser.ts
CHANGED
|
@@ -47,6 +47,7 @@ const BINARY_COMMANDS = [
|
|
|
47
47
|
'dfrac',
|
|
48
48
|
'tbinom',
|
|
49
49
|
'overset',
|
|
50
|
+
'underset',
|
|
50
51
|
]
|
|
51
52
|
|
|
52
53
|
const IGNORED_COMMANDS = [
|
|
@@ -86,7 +87,7 @@ function eat_whitespaces(tokens: TexToken[], start: number): TexToken[] {
|
|
|
86
87
|
|
|
87
88
|
function eat_parenthesis(tokens: TexToken[], start: number): TexToken | null {
|
|
88
89
|
const firstToken = tokens[start];
|
|
89
|
-
if (firstToken.type === TexTokenType.ELEMENT && ['(', ')', '[', ']', '|', '\\{', '\\}', '.'].includes(firstToken.value)) {
|
|
90
|
+
if (firstToken.type === TexTokenType.ELEMENT && ['(', ')', '[', ']', '|', '\\{', '\\}', '.', '\\|'].includes(firstToken.value)) {
|
|
90
91
|
return firstToken;
|
|
91
92
|
} else if (firstToken.type === TexTokenType.COMMAND && ['lfloor', 'rfloor', 'lceil', 'rceil', 'langle', 'rangle'].includes(firstToken.value.slice(1))) {
|
|
92
93
|
return firstToken;
|
|
@@ -166,7 +167,7 @@ const rules_map = new Map<string, (a: Scanner<TexToken>) => TexToken | TexToken[
|
|
|
166
167
|
],
|
|
167
168
|
[String.raw`%[^\n]*`, (s) => new TexToken(TexTokenType.COMMENT, s.text()!.substring(1))],
|
|
168
169
|
[String.raw`[{}_^&]`, (s) => new TexToken(TexTokenType.CONTROL, s.text()!)],
|
|
169
|
-
[String.raw`\\[
|
|
170
|
+
[String.raw`\\[\\,:;! ]`, (s) => new TexToken(TexTokenType.CONTROL, s.text()!)],
|
|
170
171
|
[String.raw`\r?\n`, (_s) => new TexToken(TexTokenType.NEWLINE, "\n")],
|
|
171
172
|
[String.raw`\s+`, (s) => new TexToken(TexTokenType.SPACE, s.text()!)],
|
|
172
173
|
[String.raw`\\[{}%$&#_|]`, (s) => new TexToken(TexTokenType.ELEMENT, s.text()!)],
|
|
@@ -394,6 +395,7 @@ export class LatexParser {
|
|
|
394
395
|
case '}':
|
|
395
396
|
throw new LatexParserError("Unmatched '}'");
|
|
396
397
|
case '\\\\':
|
|
398
|
+
case '\\!':
|
|
397
399
|
case '\\,':
|
|
398
400
|
case '\\:':
|
|
399
401
|
case '\\;':
|
package/src/types.ts
CHANGED
|
@@ -125,6 +125,13 @@ export class TexNode {
|
|
|
125
125
|
case 'ordgroup': {
|
|
126
126
|
return this.args!.map((n) => n.serialize()).flat();
|
|
127
127
|
}
|
|
128
|
+
case 'leftright': {
|
|
129
|
+
let tokens = this.args!.map((n) => n.serialize()).flat();
|
|
130
|
+
tokens.splice(0, 0, new TexToken(TexTokenType.COMMAND, '\\left'));
|
|
131
|
+
tokens.splice(tokens.length - 1, 0, new TexToken(TexTokenType.COMMAND, '\\right'));
|
|
132
|
+
|
|
133
|
+
return tokens;
|
|
134
|
+
}
|
|
128
135
|
case 'unaryFunc': {
|
|
129
136
|
let tokens: TexToken[] = [];
|
|
130
137
|
tokens.push(new TexToken(TexTokenType.COMMAND, this.content));
|
|
@@ -382,14 +389,21 @@ export const TYPST_NONE = new TypstNode('none', '#none');
|
|
|
382
389
|
export const TYPST_TRUE: TypstPrimitiveValue = true;
|
|
383
390
|
export const TYPST_FALSE: TypstPrimitiveValue = false;
|
|
384
391
|
|
|
392
|
+
/**
|
|
393
|
+
* ATTENTION:
|
|
394
|
+
* Don't use any options except those explicitly documented in
|
|
395
|
+
* https://github.com/qwinsi/tex2typst/blob/main/docs/api-reference.md
|
|
396
|
+
* Any undocumented options may break in the future!
|
|
397
|
+
*/
|
|
385
398
|
export interface Tex2TypstOptions {
|
|
386
|
-
nonStrict?: boolean;
|
|
387
|
-
preferTypstIntrinsic?: boolean;
|
|
388
|
-
preferShorthands?: boolean;
|
|
389
|
-
keepSpaces?: boolean;
|
|
390
|
-
fracToSlash?: boolean;
|
|
391
|
-
inftyToOo?: boolean;
|
|
392
|
-
|
|
399
|
+
nonStrict?: boolean; /** default is true */
|
|
400
|
+
preferTypstIntrinsic?: boolean; /** default is true */
|
|
401
|
+
preferShorthands?: boolean; /** default is true */
|
|
402
|
+
keepSpaces?: boolean; /** default is false */
|
|
403
|
+
fracToSlash?: boolean; /** default is true */
|
|
404
|
+
inftyToOo?: boolean; /** default is false */
|
|
405
|
+
optimize?: boolean; /** default is true */
|
|
406
|
+
nonAsciiWrapper?: string; /** default is "" */
|
|
393
407
|
customTexMacros?: { [key: string]: string };
|
|
394
408
|
// TODO: custom typst functions
|
|
395
409
|
}
|
package/src/typst-writer.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface TypstWriterOptions {
|
|
|
40
40
|
preferShorthands: boolean;
|
|
41
41
|
keepSpaces: boolean;
|
|
42
42
|
inftyToOo: boolean;
|
|
43
|
+
optimize: boolean;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export class TypstWriter {
|
|
@@ -47,17 +48,19 @@ export class TypstWriter {
|
|
|
47
48
|
private preferShorthands: boolean;
|
|
48
49
|
private keepSpaces: boolean;
|
|
49
50
|
private inftyToOo: boolean;
|
|
51
|
+
private optimize: boolean;
|
|
50
52
|
|
|
51
53
|
protected buffer: string = "";
|
|
52
54
|
protected queue: TypstToken[] = [];
|
|
53
55
|
|
|
54
56
|
private insideFunctionDepth = 0;
|
|
55
57
|
|
|
56
|
-
constructor(
|
|
57
|
-
this.nonStrict =
|
|
58
|
-
this.preferShorthands =
|
|
59
|
-
this.keepSpaces =
|
|
60
|
-
this.inftyToOo =
|
|
58
|
+
constructor(options: TypstWriterOptions) {
|
|
59
|
+
this.nonStrict = options.nonStrict;
|
|
60
|
+
this.preferShorthands = options.preferShorthands;
|
|
61
|
+
this.keepSpaces = options.keepSpaces;
|
|
62
|
+
this.inftyToOo = options.inftyToOo;
|
|
63
|
+
this.optimize = options.optimize;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
|
|
@@ -68,8 +71,6 @@ export class TypstWriter {
|
|
|
68
71
|
return;
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
// TODO: "C \frac{xy}{z}" should translate to "C (x y)/z" instead of "C(x y)/z"
|
|
72
|
-
|
|
73
74
|
let no_need_space = false;
|
|
74
75
|
// putting the first token in clause
|
|
75
76
|
no_need_space ||= /[\(\[\|]$/.test(this.buffer) && /^\w/.test(str);
|
|
@@ -163,7 +164,7 @@ export class TypstWriter {
|
|
|
163
164
|
// Put prime symbol before '_'. Because $y_1'$ is not displayed properly in Typst (so far)
|
|
164
165
|
// e.g.
|
|
165
166
|
// y_1' -> y'_1
|
|
166
|
-
// y_{a_1}' -> y'_
|
|
167
|
+
// y_{a_1}' -> y'_(a_1)
|
|
167
168
|
this.queue.push(new TypstToken(TypstTokenType.ELEMENT, '\''));
|
|
168
169
|
trailing_space_needed = false;
|
|
169
170
|
}
|
|
@@ -375,9 +376,11 @@ export class TypstWriter {
|
|
|
375
376
|
res = res.replace(/round\(\)/g, 'round("")');
|
|
376
377
|
return res;
|
|
377
378
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
379
|
+
if (this.optimize) {
|
|
380
|
+
const all_passes = [smartFloorPass, smartCeilPass, smartRoundPass];
|
|
381
|
+
for (const pass of all_passes) {
|
|
382
|
+
this.buffer = pass(this.buffer);
|
|
383
|
+
}
|
|
381
384
|
}
|
|
382
385
|
return this.buffer;
|
|
383
386
|
}
|
package/TODO.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
- Typst math `limits(Y)^X` to TeX `\overset{X}{Y}`
|
package/docs/api-reference.md
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# API Reference of tex2typst.js
|
|
2
|
-
|
|
3
|
-
## Basic usage
|
|
4
|
-
|
|
5
|
-
```javascript
|
|
6
|
-
import { tex2typst, typst2tex } from 'tex2typst';
|
|
7
|
-
|
|
8
|
-
let tex = "e \\overset{\\text{def}}{=} \\lim_{{n \\to \\infty}} \left(1 + \\frac{1}{n}\\right)^n";
|
|
9
|
-
let typst = tex2typst(tex);
|
|
10
|
-
console.log(typst);
|
|
11
|
-
// e eq.def lim_(n -> infinity)(1 + 1/n)^n
|
|
12
|
-
|
|
13
|
-
let tex_recovered = typst2tex(typst);
|
|
14
|
-
console.log(tex_recovered);
|
|
15
|
-
// e \overset{\text{def}}{=} \lim_{n \rightarrow \infty} \left(1 + \frac{1}{n} \right)^n
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Advanced options
|
|
19
|
-
|
|
20
|
-
`tex2typst` function accepts an optional second argument, which is an object containing options to customize the conversion.
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
interface Tex2TypstOptions {
|
|
24
|
-
preferShorthands?: boolean;
|
|
25
|
-
fracToSlash?: boolean;
|
|
26
|
-
inftyToOo?: boolean;
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
- `preferShorthands`: If set to `true`, the function will prefer using shorthands in Typst (e.g., `->` instead of `arrow.r`, `<<` instead of `lt.double`) when converting TeX to Typst. Default is `ture`.
|
|
31
|
-
|
|
32
|
-
```javascript
|
|
33
|
-
let tex = "a \\rightarrow b \\ll c";
|
|
34
|
-
let typst1 = tex2typst(tex, { preferShorthands: false });
|
|
35
|
-
console.log(typst1);
|
|
36
|
-
// a arrow.r b lt.double c
|
|
37
|
-
let typst2 = tex2typst(tex, { preferShorthands: true });
|
|
38
|
-
console.log(typst2);
|
|
39
|
-
// a -> b << c
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
- `fracToSlash`: If set to `true`, the Typst result will use the slash notation for fractions. Default is `true`.
|
|
43
|
-
|
|
44
|
-
```javascript
|
|
45
|
-
let tex = "\\frac{a}{b}";
|
|
46
|
-
let tpyst1 = tex2typst(tex, { fracToSlash: false });
|
|
47
|
-
console.log(typst1);
|
|
48
|
-
// frac(a, b)
|
|
49
|
-
let typst2 = tex2typst(tex, { fracToSlash: true });
|
|
50
|
-
console.log(typst2);
|
|
51
|
-
// a / b
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
- `inftyToOo`: If set to `true`, `\infty` converts to `oo` instead of `infinity`. Default is `false`.
|
|
55
|
-
|
|
56
|
-
```javascript
|
|
57
|
-
let tex = "\\infty";
|
|
58
|
-
let typst1 = tex2typst(tex, { inftyToOo: false });
|
|
59
|
-
console.log(typst1);
|
|
60
|
-
// infinity
|
|
61
|
-
let typst2 = tex2typst(tex, { inftyToOo: true });
|
|
62
|
-
console.log(typst2);
|
|
63
|
-
// oo
|
|
64
|
-
```
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import urllib.request
|
|
2
|
-
import html
|
|
3
|
-
from bs4 import BeautifulSoup
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if __name__ == '__main__':
|
|
7
|
-
shorthand_map = []
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
url = "https://typst.app/docs/reference/symbols/"
|
|
11
|
-
with urllib.request.urlopen(url) as response:
|
|
12
|
-
html_text = response.read().decode('utf-8')
|
|
13
|
-
|
|
14
|
-
soup = BeautifulSoup(html_text, 'html.parser')
|
|
15
|
-
|
|
16
|
-
# <ul class="symbol-grid">
|
|
17
|
-
ul_list = soup.find_all('ul', class_='symbol-grid')
|
|
18
|
-
# ul_shorthands_markup = ul_list[0]
|
|
19
|
-
ul_shorthands_math = ul_list[1]
|
|
20
|
-
|
|
21
|
-
li_list = ul_shorthands_math.find_all('li')
|
|
22
|
-
for li in li_list:
|
|
23
|
-
# e.g. <li id="symbol-arrow.r" data-math-shorthand="->"><button>...</button></li>
|
|
24
|
-
# ==> typst = "arrow.r"
|
|
25
|
-
# ==> shorthand = "->"
|
|
26
|
-
typst = li['id'][7:]
|
|
27
|
-
shorthand = html.unescape(li['data-math-shorthand'])
|
|
28
|
-
shorthand_map.append((typst, shorthand))
|
|
29
|
-
|
|
30
|
-
# Sort by length of shorthand, order from longest to shortest
|
|
31
|
-
shorthand_map.sort(key=lambda x: len(x[1]), reverse=True)
|
|
32
|
-
for typst, shorthand in shorthand_map:
|
|
33
|
-
print(f"['{typst}', '{shorthand}'],")
|
package/tools/make-symbol-map.py
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import urllib.request
|
|
2
|
-
from bs4 import BeautifulSoup
|
|
3
|
-
|
|
4
|
-
if __name__ == '__main__':
|
|
5
|
-
symbol_map = {}
|
|
6
|
-
|
|
7
|
-
url = "https://typst.app/docs/reference/symbols/sym/"
|
|
8
|
-
with urllib.request.urlopen(url) as response:
|
|
9
|
-
html_text = response.read().decode('utf-8')
|
|
10
|
-
|
|
11
|
-
soup = BeautifulSoup(html_text, 'html.parser')
|
|
12
|
-
# <ul class="symbol-grid">
|
|
13
|
-
ul = soup.find('ul', class_='symbol-grid')
|
|
14
|
-
li_list = ul.find_all('li')
|
|
15
|
-
for li in li_list:
|
|
16
|
-
# e.g. <li id="symbol-brace.r.double" data-latex-name="\rBrace" data-codepoint="10628"><button>...</button></li>
|
|
17
|
-
# ==> latex = rBrace
|
|
18
|
-
# ==> typst = brace.r.double
|
|
19
|
-
# ==> unicode = 10628 = \u2984
|
|
20
|
-
latex = li.get('data-latex-name', None)
|
|
21
|
-
typst = li['id'][7:]
|
|
22
|
-
unicode = int(li['data-codepoint'])
|
|
23
|
-
if latex is not None:
|
|
24
|
-
# some latex macro can be associated with multiple typst
|
|
25
|
-
# e.g. \equiv can be mapped to equal or equiv.triple
|
|
26
|
-
# We only keep the first one
|
|
27
|
-
if latex not in symbol_map:
|
|
28
|
-
symbol_map[latex] = typst
|
|
29
|
-
|
|
30
|
-
# sort the pairs with alphabetical order of latex
|
|
31
|
-
sorted_keys = sorted(list(symbol_map.keys()), key=str.lower)
|
|
32
|
-
sorted_symbol_map = [(key, symbol_map[key]) for key in sorted_keys]
|
|
33
|
-
for latex, typst in sorted_symbol_map:
|
|
34
|
-
print(f" ['{latex[1:]}', '{typst}'],")
|
|
35
|
-
# print(f'{latex[1:]} = "{typst}"')
|