tex2typst 0.3.0-beta-5 → 0.3.0-beta-7
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 +17 -35
- package/dist/convert.d.ts +3 -0
- package/dist/index.js +296 -265
- package/dist/tex-writer.d.ts +1 -3
- package/dist/tex2typst.min.js +14 -14
- package/dist/types.d.ts +1 -0
- package/dist/{writer.d.ts → typst-writer.d.ts} +1 -1
- package/package.json +1 -1
- package/src/convert.ts +478 -0
- package/src/index.ts +8 -3
- package/src/tex-writer.ts +1 -211
- package/src/types.ts +1 -0
- package/src/{writer.ts → typst-writer.ts} +26 -253
package/src/tex-writer.ts
CHANGED
|
@@ -1,39 +1,6 @@
|
|
|
1
1
|
import { array_includes, array_split } from "./generic";
|
|
2
2
|
import { reverseSymbolMap } from "./map";
|
|
3
|
-
import { TexNode, TexToken,
|
|
4
|
-
|
|
5
|
-
const TYPST_UNARY_FUNCTIONS: string[] = [
|
|
6
|
-
'sqrt',
|
|
7
|
-
'bold',
|
|
8
|
-
'arrow',
|
|
9
|
-
'upright',
|
|
10
|
-
'lr',
|
|
11
|
-
'op',
|
|
12
|
-
'macron',
|
|
13
|
-
'dot',
|
|
14
|
-
'dot.double',
|
|
15
|
-
'hat',
|
|
16
|
-
'tilde',
|
|
17
|
-
'overline',
|
|
18
|
-
'underline',
|
|
19
|
-
'bb',
|
|
20
|
-
'cal',
|
|
21
|
-
'frak',
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
const TYPST_BINARY_FUNCTIONS: string[] = [
|
|
25
|
-
'frac',
|
|
26
|
-
'root',
|
|
27
|
-
'overbrace',
|
|
28
|
-
'underbrace',
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
function apply_escape_if_needed(c: string) {
|
|
32
|
-
if (['{', '}', '%'].includes(c)) {
|
|
33
|
-
return '\\' + c;
|
|
34
|
-
}
|
|
35
|
-
return c;
|
|
36
|
-
}
|
|
3
|
+
import { TexNode, TexToken, TexSupsubData, TexTokenType } from "./types";
|
|
37
4
|
|
|
38
5
|
|
|
39
6
|
export class TexWriter {
|
|
@@ -106,180 +73,3 @@ export class TexWriter {
|
|
|
106
73
|
}
|
|
107
74
|
}
|
|
108
75
|
|
|
109
|
-
export function convert_typst_node_to_tex(node: TypstNode): TexNode {
|
|
110
|
-
// special hook for eq.def
|
|
111
|
-
if(node.eq(new TypstNode('symbol', 'eq.def'))) {
|
|
112
|
-
return new TexNode('binaryFunc', '\\overset', [
|
|
113
|
-
new TexNode('text', 'def'),
|
|
114
|
-
new TexNode('element', '=')
|
|
115
|
-
]);
|
|
116
|
-
}
|
|
117
|
-
switch (node.type) {
|
|
118
|
-
case 'empty':
|
|
119
|
-
return new TexNode('empty', '');
|
|
120
|
-
case 'whitespace':
|
|
121
|
-
return new TexNode('whitespace', node.content);
|
|
122
|
-
case 'atom':
|
|
123
|
-
return new TexNode('element', node.content);
|
|
124
|
-
case 'symbol':
|
|
125
|
-
switch(node.content) {
|
|
126
|
-
// special hook for comma
|
|
127
|
-
case 'comma':
|
|
128
|
-
return new TexNode('element', ',');
|
|
129
|
-
// special hook for hyph and hyph.minus
|
|
130
|
-
case 'hyph':
|
|
131
|
-
case 'hyph.minus':
|
|
132
|
-
return new TexNode('text', '-');
|
|
133
|
-
default:
|
|
134
|
-
return new TexNode('symbol', typst_token_to_tex(node.content));
|
|
135
|
-
}
|
|
136
|
-
case 'text':
|
|
137
|
-
return new TexNode('text', node.content);
|
|
138
|
-
case 'comment':
|
|
139
|
-
return new TexNode('comment', node.content);
|
|
140
|
-
case 'group': {
|
|
141
|
-
const args = node.args!.map(convert_typst_node_to_tex);
|
|
142
|
-
return new TexNode('ordgroup', node.content, args);
|
|
143
|
-
}
|
|
144
|
-
case 'funcCall': {
|
|
145
|
-
if (TYPST_UNARY_FUNCTIONS.includes(node.content)) {
|
|
146
|
-
// special hook for lr
|
|
147
|
-
if (node.content === 'lr') {
|
|
148
|
-
const body = node.args![0];
|
|
149
|
-
if (body.type === 'group') {
|
|
150
|
-
let left_delim = body.args![0].content;
|
|
151
|
-
let right_delim = body.args![body.args!.length - 1].content;
|
|
152
|
-
left_delim = apply_escape_if_needed(left_delim);
|
|
153
|
-
right_delim = apply_escape_if_needed(right_delim);
|
|
154
|
-
return new TexNode('ordgroup', '', [
|
|
155
|
-
new TexNode('element', '\\left' + left_delim),
|
|
156
|
-
...body.args!.slice(1, body.args!.length - 1).map(convert_typst_node_to_tex),
|
|
157
|
-
new TexNode('element', '\\right' + right_delim)
|
|
158
|
-
]);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
const command = typst_token_to_tex(node.content);
|
|
162
|
-
return new TexNode('unaryFunc', command, node.args!.map(convert_typst_node_to_tex));
|
|
163
|
-
} else if (TYPST_BINARY_FUNCTIONS.includes(node.content)) {
|
|
164
|
-
// special hook for root
|
|
165
|
-
if (node.content === 'root') {
|
|
166
|
-
const [degree, radicand] = node.args!;
|
|
167
|
-
const data: TexSqrtData = convert_typst_node_to_tex(degree);
|
|
168
|
-
return new TexNode('unaryFunc', '\\sqrt', [convert_typst_node_to_tex(radicand)], data);
|
|
169
|
-
}
|
|
170
|
-
// special hook for overbrace and underbrace
|
|
171
|
-
if (node.content === 'overbrace' || node.content === 'underbrace') {
|
|
172
|
-
const [body, label] = node.args!;
|
|
173
|
-
const base = new TexNode('unaryFunc', '\\' + node.content, [convert_typst_node_to_tex(body)]);
|
|
174
|
-
const script = convert_typst_node_to_tex(label);
|
|
175
|
-
const data = node.content === 'overbrace' ? { base, sup: script } : { base, sub: script };
|
|
176
|
-
return new TexNode('supsub', '', [], data);
|
|
177
|
-
}
|
|
178
|
-
const command = typst_token_to_tex(node.content);
|
|
179
|
-
return new TexNode('binaryFunc', command, node.args!.map(convert_typst_node_to_tex));
|
|
180
|
-
} else {
|
|
181
|
-
return new TexNode('ordgroup', '', [
|
|
182
|
-
new TexNode('symbol', typst_token_to_tex(node.content)),
|
|
183
|
-
new TexNode('element', '('),
|
|
184
|
-
...node.args!.map(convert_typst_node_to_tex),
|
|
185
|
-
new TexNode('element', ')')
|
|
186
|
-
])
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
case 'supsub': {
|
|
190
|
-
const { base, sup, sub } = node.data as TypstSupsubData;
|
|
191
|
-
const base_tex = convert_typst_node_to_tex(base);
|
|
192
|
-
let sup_tex: TexNode | undefined;
|
|
193
|
-
let sub_tex: TexNode | undefined;
|
|
194
|
-
if (sup) {
|
|
195
|
-
sup_tex = convert_typst_node_to_tex(sup);
|
|
196
|
-
}
|
|
197
|
-
if (sub) {
|
|
198
|
-
sub_tex = convert_typst_node_to_tex(sub);
|
|
199
|
-
}
|
|
200
|
-
const res = new TexNode('supsub', '', [], {
|
|
201
|
-
base: base_tex,
|
|
202
|
-
sup: sup_tex,
|
|
203
|
-
sub: sub_tex
|
|
204
|
-
});
|
|
205
|
-
return res;
|
|
206
|
-
}
|
|
207
|
-
case 'matrix': {
|
|
208
|
-
const typst_data = node.data as TypstNode[][];
|
|
209
|
-
const tex_data = typst_data.map(row => row.map(convert_typst_node_to_tex));
|
|
210
|
-
const matrix = new TexNode('beginend', 'matrix', [], tex_data);
|
|
211
|
-
let left_delim = "\\left(";
|
|
212
|
-
let right_delim = "\\right)";
|
|
213
|
-
if (node.options) {
|
|
214
|
-
if('delim' in node.options) {
|
|
215
|
-
switch (node.options.delim) {
|
|
216
|
-
case '#none':
|
|
217
|
-
return matrix;
|
|
218
|
-
case '[':
|
|
219
|
-
left_delim = "\\left[";
|
|
220
|
-
right_delim = "\\right]";
|
|
221
|
-
break;
|
|
222
|
-
case ']':
|
|
223
|
-
left_delim = "\\left]";
|
|
224
|
-
right_delim = "\\right[";
|
|
225
|
-
break;
|
|
226
|
-
case '{':
|
|
227
|
-
left_delim = "\\left\\{";
|
|
228
|
-
right_delim = "\\right\\}";
|
|
229
|
-
break;
|
|
230
|
-
case '}':
|
|
231
|
-
left_delim = "\\left\\}";
|
|
232
|
-
right_delim = "\\right\\{";
|
|
233
|
-
break;
|
|
234
|
-
case '|':
|
|
235
|
-
left_delim = "\\left|";
|
|
236
|
-
right_delim = "\\right|";
|
|
237
|
-
break;
|
|
238
|
-
case ')':
|
|
239
|
-
left_delim = "\\left)";
|
|
240
|
-
right_delim = "\\right(";
|
|
241
|
-
case '(':
|
|
242
|
-
default:
|
|
243
|
-
left_delim = "\\left(";
|
|
244
|
-
right_delim = "\\right)";
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return new TexNode('ordgroup', '', [
|
|
250
|
-
new TexNode('element', left_delim),
|
|
251
|
-
matrix,
|
|
252
|
-
new TexNode('element', right_delim)
|
|
253
|
-
]);
|
|
254
|
-
}
|
|
255
|
-
case 'control': {
|
|
256
|
-
switch (node.content) {
|
|
257
|
-
case '\\':
|
|
258
|
-
return new TexNode('control', '\\\\');
|
|
259
|
-
case '&':
|
|
260
|
-
return new TexNode('control', '&');
|
|
261
|
-
default:
|
|
262
|
-
throw new Error('[convert_typst_node_to_tex] Unimplemented control: ' + node.content);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
case 'fraction': {
|
|
266
|
-
const [numerator, denominator] = node.args!;
|
|
267
|
-
const num_tex = convert_typst_node_to_tex(numerator);
|
|
268
|
-
const den_tex = convert_typst_node_to_tex(denominator);
|
|
269
|
-
return new TexNode('binaryFunc', '\\frac', [num_tex, den_tex]);
|
|
270
|
-
}
|
|
271
|
-
default:
|
|
272
|
-
throw new Error('[convert_typst_node_to_tex] Unimplemented type: ' + node.type);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export function typst_token_to_tex(token: string): string {
|
|
277
|
-
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
278
|
-
return token;
|
|
279
|
-
} else if (token === 'thin') {
|
|
280
|
-
return '\\,';
|
|
281
|
-
} else if (reverseSymbolMap.has(token)) {
|
|
282
|
-
return '\\' + reverseSymbolMap.get(token)!;
|
|
283
|
-
}
|
|
284
|
-
return '\\' + token;
|
|
285
|
-
}
|
package/src/types.ts
CHANGED
|
@@ -384,6 +384,7 @@ export interface Tex2TypstOptions {
|
|
|
384
384
|
nonStrict?: boolean; // default is true
|
|
385
385
|
preferTypstIntrinsic?: boolean; // default is true,
|
|
386
386
|
keepSpaces?: boolean; // default is false
|
|
387
|
+
fracToSlash?: boolean; // default is true
|
|
387
388
|
customTexMacros?: { [key: string]: string };
|
|
388
389
|
// TODO: custom typst functions
|
|
389
390
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { TexNode, TexSqrtData, TexSupsubData, TypstNode, TypstSupsubData, TypstToken, TypstTokenType } from "./types";
|
|
1
|
+
import { TexNode, TypstNode, TypstSupsubData, TypstToken, TypstTokenType } from "./types";
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
// symbols that are supported by Typst but not by KaTeX
|
|
6
|
-
const TYPST_INTRINSIC_SYMBOLS = [
|
|
5
|
+
export const TYPST_INTRINSIC_SYMBOLS = [
|
|
7
6
|
'dim',
|
|
8
7
|
'id',
|
|
9
8
|
'im',
|
|
@@ -20,48 +19,6 @@ function is_delimiter(c: TypstNode): boolean {
|
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
|
|
23
|
-
// \overset{X}{Y} -> op(Y, limits: #true)^X
|
|
24
|
-
// and with special case \overset{\text{def}}{=} -> eq.def
|
|
25
|
-
function convert_overset(node: TexNode): TypstNode {
|
|
26
|
-
const [sup, base] = node.args!;
|
|
27
|
-
|
|
28
|
-
const is_def = (n: TexNode): boolean => {
|
|
29
|
-
if (n.eq(new TexNode('text', 'def'))) {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
// \overset{def}{=} is also considered as eq.def
|
|
33
|
-
if (n.type === 'ordgroup' && n.args!.length === 3) {
|
|
34
|
-
const [a1, a2, a3] = n.args!;
|
|
35
|
-
const d = new TexNode('element', 'd');
|
|
36
|
-
const e = new TexNode('element', 'e');
|
|
37
|
-
const f = new TexNode('element', 'f');
|
|
38
|
-
if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return false;
|
|
43
|
-
};
|
|
44
|
-
const is_eq = (n: TexNode): boolean => n.eq(new TexNode('element', '='));
|
|
45
|
-
if (is_def(sup) && is_eq(base)) {
|
|
46
|
-
return new TypstNode('symbol', 'eq.def');
|
|
47
|
-
}
|
|
48
|
-
const op_call = new TypstNode(
|
|
49
|
-
'funcCall',
|
|
50
|
-
'op',
|
|
51
|
-
[convertTree(base)]
|
|
52
|
-
);
|
|
53
|
-
op_call.setOptions({ limits: '#true' });
|
|
54
|
-
return new TypstNode(
|
|
55
|
-
'supsub',
|
|
56
|
-
'',
|
|
57
|
-
[],
|
|
58
|
-
{
|
|
59
|
-
base: op_call,
|
|
60
|
-
sup: convertTree(sup),
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
22
|
const TYPST_LEFT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '(');
|
|
66
23
|
const TYPST_RIGHT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ELEMENT, ')');
|
|
67
24
|
const TYPST_COMMA: TypstToken = new TypstToken(TypstTokenType.ELEMENT, ',');
|
|
@@ -106,8 +63,10 @@ export class TypstWriter {
|
|
|
106
63
|
no_need_space ||= /[\(\[\|]$/.test(this.buffer) && /^\w/.test(str);
|
|
107
64
|
// closing a clause
|
|
108
65
|
no_need_space ||= /^[})\]\|]$/.test(str);
|
|
66
|
+
// putting the opening '(' for a function
|
|
67
|
+
no_need_space ||= /[^=]$/.test(this.buffer) && str === '(';
|
|
109
68
|
// putting punctuation
|
|
110
|
-
no_need_space ||= /^[
|
|
69
|
+
no_need_space ||= /^[_^,;!]$/.test(str);
|
|
111
70
|
// putting a prime
|
|
112
71
|
no_need_space ||= str === "'";
|
|
113
72
|
// continue a number
|
|
@@ -122,6 +81,8 @@ export class TypstWriter {
|
|
|
122
81
|
no_need_space ||= /^\s/.test(str);
|
|
123
82
|
// "&=" instead of "& ="
|
|
124
83
|
no_need_space ||= this.buffer.endsWith('&') && str === '=';
|
|
84
|
+
// before or after a slash e.g. "a/b" instead of "a / b"
|
|
85
|
+
no_need_space ||= this.buffer.endsWith('/') || str === '/';
|
|
125
86
|
// other cases
|
|
126
87
|
no_need_space ||= /[\s_^{\(]$/.test(this.buffer);
|
|
127
88
|
if (!no_need_space) {
|
|
@@ -218,6 +179,25 @@ export class TypstWriter {
|
|
|
218
179
|
this.insideFunctionDepth--;
|
|
219
180
|
break;
|
|
220
181
|
}
|
|
182
|
+
case 'fraction': {
|
|
183
|
+
const [numerator, denominator] = node.args!;
|
|
184
|
+
if(numerator.type === 'group') {
|
|
185
|
+
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
186
|
+
this.serialize(numerator);
|
|
187
|
+
this.queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
188
|
+
} else {
|
|
189
|
+
this.serialize(numerator);
|
|
190
|
+
}
|
|
191
|
+
this.queue.push(new TypstToken(TypstTokenType.ELEMENT, '/'));
|
|
192
|
+
if(denominator.type === 'group') {
|
|
193
|
+
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
194
|
+
this.serialize(denominator);
|
|
195
|
+
this.queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
196
|
+
} else {
|
|
197
|
+
this.serialize(denominator);
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
221
201
|
case 'align': {
|
|
222
202
|
const matrix = node.data as TypstNode[][];
|
|
223
203
|
matrix.forEach((row, i) => {
|
|
@@ -355,210 +335,3 @@ export class TypstWriter {
|
|
|
355
335
|
return this.buffer;
|
|
356
336
|
}
|
|
357
337
|
}
|
|
358
|
-
|
|
359
|
-
// Convert a tree of TexNode into a tree of TypstNode
|
|
360
|
-
export function convertTree(node: TexNode): TypstNode {
|
|
361
|
-
switch (node.type) {
|
|
362
|
-
case 'empty':
|
|
363
|
-
return new TypstNode('empty', '');
|
|
364
|
-
case 'whitespace':
|
|
365
|
-
return new TypstNode('whitespace', node.content);
|
|
366
|
-
case 'ordgroup':
|
|
367
|
-
return new TypstNode(
|
|
368
|
-
'group',
|
|
369
|
-
'',
|
|
370
|
-
node.args!.map(convertTree),
|
|
371
|
-
);
|
|
372
|
-
case 'element':
|
|
373
|
-
return new TypstNode('atom', convertToken(node.content));
|
|
374
|
-
case 'symbol':
|
|
375
|
-
return new TypstNode('symbol', convertToken(node.content));
|
|
376
|
-
case 'text':
|
|
377
|
-
return new TypstNode('text', node.content);
|
|
378
|
-
case 'comment':
|
|
379
|
-
return new TypstNode('comment', node.content);
|
|
380
|
-
case 'supsub': {
|
|
381
|
-
let { base, sup, sub } = node.data as TexSupsubData;
|
|
382
|
-
|
|
383
|
-
// Special logic for overbrace
|
|
384
|
-
if (base && base.type === 'unaryFunc' && base.content === '\\overbrace' && sup) {
|
|
385
|
-
return new TypstNode(
|
|
386
|
-
'funcCall',
|
|
387
|
-
'overbrace',
|
|
388
|
-
[convertTree(base.args![0]), convertTree(sup)],
|
|
389
|
-
);
|
|
390
|
-
} else if (base && base.type === 'unaryFunc' && base.content === '\\underbrace' && sub) {
|
|
391
|
-
return new TypstNode(
|
|
392
|
-
'funcCall',
|
|
393
|
-
'underbrace',
|
|
394
|
-
[convertTree(base.args![0]), convertTree(sub)],
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const data: TypstSupsubData = {
|
|
399
|
-
base: convertTree(base),
|
|
400
|
-
};
|
|
401
|
-
if (data.base.type === 'empty') {
|
|
402
|
-
data.base = new TypstNode('text', '');
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (sup) {
|
|
406
|
-
data.sup = convertTree(sup);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (sub) {
|
|
410
|
-
data.sub = convertTree(sub);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return new TypstNode('supsub', '', [], data);
|
|
414
|
-
}
|
|
415
|
-
case 'leftright': {
|
|
416
|
-
const [left, body, right] = node.args!;
|
|
417
|
-
// These pairs will be handled by Typst compiler by default. No need to add lr()
|
|
418
|
-
const group: TypstNode = new TypstNode(
|
|
419
|
-
'group',
|
|
420
|
-
'',
|
|
421
|
-
node.args!.map(convertTree),
|
|
422
|
-
);
|
|
423
|
-
if ([
|
|
424
|
-
"[]", "()", "\\{\\}",
|
|
425
|
-
"\\lfloor\\rfloor",
|
|
426
|
-
"\\lceil\\rceil",
|
|
427
|
-
"\\lfloor\\rceil",
|
|
428
|
-
].includes(left.content + right.content)) {
|
|
429
|
-
return group;
|
|
430
|
-
}
|
|
431
|
-
// "\left\{ A \right." -> "{A"
|
|
432
|
-
// "\left. A \right\}" -> "lr( A} )"
|
|
433
|
-
if(right.content === '.') {
|
|
434
|
-
group.args!.pop();
|
|
435
|
-
return group;
|
|
436
|
-
} else if(left.content === '.') {
|
|
437
|
-
group.args!.shift();
|
|
438
|
-
return new TypstNode( 'funcCall', 'lr', [group]);
|
|
439
|
-
}
|
|
440
|
-
return new TypstNode(
|
|
441
|
-
'funcCall',
|
|
442
|
-
'lr',
|
|
443
|
-
[group],
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
case 'binaryFunc': {
|
|
447
|
-
if (node.content === '\\overset') {
|
|
448
|
-
return convert_overset(node);
|
|
449
|
-
}
|
|
450
|
-
return new TypstNode(
|
|
451
|
-
'funcCall',
|
|
452
|
-
convertToken(node.content),
|
|
453
|
-
node.args!.map(convertTree),
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
case 'unaryFunc': {
|
|
457
|
-
const arg0 = convertTree(node.args![0]);
|
|
458
|
-
// \sqrt{3}{x} -> root(3, x)
|
|
459
|
-
if (node.content === '\\sqrt' && node.data) {
|
|
460
|
-
const data = convertTree(node.data as TexSqrtData); // the number of times to take the root
|
|
461
|
-
return new TypstNode(
|
|
462
|
-
'funcCall',
|
|
463
|
-
'root',
|
|
464
|
-
[data, arg0],
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
// \mathbf{a} -> upright(mathbf(a))
|
|
468
|
-
if (node.content === '\\mathbf') {
|
|
469
|
-
const inner: TypstNode = new TypstNode(
|
|
470
|
-
'funcCall',
|
|
471
|
-
'bold',
|
|
472
|
-
[arg0],
|
|
473
|
-
);
|
|
474
|
-
return new TypstNode(
|
|
475
|
-
'funcCall',
|
|
476
|
-
'upright',
|
|
477
|
-
[inner],
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
// \mathbb{R} -> RR
|
|
481
|
-
if (node.content === '\\mathbb' && arg0.type === 'atom' && /^[A-Z]$/.test(arg0.content)) {
|
|
482
|
-
return new TypstNode('symbol', arg0.content + arg0.content);
|
|
483
|
-
}
|
|
484
|
-
// \operatorname{opname} -> op("opname")
|
|
485
|
-
if (node.content === '\\operatorname') {
|
|
486
|
-
const body = node.args!;
|
|
487
|
-
if (body.length !== 1 || body[0].type !== 'text') {
|
|
488
|
-
throw new TypstWriterError(`Expecting body of \\operatorname to be text but got`, node);
|
|
489
|
-
}
|
|
490
|
-
const text = body[0].content;
|
|
491
|
-
|
|
492
|
-
if (TYPST_INTRINSIC_SYMBOLS.includes(text)) {
|
|
493
|
-
return new TypstNode('symbol', text);
|
|
494
|
-
} else {
|
|
495
|
-
return new TypstNode(
|
|
496
|
-
'funcCall',
|
|
497
|
-
'op',
|
|
498
|
-
[new TypstNode('text', text)],
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// generic case
|
|
504
|
-
return new TypstNode(
|
|
505
|
-
'funcCall',
|
|
506
|
-
convertToken(node.content),
|
|
507
|
-
node.args!.map(convertTree),
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
case 'beginend': {
|
|
511
|
-
const matrix = node.data as TexNode[][];
|
|
512
|
-
const data = matrix.map((row) => row.map(convertTree));
|
|
513
|
-
|
|
514
|
-
if (node.content!.startsWith('align')) {
|
|
515
|
-
// align, align*, alignat, alignat*, aligned, etc.
|
|
516
|
-
return new TypstNode( 'align', '', [], data);
|
|
517
|
-
} else {
|
|
518
|
-
const res = new TypstNode('matrix', '', [], data);
|
|
519
|
-
res.setOptions({'delim': '#none'});
|
|
520
|
-
return res;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
case 'unknownMacro':
|
|
524
|
-
return new TypstNode('unknown', convertToken(node.content));
|
|
525
|
-
case 'control':
|
|
526
|
-
if (node.content === '\\\\') {
|
|
527
|
-
return new TypstNode('symbol', '\\');
|
|
528
|
-
} else if (node.content === '\\,') {
|
|
529
|
-
return new TypstNode('symbol', 'thin');
|
|
530
|
-
} else {
|
|
531
|
-
throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node);
|
|
532
|
-
}
|
|
533
|
-
default:
|
|
534
|
-
throw new TypstWriterError(`Unimplemented node type: ${node.type}`, node);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
function convertToken(token: string): string {
|
|
540
|
-
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
541
|
-
return token;
|
|
542
|
-
} else if (token === '/') {
|
|
543
|
-
return '\\/';
|
|
544
|
-
} else if (token === '\\|') {
|
|
545
|
-
// \| in LaTeX is double vertical bar looks like ||
|
|
546
|
-
return 'parallel';
|
|
547
|
-
} else if (token === '\\\\') {
|
|
548
|
-
return '\\';
|
|
549
|
-
} else if (['\\$', '\\#', '\\&', '\\_'].includes(token)) {
|
|
550
|
-
return token;
|
|
551
|
-
} else if (token.startsWith('\\')) {
|
|
552
|
-
const symbol = token.slice(1);
|
|
553
|
-
if (symbolMap.has(symbol)) {
|
|
554
|
-
return symbolMap.get(symbol)!;
|
|
555
|
-
} else {
|
|
556
|
-
// Fall back to the original macro.
|
|
557
|
-
// This works for \alpha, \beta, \gamma, etc.
|
|
558
|
-
// If this.nonStrict is true, this also works for all unknown macros.
|
|
559
|
-
return symbol;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
return token;
|
|
563
|
-
}
|
|
564
|
-
|