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