tex2typst 0.2.7 → 0.2.9
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 +874 -777
- package/dist/parser.d.ts +1 -5
- package/dist/tex2typst.min.js +1 -1
- package/dist/types.d.ts +23 -2
- package/dist/writer.d.ts +4 -3
- package/package.json +2 -2
- package/src/index.ts +5 -4
- package/src/parser.ts +91 -84
- package/src/types.ts +30 -2
- package/src/writer.ts +285 -189
package/src/writer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { symbolMap } from "./map";
|
|
2
|
-
import { TexNode, TexSqrtData, TexSupsubData, TypstNode } from "./types";
|
|
2
|
+
import { TexNode, TexSqrtData, TexSupsubData, TypstNode, TypstSupsubData } from "./types";
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
// symbols that are supported by Typst but not by KaTeX
|
|
@@ -14,8 +14,13 @@ const TYPST_INTRINSIC_SYMBOLS = [
|
|
|
14
14
|
// 'sgn
|
|
15
15
|
];
|
|
16
16
|
|
|
17
|
+
|
|
18
|
+
function is_delimiter(c: TypstNode): boolean {
|
|
19
|
+
return c.type === 'atom' && ['(', ')', '[', ']', '{', '}', '|', '⌊', '⌋', '⌈', '⌉'].includes(c.content);
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
export class TypstWriterError extends Error {
|
|
18
|
-
node: TexNode;
|
|
23
|
+
node: TexNode | TypstNode;
|
|
19
24
|
|
|
20
25
|
constructor(message: string, node: TexNode | TypstNode) {
|
|
21
26
|
super(message);
|
|
@@ -60,7 +65,7 @@ export class TypstWriter {
|
|
|
60
65
|
// buffer is empty
|
|
61
66
|
no_need_space ||= this.buffer === "";
|
|
62
67
|
// other cases
|
|
63
|
-
no_need_space ||= /[\
|
|
68
|
+
no_need_space ||= /[\s_^{\(]$/.test(this.buffer);
|
|
64
69
|
if(!no_need_space) {
|
|
65
70
|
this.buffer += ' ';
|
|
66
71
|
}
|
|
@@ -73,165 +78,82 @@ export class TypstWriter {
|
|
|
73
78
|
this.buffer += str;
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
public append(node:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
this.queue.push({ type: 'symbol', content: content });
|
|
89
|
-
} else if (node.type === 'symbol') {
|
|
90
|
-
this.queue.push({ type: 'symbol', content: node.content });
|
|
91
|
-
} else if (node.type === 'text') {
|
|
92
|
-
this.queue.push(node as TypstNode)
|
|
93
|
-
} else if (node.type === 'supsub') {
|
|
94
|
-
let { base, sup, sub } = node.data as TexSupsubData;
|
|
95
|
-
|
|
96
|
-
// Special logic for overbrace
|
|
97
|
-
if (base && base.type === 'unaryFunc' && base.content === '\\overbrace' && sup) {
|
|
98
|
-
this.append({ type: 'binaryFunc', content: '\\overbrace', args: [base.args![0], sup] });
|
|
99
|
-
return;
|
|
100
|
-
} else if (base && base.type === 'unaryFunc' && base.content === '\\underbrace' && sub) {
|
|
101
|
-
this.append({ type: 'binaryFunc', content: '\\underbrace', args: [base.args![0], sub] });
|
|
102
|
-
return;
|
|
103
|
-
|
|
81
|
+
public append(node: TypstNode) {
|
|
82
|
+
switch (node.type) {
|
|
83
|
+
case 'empty':
|
|
84
|
+
break;
|
|
85
|
+
case 'atom': {
|
|
86
|
+
if (node.content === ',' && this.insideFunctionDepth > 0) {
|
|
87
|
+
this.queue.push({ type: 'symbol', content: 'comma' });
|
|
88
|
+
} else {
|
|
89
|
+
this.queue.push({ type: 'atom', content: node.content });
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
104
92
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
93
|
+
case 'symbol':
|
|
94
|
+
case 'text':
|
|
95
|
+
case 'comment':
|
|
96
|
+
case 'newline':
|
|
97
|
+
this.queue.push(node);
|
|
98
|
+
break;
|
|
99
|
+
case 'group':
|
|
100
|
+
for (const item of node.args!) {
|
|
101
|
+
this.append(item);
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case 'supsub': {
|
|
105
|
+
let { base, sup, sub } = node.data as TypstSupsubData;
|
|
109
106
|
this.appendWithBracketsIfNeeded(base);
|
|
110
|
-
}
|
|
111
107
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
} else if (node.type === 'leftright') {
|
|
135
|
-
const [left, body, right] = node.args!;
|
|
136
|
-
// These pairs will be handled by Typst compiler by default. No need to add lr()
|
|
137
|
-
if (["[]", "()", "\\{\\}", "\\lfloor\\rfloor", "\\lceil\\rceil"].includes(left.content + right.content)) {
|
|
138
|
-
this.append(left);
|
|
139
|
-
this.append(body);
|
|
140
|
-
this.append(right);
|
|
141
|
-
return;
|
|
108
|
+
let trailing_space_needed = false;
|
|
109
|
+
const has_prime = (sup && sup.type === 'atom' && sup.content === '\'');
|
|
110
|
+
if (has_prime) {
|
|
111
|
+
// Put prime symbol before '_'. Because $y_1'$ is not displayed properly in Typst (so far)
|
|
112
|
+
// e.g.
|
|
113
|
+
// y_1' -> y'_1
|
|
114
|
+
// y_{a_1}' -> y'_{a_1}
|
|
115
|
+
this.queue.push({ type: 'atom', content: '\''});
|
|
116
|
+
trailing_space_needed = false;
|
|
117
|
+
}
|
|
118
|
+
if (sub) {
|
|
119
|
+
this.queue.push({ type: 'atom', content: '_'});
|
|
120
|
+
trailing_space_needed = this.appendWithBracketsIfNeeded(sub);
|
|
121
|
+
}
|
|
122
|
+
if (sup && !has_prime) {
|
|
123
|
+
this.queue.push({ type: 'atom', content: '^'});
|
|
124
|
+
trailing_space_needed = this.appendWithBracketsIfNeeded(sup);
|
|
125
|
+
}
|
|
126
|
+
if (trailing_space_needed) {
|
|
127
|
+
this.queue.push({ type: 'softSpace', content: ''});
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
142
130
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.queue.push({ type: 'atom', content: '('});
|
|
147
|
-
this.append(left);
|
|
148
|
-
this.append(body);
|
|
149
|
-
this.append(right);
|
|
150
|
-
this.queue.push({ type: 'atom', content: ')'});
|
|
151
|
-
this.insideFunctionDepth --;
|
|
152
|
-
} else if (node.type === 'binaryFunc') {
|
|
153
|
-
const func_symbol: TypstNode = { type: 'symbol', content: node.content };
|
|
154
|
-
const [arg0, arg1] = node.args!;
|
|
155
|
-
this.queue.push(func_symbol);
|
|
156
|
-
this.insideFunctionDepth ++;
|
|
157
|
-
this.queue.push({ type: 'atom', content: '('});
|
|
158
|
-
this.append(arg0);
|
|
159
|
-
this.queue.push({ type: 'atom', content: ','});
|
|
160
|
-
this.append(arg1);
|
|
161
|
-
this.queue.push({ type: 'atom', content: ')'});
|
|
162
|
-
this.insideFunctionDepth --;
|
|
163
|
-
} else if (node.type === 'unaryFunc') {
|
|
164
|
-
const func_symbol: TypstNode = { type: 'symbol', content: node.content };
|
|
165
|
-
const arg0 = node.args![0];
|
|
166
|
-
if (node.content === '\\sqrt' && node.data) {
|
|
167
|
-
func_symbol.content = 'root';
|
|
131
|
+
case 'binaryFunc': {
|
|
132
|
+
const func_symbol: TypstNode = { type: 'symbol', content: node.content };
|
|
133
|
+
const [arg0, arg1] = node.args!;
|
|
168
134
|
this.queue.push(func_symbol);
|
|
169
135
|
this.insideFunctionDepth ++;
|
|
170
136
|
this.queue.push({ type: 'atom', content: '('});
|
|
171
|
-
this.append(node.data as TexSqrtData); // the number of times to take the root
|
|
172
|
-
this.queue.push({ type: 'atom', content: ','});
|
|
173
137
|
this.append(arg0);
|
|
138
|
+
this.queue.push({ type: 'atom', content: ','});
|
|
139
|
+
this.append(arg1);
|
|
174
140
|
this.queue.push({ type: 'atom', content: ')'});
|
|
175
141
|
this.insideFunctionDepth --;
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'unaryFunc': {
|
|
145
|
+
const func_symbol: TypstNode = { type: 'symbol', content: node.content };
|
|
146
|
+
const arg0 = node.args![0];
|
|
181
147
|
this.queue.push(func_symbol);
|
|
182
148
|
this.insideFunctionDepth ++;
|
|
183
149
|
this.queue.push({ type: 'atom', content: '('});
|
|
184
150
|
this.append(arg0);
|
|
185
151
|
this.queue.push({ type: 'atom', content: ')'});
|
|
186
152
|
this.insideFunctionDepth --;
|
|
187
|
-
|
|
188
|
-
this.insideFunctionDepth --;
|
|
189
|
-
return;
|
|
190
|
-
} else if (node.content === '\\mathbb') {
|
|
191
|
-
const body = node.args![0];
|
|
192
|
-
if (body.type === 'element' && /^[A-Z]$/.test(body.content)) {
|
|
193
|
-
// \mathbb{R} -> RR
|
|
194
|
-
this.queue.push({ type: 'symbol', content: body.content + body.content});
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
// Fall through
|
|
198
|
-
} else if (node.content === '\\operatorname') {
|
|
199
|
-
let body = node.args!;
|
|
200
|
-
if (body.length === 1 && body[0].type == 'ordgroup') {
|
|
201
|
-
body = body[0].args!;
|
|
202
|
-
}
|
|
203
|
-
const text = body.reduce((buff, n) => {
|
|
204
|
-
// Hope convertToken() will not throw an error
|
|
205
|
-
// If it does, the input is bad.
|
|
206
|
-
buff += convertToken(n.content);
|
|
207
|
-
return buff;
|
|
208
|
-
}, "" as string);
|
|
209
|
-
|
|
210
|
-
if (this.preferTypstIntrinsic && TYPST_INTRINSIC_SYMBOLS.includes(text)) {
|
|
211
|
-
// e.g. we prefer just sech over op("sech")
|
|
212
|
-
this.queue.push({ type: 'symbol', content: text});
|
|
213
|
-
} else {
|
|
214
|
-
this.queue.push({ type: 'symbol', content: 'op' });
|
|
215
|
-
this.queue.push({ type: 'atom', content: '('});
|
|
216
|
-
this.queue.push({ type: 'text', content: text});
|
|
217
|
-
this.queue.push({ type: 'atom', content: ')'});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return;
|
|
153
|
+
break;
|
|
221
154
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
this.queue.push({ type: 'atom', content: '('});
|
|
225
|
-
this.append(arg0);
|
|
226
|
-
this.queue.push({ type: 'atom', content: ')'});
|
|
227
|
-
this.insideFunctionDepth --;
|
|
228
|
-
} else if (node.type === 'newline') {
|
|
229
|
-
this.queue.push({ type: 'newline', content: '\n'});
|
|
230
|
-
return;
|
|
231
|
-
} else if (node.type === 'beginend') {
|
|
232
|
-
if (node.content!.startsWith('align')) {
|
|
233
|
-
// align, align*, alignat, alignat*, aligned, etc.
|
|
234
|
-
const matrix = node.data as TexNode[][];
|
|
155
|
+
case 'align': {
|
|
156
|
+
const matrix = node.data as TypstNode[][];
|
|
235
157
|
matrix.forEach((row, i) => {
|
|
236
158
|
row.forEach((cell, j) => {
|
|
237
159
|
if (j > 0) {
|
|
@@ -240,11 +162,13 @@ export class TypstWriter {
|
|
|
240
162
|
this.append(cell);
|
|
241
163
|
});
|
|
242
164
|
if (i < matrix.length - 1) {
|
|
243
|
-
this.queue.push({ type: 'symbol', content: '
|
|
165
|
+
this.queue.push({ type: 'symbol', content: '\\' });
|
|
244
166
|
}
|
|
245
167
|
});
|
|
246
|
-
|
|
247
|
-
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'matrix': {
|
|
171
|
+
const matrix = node.data as TypstNode[][];
|
|
248
172
|
this.queue.push({ type: 'symbol', content: 'mat' });
|
|
249
173
|
this.insideFunctionDepth ++;
|
|
250
174
|
this.queue.push({ type: 'atom', content: '('});
|
|
@@ -252,10 +176,10 @@ export class TypstWriter {
|
|
|
252
176
|
matrix.forEach((row, i) => {
|
|
253
177
|
row.forEach((cell, j) => {
|
|
254
178
|
// There is a leading & in row
|
|
255
|
-
if (cell.type === 'ordgroup' && cell.args!.length === 0) {
|
|
256
|
-
this.queue.push({ type: 'atom', content: ',' });
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
179
|
+
// if (cell.type === 'ordgroup' && cell.args!.length === 0) {
|
|
180
|
+
// this.queue.push({ type: 'atom', content: ',' });
|
|
181
|
+
// return;
|
|
182
|
+
// }
|
|
259
183
|
// if (j == 0 && cell.type === 'newline' && cell.content === '\n') {
|
|
260
184
|
// return;
|
|
261
185
|
// }
|
|
@@ -272,27 +196,41 @@ export class TypstWriter {
|
|
|
272
196
|
});
|
|
273
197
|
this.queue.push({ type: 'atom', content: ')'});
|
|
274
198
|
this.insideFunctionDepth --;
|
|
199
|
+
break;
|
|
275
200
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
201
|
+
case 'unknown': {
|
|
202
|
+
if (this.nonStrict) {
|
|
203
|
+
this.queue.push({ type: 'symbol', content: node.content });
|
|
204
|
+
} else {
|
|
205
|
+
throw new TypstWriterError(`Unknown macro: ${node.content}`, node);
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
282
208
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
209
|
+
default:
|
|
210
|
+
throw new TypstWriterError(`Unimplemented node type to append: ${node.type}`, node);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private appendWithBracketsIfNeeded(node: TypstNode): boolean {
|
|
215
|
+
let need_to_wrap = ['group', 'supsub', 'empty'].includes(node.type);
|
|
216
|
+
|
|
217
|
+
if (node.type === 'group') {
|
|
218
|
+
const first = node.args![0];
|
|
219
|
+
const last = node.args![node.args!.length - 1];
|
|
220
|
+
if (is_delimiter(first) && is_delimiter(last)) {
|
|
221
|
+
need_to_wrap = false;
|
|
290
222
|
}
|
|
291
|
-
}
|
|
292
|
-
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (need_to_wrap) {
|
|
226
|
+
this.queue.push({ type: 'atom', content: '(' });
|
|
227
|
+
this.append(node);
|
|
228
|
+
this.queue.push({ type: 'atom', content: ')' });
|
|
293
229
|
} else {
|
|
294
|
-
|
|
230
|
+
this.append(node);
|
|
295
231
|
}
|
|
232
|
+
|
|
233
|
+
return !need_to_wrap;
|
|
296
234
|
}
|
|
297
235
|
|
|
298
236
|
protected flushQueue() {
|
|
@@ -300,10 +238,8 @@ export class TypstWriter {
|
|
|
300
238
|
let str = "";
|
|
301
239
|
switch (node.type) {
|
|
302
240
|
case 'atom':
|
|
303
|
-
str = node.content;
|
|
304
|
-
break;
|
|
305
241
|
case 'symbol':
|
|
306
|
-
str =
|
|
242
|
+
str = node.content;
|
|
307
243
|
break;
|
|
308
244
|
case 'text':
|
|
309
245
|
str = `"${node.content}"`;
|
|
@@ -328,24 +264,6 @@ export class TypstWriter {
|
|
|
328
264
|
this.queue = [];
|
|
329
265
|
}
|
|
330
266
|
|
|
331
|
-
private appendWithBracketsIfNeeded(node: TexNode): boolean {
|
|
332
|
-
const is_single = ['symbol', 'element', 'unaryFunc', 'binaryFunc', 'leftright'].includes(node.type);
|
|
333
|
-
if (is_single) {
|
|
334
|
-
this.append(node);
|
|
335
|
-
} else {
|
|
336
|
-
this.queue.push({
|
|
337
|
-
type: 'atom',
|
|
338
|
-
content: '('
|
|
339
|
-
});
|
|
340
|
-
this.append(node);
|
|
341
|
-
this.queue.push({
|
|
342
|
-
type: 'atom',
|
|
343
|
-
content: ')'
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
return is_single;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
267
|
public finalize(): string {
|
|
350
268
|
this.flushQueue();
|
|
351
269
|
const smartFloorPass = function (input: string): string {
|
|
@@ -368,6 +286,184 @@ export class TypstWriter {
|
|
|
368
286
|
}
|
|
369
287
|
}
|
|
370
288
|
|
|
289
|
+
export function convertTree(node: TexNode): TypstNode {
|
|
290
|
+
switch (node.type) {
|
|
291
|
+
case 'empty':
|
|
292
|
+
case 'whitespace':
|
|
293
|
+
return { type: 'empty', content: '' };
|
|
294
|
+
case 'ordgroup':
|
|
295
|
+
return {
|
|
296
|
+
type: 'group',
|
|
297
|
+
content: '',
|
|
298
|
+
args: node.args!.map(convertTree),
|
|
299
|
+
};
|
|
300
|
+
case 'element':
|
|
301
|
+
return { type: 'atom', content: convertToken(node.content) };
|
|
302
|
+
case 'symbol':
|
|
303
|
+
return { type: 'symbol', content: convertToken(node.content) };
|
|
304
|
+
case 'text':
|
|
305
|
+
return { type: 'text', content: node.content };
|
|
306
|
+
case 'comment':
|
|
307
|
+
return { type: 'comment', content: node.content };
|
|
308
|
+
case 'supsub': {
|
|
309
|
+
let { base, sup, sub } = node.data as TexSupsubData;
|
|
310
|
+
|
|
311
|
+
// Special logic for overbrace
|
|
312
|
+
if (base && base.type === 'unaryFunc' && base.content === '\\overbrace' && sup) {
|
|
313
|
+
return {
|
|
314
|
+
type: 'binaryFunc',
|
|
315
|
+
content: 'overbrace',
|
|
316
|
+
args: [convertTree(base.args![0]), convertTree(sup)],
|
|
317
|
+
};
|
|
318
|
+
} else if (base && base.type === 'unaryFunc' && base.content === '\\underbrace' && sub) {
|
|
319
|
+
return {
|
|
320
|
+
type: 'binaryFunc',
|
|
321
|
+
content: 'underbrace',
|
|
322
|
+
args: [convertTree(base.args![0]), convertTree(sub)],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const data: TypstSupsubData = {
|
|
327
|
+
base: convertTree(base),
|
|
328
|
+
};
|
|
329
|
+
if (data.base.type === 'empty') {
|
|
330
|
+
data.base = { type: 'text', content: '' };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (sup) {
|
|
334
|
+
data.sup = convertTree(sup);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (sub) {
|
|
338
|
+
data.sub = convertTree(sub);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
type: 'supsub',
|
|
343
|
+
content: '',
|
|
344
|
+
data: data,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
case 'leftright': {
|
|
348
|
+
const [left, body, right] = node.args!;
|
|
349
|
+
// These pairs will be handled by Typst compiler by default. No need to add lr()
|
|
350
|
+
const group: TypstNode = {
|
|
351
|
+
type: 'group',
|
|
352
|
+
content: '',
|
|
353
|
+
args: node.args!.map(convertTree),
|
|
354
|
+
};
|
|
355
|
+
if (["[]", "()", "\\{\\}", "\\lfloor\\rfloor", "\\lceil\\rceil"].includes(left.content + right.content)) {
|
|
356
|
+
return group;
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
type: 'unaryFunc',
|
|
360
|
+
content: 'lr',
|
|
361
|
+
args: [group],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
case 'binaryFunc': {
|
|
365
|
+
return {
|
|
366
|
+
type: 'binaryFunc',
|
|
367
|
+
content: convertToken(node.content),
|
|
368
|
+
args: node.args!.map(convertTree),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
case 'unaryFunc': {
|
|
372
|
+
const arg0 = convertTree(node.args![0]);
|
|
373
|
+
// \sqrt{3}{x} -> root(3, x)
|
|
374
|
+
if (node.content === '\\sqrt' && node.data) {
|
|
375
|
+
const data = convertTree(node.data as TexSqrtData); // the number of times to take the root
|
|
376
|
+
return {
|
|
377
|
+
type: 'binaryFunc',
|
|
378
|
+
content: 'root',
|
|
379
|
+
args: [data, arg0],
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
// \mathbf{a} -> upright(mathbf(a))
|
|
383
|
+
if (node.content === '\\mathbf') {
|
|
384
|
+
const inner: TypstNode = {
|
|
385
|
+
type: 'unaryFunc',
|
|
386
|
+
content: 'bold',
|
|
387
|
+
args: [arg0],
|
|
388
|
+
};
|
|
389
|
+
return {
|
|
390
|
+
type: 'unaryFunc',
|
|
391
|
+
content: 'upright',
|
|
392
|
+
args: [inner],
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
// \mathbb{R} -> RR
|
|
396
|
+
if (node.content === '\\mathbb' && arg0.type === 'atom' && /^[A-Z]$/.test(arg0.content)) {
|
|
397
|
+
return {
|
|
398
|
+
type: 'symbol',
|
|
399
|
+
content: arg0.content + arg0.content,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
// \operatorname{opname} -> op("opname")
|
|
403
|
+
if (node.content === '\\operatorname') {
|
|
404
|
+
const body = node.args!;
|
|
405
|
+
if (body.length !== 1 || body[0].type !== 'text') {
|
|
406
|
+
throw new TypstWriterError(`Expecting body of \\operatorname to be text but got`, node);
|
|
407
|
+
}
|
|
408
|
+
const text = body[0].content;
|
|
409
|
+
|
|
410
|
+
if (TYPST_INTRINSIC_SYMBOLS.includes(text)) {
|
|
411
|
+
return {
|
|
412
|
+
type: 'symbol',
|
|
413
|
+
content: text,
|
|
414
|
+
};
|
|
415
|
+
} else {
|
|
416
|
+
return {
|
|
417
|
+
type: 'unaryFunc',
|
|
418
|
+
content: 'op',
|
|
419
|
+
args: [{ type: 'text', content: text }],
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// generic case
|
|
425
|
+
return {
|
|
426
|
+
type: 'unaryFunc',
|
|
427
|
+
content: convertToken(node.content),
|
|
428
|
+
args: node.args!.map(convertTree),
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
case 'newline':
|
|
432
|
+
return { type: 'newline', content: '\n' };
|
|
433
|
+
case 'beginend': {
|
|
434
|
+
const matrix = node.data as TexNode[][];
|
|
435
|
+
const data = matrix.map((row) => row.map(convertTree));
|
|
436
|
+
|
|
437
|
+
if (node.content!.startsWith('align')) {
|
|
438
|
+
// align, align*, alignat, alignat*, aligned, etc.
|
|
439
|
+
return {
|
|
440
|
+
type: 'align',
|
|
441
|
+
content: '',
|
|
442
|
+
data: data,
|
|
443
|
+
};
|
|
444
|
+
} else {
|
|
445
|
+
return {
|
|
446
|
+
type: 'matrix',
|
|
447
|
+
content: 'mat',
|
|
448
|
+
data: data,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
case 'unknownMacro':
|
|
453
|
+
return { type: 'unknown', content: convertToken(node.content) };
|
|
454
|
+
case 'control':
|
|
455
|
+
if (node.content === '\\\\') {
|
|
456
|
+
return { type: 'symbol', content: '\\' };
|
|
457
|
+
} else if (node.content === '\\,') {
|
|
458
|
+
return { type: 'symbol', content: 'thin' };
|
|
459
|
+
} else {
|
|
460
|
+
throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node);
|
|
461
|
+
}
|
|
462
|
+
default:
|
|
463
|
+
throw new TypstWriterError(`Unimplemented node type: ${node.type}`, node);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
371
467
|
|
|
372
468
|
function convertToken(token: string): string {
|
|
373
469
|
if (/^[a-zA-Z0-9]$/.test(token)) {
|