tex2typst 0.2.12 → 0.2.15
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 +307 -245
- package/dist/tex-parser.d.ts +28 -0
- package/dist/tex2typst.min.js +1 -1
- package/dist/types.d.ts +47 -6
- package/dist/writer.d.ts +7 -7
- package/package.json +2 -2
- package/src/index.ts +4 -3
- package/src/{parser.ts → tex-parser.ts} +119 -111
- package/src/types.ts +99 -10
- package/src/writer.ts +255 -197
- package/dist/parser.d.ts +0 -28
package/src/writer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { symbolMap } from "./map";
|
|
2
|
-
import { TexNode, TexSqrtData, TexSupsubData, TypstNode, TypstSupsubData } from "./types";
|
|
2
|
+
import { TexNode, TexSqrtData, TexSupsubData, TypstNode, TypstSupsubData, TypstToken, TypstTokenType } from "./types";
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
// symbols that are supported by Typst but not by KaTeX
|
|
@@ -19,10 +19,58 @@ function is_delimiter(c: TypstNode): boolean {
|
|
|
19
19
|
return c.type === 'atom' && ['(', ')', '[', ']', '{', '}', '|', '⌊', '⌋', '⌈', '⌉'].includes(c.content);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
|
|
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_shallow(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_shallow(d) && a2.eq_shallow(e) && a3.eq_shallow(f)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
};
|
|
44
|
+
const is_eq = (n: TexNode): boolean => n.eq_shallow(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
|
+
'unaryFunc',
|
|
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
|
+
const TYPST_LEFT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ATOM, '(');
|
|
66
|
+
const TYPST_RIGHT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ATOM, ')');
|
|
67
|
+
const TYPST_COMMA: TypstToken = new TypstToken(TypstTokenType.ATOM, ',');
|
|
68
|
+
const TYPST_NEWLINE: TypstToken = new TypstToken(TypstTokenType.SYMBOL, '\n');
|
|
69
|
+
|
|
22
70
|
export class TypstWriterError extends Error {
|
|
23
|
-
node: TexNode | TypstNode;
|
|
71
|
+
node: TexNode | TypstNode | TypstToken;
|
|
24
72
|
|
|
25
|
-
constructor(message: string, node: TexNode | TypstNode) {
|
|
73
|
+
constructor(message: string, node: TexNode | TypstNode | TypstToken) {
|
|
26
74
|
super(message);
|
|
27
75
|
this.name = "TypstWriterError";
|
|
28
76
|
this.node = node;
|
|
@@ -32,73 +80,91 @@ export class TypstWriterError extends Error {
|
|
|
32
80
|
export class TypstWriter {
|
|
33
81
|
private nonStrict: boolean;
|
|
34
82
|
private preferTypstIntrinsic: boolean;
|
|
83
|
+
private keepSpaces: boolean;
|
|
35
84
|
|
|
36
85
|
protected buffer: string = "";
|
|
37
|
-
protected queue:
|
|
86
|
+
protected queue: TypstToken[] = [];
|
|
38
87
|
|
|
39
|
-
private needSpaceAfterSingleItemScript = false;
|
|
40
88
|
private insideFunctionDepth = 0;
|
|
41
89
|
|
|
42
|
-
constructor(nonStrict: boolean, preferTypstIntrinsic: boolean) {
|
|
90
|
+
constructor(nonStrict: boolean, preferTypstIntrinsic: boolean, keepSpaces: boolean) {
|
|
43
91
|
this.nonStrict = nonStrict;
|
|
44
92
|
this.preferTypstIntrinsic = preferTypstIntrinsic;
|
|
93
|
+
this.keepSpaces = keepSpaces;
|
|
45
94
|
}
|
|
46
95
|
|
|
47
96
|
|
|
48
|
-
private writeBuffer(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// starting clause
|
|
54
|
-
no_need_space ||= /[\(\|]$/.test(this.buffer) && /^\w/.test(str);
|
|
55
|
-
// putting punctuation
|
|
56
|
-
no_need_space ||= /^[}()_^,;!\|]$/.test(str);
|
|
57
|
-
// putting a prime
|
|
58
|
-
no_need_space ||= str === "'";
|
|
59
|
-
// continue a number
|
|
60
|
-
no_need_space ||= /[0-9]$/.test(this.buffer) && /^[0-9]/.test(str);
|
|
61
|
-
// leading sign
|
|
62
|
-
no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
|
|
63
|
-
// new line
|
|
64
|
-
no_need_space ||= str.startsWith('\n');
|
|
65
|
-
// buffer is empty
|
|
66
|
-
no_need_space ||= this.buffer === "";
|
|
67
|
-
// other cases
|
|
68
|
-
no_need_space ||= /[\s_^{\(]$/.test(this.buffer);
|
|
69
|
-
if(!no_need_space) {
|
|
70
|
-
this.buffer += ' ';
|
|
71
|
-
}
|
|
97
|
+
private writeBuffer(token: TypstToken) {
|
|
98
|
+
const str = token.content;
|
|
99
|
+
|
|
100
|
+
if (str === '') {
|
|
101
|
+
return;
|
|
72
102
|
}
|
|
73
103
|
|
|
74
|
-
|
|
75
|
-
|
|
104
|
+
let no_need_space = false;
|
|
105
|
+
// starting clause
|
|
106
|
+
no_need_space ||= /[\(\|]$/.test(this.buffer) && /^\w/.test(str);
|
|
107
|
+
// putting punctuation
|
|
108
|
+
no_need_space ||= /^[}()_^,;!\|]$/.test(str);
|
|
109
|
+
// putting a prime
|
|
110
|
+
no_need_space ||= str === "'";
|
|
111
|
+
// continue a number
|
|
112
|
+
no_need_space ||= /[0-9]$/.test(this.buffer) && /^[0-9]/.test(str);
|
|
113
|
+
// leading sign. e.g. produce "+1" instead of " +1"
|
|
114
|
+
no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
|
|
115
|
+
// new line
|
|
116
|
+
no_need_space ||= str.startsWith('\n');
|
|
117
|
+
// buffer is empty
|
|
118
|
+
no_need_space ||= this.buffer === "";
|
|
119
|
+
// str is starting with a space itself
|
|
120
|
+
no_need_space ||= /^\s/.test(str);
|
|
121
|
+
// other cases
|
|
122
|
+
no_need_space ||= /[\s_^{\(]$/.test(this.buffer);
|
|
123
|
+
if (!no_need_space) {
|
|
124
|
+
this.buffer += ' ';
|
|
76
125
|
}
|
|
77
126
|
|
|
78
127
|
this.buffer += str;
|
|
79
128
|
}
|
|
80
129
|
|
|
81
|
-
|
|
130
|
+
// Serialize a tree of TypstNode into a list of TypstToken
|
|
131
|
+
public serialize(node: TypstNode) {
|
|
82
132
|
switch (node.type) {
|
|
83
133
|
case 'empty':
|
|
84
134
|
break;
|
|
85
135
|
case 'atom': {
|
|
86
136
|
if (node.content === ',' && this.insideFunctionDepth > 0) {
|
|
87
|
-
this.queue.push(
|
|
137
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, 'comma'));
|
|
88
138
|
} else {
|
|
89
|
-
this.queue.push(
|
|
139
|
+
this.queue.push(new TypstToken(TypstTokenType.ATOM, node.content));
|
|
90
140
|
}
|
|
91
141
|
break;
|
|
92
142
|
}
|
|
93
143
|
case 'symbol':
|
|
144
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, node.content));
|
|
145
|
+
break;
|
|
94
146
|
case 'text':
|
|
147
|
+
this.queue.push(new TypstToken(TypstTokenType.TEXT, `"${node.content}"`));
|
|
148
|
+
break;
|
|
95
149
|
case 'comment':
|
|
96
|
-
|
|
97
|
-
|
|
150
|
+
this.queue.push(new TypstToken(TypstTokenType.COMMENT, `//${node.content}`));
|
|
151
|
+
break;
|
|
152
|
+
case 'whitespace':
|
|
153
|
+
for (const c of node.content) {
|
|
154
|
+
if (c === ' ') {
|
|
155
|
+
if (this.keepSpaces) {
|
|
156
|
+
this.queue.push(new TypstToken(TypstTokenType.SPACE, c));
|
|
157
|
+
}
|
|
158
|
+
} else if (c === '\n') {
|
|
159
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, c));
|
|
160
|
+
} else {
|
|
161
|
+
throw new TypstWriterError(`Unexpected whitespace character: ${c}`, node);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
98
164
|
break;
|
|
99
165
|
case 'group':
|
|
100
166
|
for (const item of node.args!) {
|
|
101
|
-
this.
|
|
167
|
+
this.serialize(item);
|
|
102
168
|
}
|
|
103
169
|
break;
|
|
104
170
|
case 'supsub': {
|
|
@@ -112,44 +178,49 @@ export class TypstWriter {
|
|
|
112
178
|
// e.g.
|
|
113
179
|
// y_1' -> y'_1
|
|
114
180
|
// y_{a_1}' -> y'_{a_1}
|
|
115
|
-
this.queue.push(
|
|
181
|
+
this.queue.push(new TypstToken(TypstTokenType.ATOM, '\''));
|
|
116
182
|
trailing_space_needed = false;
|
|
117
183
|
}
|
|
118
184
|
if (sub) {
|
|
119
|
-
this.queue.push(
|
|
185
|
+
this.queue.push(new TypstToken(TypstTokenType.ATOM, '_'));
|
|
120
186
|
trailing_space_needed = this.appendWithBracketsIfNeeded(sub);
|
|
121
187
|
}
|
|
122
188
|
if (sup && !has_prime) {
|
|
123
|
-
this.queue.push(
|
|
189
|
+
this.queue.push(new TypstToken(TypstTokenType.ATOM, '^'));
|
|
124
190
|
trailing_space_needed = this.appendWithBracketsIfNeeded(sup);
|
|
125
191
|
}
|
|
126
192
|
if (trailing_space_needed) {
|
|
127
|
-
this.queue.push(
|
|
193
|
+
this.queue.push(new TypstToken(TypstTokenType.CONTROL, ' '));
|
|
128
194
|
}
|
|
129
195
|
break;
|
|
130
196
|
}
|
|
131
197
|
case 'binaryFunc': {
|
|
132
|
-
const func_symbol:
|
|
198
|
+
const func_symbol: TypstToken = new TypstToken(TypstTokenType.SYMBOL, node.content);
|
|
133
199
|
const [arg0, arg1] = node.args!;
|
|
134
200
|
this.queue.push(func_symbol);
|
|
135
|
-
this.insideFunctionDepth
|
|
136
|
-
this.queue.push(
|
|
137
|
-
this.
|
|
138
|
-
this.queue.push(
|
|
139
|
-
this.
|
|
140
|
-
this.queue.push(
|
|
141
|
-
this.insideFunctionDepth
|
|
201
|
+
this.insideFunctionDepth++;
|
|
202
|
+
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
203
|
+
this.serialize(arg0);
|
|
204
|
+
this.queue.push(new TypstToken(TypstTokenType.ATOM, ','));
|
|
205
|
+
this.serialize(arg1);
|
|
206
|
+
this.queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
207
|
+
this.insideFunctionDepth--;
|
|
142
208
|
break;
|
|
143
209
|
}
|
|
144
210
|
case 'unaryFunc': {
|
|
145
|
-
const func_symbol:
|
|
211
|
+
const func_symbol: TypstToken = new TypstToken(TypstTokenType.SYMBOL, node.content);
|
|
146
212
|
const arg0 = node.args![0];
|
|
147
213
|
this.queue.push(func_symbol);
|
|
148
|
-
this.insideFunctionDepth
|
|
149
|
-
this.queue.push(
|
|
150
|
-
this.
|
|
151
|
-
|
|
152
|
-
|
|
214
|
+
this.insideFunctionDepth++;
|
|
215
|
+
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
216
|
+
this.serialize(arg0);
|
|
217
|
+
if (node.options) {
|
|
218
|
+
for (const [key, value] of Object.entries(node.options)) {
|
|
219
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, `, ${key}: ${value}`));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
this.queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
223
|
+
this.insideFunctionDepth--;
|
|
153
224
|
break;
|
|
154
225
|
}
|
|
155
226
|
case 'align': {
|
|
@@ -157,50 +228,54 @@ export class TypstWriter {
|
|
|
157
228
|
matrix.forEach((row, i) => {
|
|
158
229
|
row.forEach((cell, j) => {
|
|
159
230
|
if (j > 0) {
|
|
160
|
-
this.queue.push(
|
|
231
|
+
this.queue.push(new TypstToken(TypstTokenType.ATOM, '&'));
|
|
161
232
|
}
|
|
162
|
-
this.
|
|
233
|
+
this.serialize(cell);
|
|
163
234
|
});
|
|
164
235
|
if (i < matrix.length - 1) {
|
|
165
|
-
this.queue.push(
|
|
236
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, '\\'));
|
|
166
237
|
}
|
|
167
238
|
});
|
|
168
239
|
break;
|
|
169
240
|
}
|
|
170
241
|
case 'matrix': {
|
|
171
242
|
const matrix = node.data as TypstNode[][];
|
|
172
|
-
this.queue.push(
|
|
173
|
-
this.insideFunctionDepth
|
|
174
|
-
this.queue.push(
|
|
175
|
-
|
|
243
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, 'mat'));
|
|
244
|
+
this.insideFunctionDepth++;
|
|
245
|
+
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
246
|
+
if (node.options) {
|
|
247
|
+
for (const [key, value] of Object.entries(node.options)) {
|
|
248
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, `${key}: ${value}, `));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
176
251
|
matrix.forEach((row, i) => {
|
|
177
252
|
row.forEach((cell, j) => {
|
|
178
253
|
// There is a leading & in row
|
|
179
254
|
// if (cell.type === 'ordgroup' && cell.args!.length === 0) {
|
|
180
|
-
|
|
181
|
-
|
|
255
|
+
// this.queue.push(new TypstNode('atom', ','));
|
|
256
|
+
// return;
|
|
182
257
|
// }
|
|
183
258
|
// if (j == 0 && cell.type === 'newline' && cell.content === '\n') {
|
|
184
|
-
|
|
259
|
+
// return;
|
|
185
260
|
// }
|
|
186
|
-
this.
|
|
261
|
+
this.serialize(cell);
|
|
187
262
|
// cell.args!.forEach((n) => this.append(n));
|
|
188
263
|
if (j < row.length - 1) {
|
|
189
|
-
this.queue.push(
|
|
264
|
+
this.queue.push(new TypstToken(TypstTokenType.ATOM, ','));
|
|
190
265
|
} else {
|
|
191
266
|
if (i < matrix.length - 1) {
|
|
192
|
-
this.queue.push(
|
|
267
|
+
this.queue.push(new TypstToken(TypstTokenType.ATOM, ';'));
|
|
193
268
|
}
|
|
194
269
|
}
|
|
195
270
|
});
|
|
196
271
|
});
|
|
197
|
-
this.queue.push(
|
|
198
|
-
this.insideFunctionDepth
|
|
272
|
+
this.queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
273
|
+
this.insideFunctionDepth--;
|
|
199
274
|
break;
|
|
200
275
|
}
|
|
201
276
|
case 'unknown': {
|
|
202
277
|
if (this.nonStrict) {
|
|
203
|
-
this.queue.push(
|
|
278
|
+
this.queue.push(new TypstToken(TypstTokenType.SYMBOL, node.content));
|
|
204
279
|
} else {
|
|
205
280
|
throw new TypstWriterError(`Unknown macro: ${node.content}`, node);
|
|
206
281
|
}
|
|
@@ -223,44 +298,35 @@ export class TypstWriter {
|
|
|
223
298
|
}
|
|
224
299
|
|
|
225
300
|
if (need_to_wrap) {
|
|
226
|
-
this.queue.push(
|
|
227
|
-
this.
|
|
228
|
-
this.queue.push(
|
|
301
|
+
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
302
|
+
this.serialize(node);
|
|
303
|
+
this.queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
229
304
|
} else {
|
|
230
|
-
this.
|
|
305
|
+
this.serialize(node);
|
|
231
306
|
}
|
|
232
307
|
|
|
233
308
|
return !need_to_wrap;
|
|
234
309
|
}
|
|
235
310
|
|
|
236
311
|
protected flushQueue() {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
this.needSpaceAfterSingleItemScript = true;
|
|
249
|
-
str = '';
|
|
250
|
-
break;
|
|
251
|
-
case 'comment':
|
|
252
|
-
str = `//${node.content}`;
|
|
253
|
-
break;
|
|
254
|
-
case 'newline':
|
|
255
|
-
str = '\n';
|
|
256
|
-
break;
|
|
257
|
-
default:
|
|
258
|
-
throw new TypstWriterError(`Unexpected node type to stringify: ${node.type}`, node)
|
|
259
|
-
}
|
|
260
|
-
if (str !== '') {
|
|
261
|
-
this.writeBuffer(str);
|
|
312
|
+
const SOFT_SPACE = new TypstToken(TypstTokenType.CONTROL, ' ');
|
|
313
|
+
|
|
314
|
+
// delete soft spaces if they are not needed
|
|
315
|
+
for(let i = 0; i < this.queue.length; i++) {
|
|
316
|
+
let token = this.queue[i];
|
|
317
|
+
if (token.eq(SOFT_SPACE)) {
|
|
318
|
+
if (i === this.queue.length - 1) {
|
|
319
|
+
this.queue[i].content = '';
|
|
320
|
+
} else if (this.queue[i + 1].isOneOf([TYPST_RIGHT_PARENTHESIS, TYPST_COMMA, TYPST_NEWLINE])) {
|
|
321
|
+
this.queue[i].content = '';
|
|
322
|
+
}
|
|
262
323
|
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.queue.forEach((token) => {
|
|
327
|
+
this.writeBuffer(token)
|
|
263
328
|
});
|
|
329
|
+
|
|
264
330
|
this.queue = [];
|
|
265
331
|
}
|
|
266
332
|
|
|
@@ -295,48 +361,50 @@ export class TypstWriter {
|
|
|
295
361
|
}
|
|
296
362
|
}
|
|
297
363
|
|
|
364
|
+
// Convert a tree of TexNode into a tree of TypstNode
|
|
298
365
|
export function convertTree(node: TexNode): TypstNode {
|
|
299
366
|
switch (node.type) {
|
|
300
367
|
case 'empty':
|
|
368
|
+
return new TypstNode('empty', '');
|
|
301
369
|
case 'whitespace':
|
|
302
|
-
return
|
|
370
|
+
return new TypstNode('whitespace', node.content);
|
|
303
371
|
case 'ordgroup':
|
|
304
|
-
return
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
372
|
+
return new TypstNode(
|
|
373
|
+
'group',
|
|
374
|
+
'',
|
|
375
|
+
node.args!.map(convertTree),
|
|
376
|
+
);
|
|
309
377
|
case 'element':
|
|
310
|
-
return
|
|
378
|
+
return new TypstNode('atom', convertToken(node.content));
|
|
311
379
|
case 'symbol':
|
|
312
|
-
return
|
|
380
|
+
return new TypstNode('symbol', convertToken(node.content));
|
|
313
381
|
case 'text':
|
|
314
|
-
return
|
|
382
|
+
return new TypstNode('text', node.content);
|
|
315
383
|
case 'comment':
|
|
316
|
-
return
|
|
384
|
+
return new TypstNode('comment', node.content);
|
|
317
385
|
case 'supsub': {
|
|
318
386
|
let { base, sup, sub } = node.data as TexSupsubData;
|
|
319
387
|
|
|
320
388
|
// Special logic for overbrace
|
|
321
389
|
if (base && base.type === 'unaryFunc' && base.content === '\\overbrace' && sup) {
|
|
322
|
-
return
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
390
|
+
return new TypstNode(
|
|
391
|
+
'binaryFunc',
|
|
392
|
+
'overbrace',
|
|
393
|
+
[convertTree(base.args![0]), convertTree(sup)],
|
|
394
|
+
);
|
|
327
395
|
} else if (base && base.type === 'unaryFunc' && base.content === '\\underbrace' && sub) {
|
|
328
|
-
return
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
396
|
+
return new TypstNode(
|
|
397
|
+
'binaryFunc',
|
|
398
|
+
'underbrace',
|
|
399
|
+
[convertTree(base.args![0]), convertTree(sub)],
|
|
400
|
+
);
|
|
333
401
|
}
|
|
334
402
|
|
|
335
403
|
const data: TypstSupsubData = {
|
|
336
404
|
base: convertTree(base),
|
|
337
405
|
};
|
|
338
406
|
if (data.base.type === 'empty') {
|
|
339
|
-
data.base =
|
|
407
|
+
data.base = new TypstNode('text', '');
|
|
340
408
|
}
|
|
341
409
|
|
|
342
410
|
if (sup) {
|
|
@@ -347,71 +415,67 @@ export function convertTree(node: TexNode): TypstNode {
|
|
|
347
415
|
data.sub = convertTree(sub);
|
|
348
416
|
}
|
|
349
417
|
|
|
350
|
-
return
|
|
351
|
-
type: 'supsub',
|
|
352
|
-
content: '',
|
|
353
|
-
data: data,
|
|
354
|
-
};
|
|
418
|
+
return new TypstNode('supsub', '', [], data);
|
|
355
419
|
}
|
|
356
420
|
case 'leftright': {
|
|
357
421
|
const [left, body, right] = node.args!;
|
|
358
422
|
// These pairs will be handled by Typst compiler by default. No need to add lr()
|
|
359
|
-
const group: TypstNode =
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
423
|
+
const group: TypstNode = new TypstNode(
|
|
424
|
+
'group',
|
|
425
|
+
'',
|
|
426
|
+
node.args!.map(convertTree),
|
|
427
|
+
);
|
|
364
428
|
if ([
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
429
|
+
"[]", "()", "\\{\\}",
|
|
430
|
+
"\\lfloor\\rfloor",
|
|
431
|
+
"\\lceil\\rceil",
|
|
432
|
+
"\\lfloor\\rceil",
|
|
433
|
+
].includes(left.content + right.content)) {
|
|
370
434
|
return group;
|
|
371
435
|
}
|
|
372
|
-
return
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
436
|
+
return new TypstNode(
|
|
437
|
+
'unaryFunc',
|
|
438
|
+
'lr',
|
|
439
|
+
[group],
|
|
440
|
+
);
|
|
377
441
|
}
|
|
378
442
|
case 'binaryFunc': {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
443
|
+
if (node.content === '\\overset') {
|
|
444
|
+
return convert_overset(node);
|
|
445
|
+
}
|
|
446
|
+
return new TypstNode(
|
|
447
|
+
'binaryFunc',
|
|
448
|
+
convertToken(node.content),
|
|
449
|
+
node.args!.map(convertTree),
|
|
450
|
+
);
|
|
384
451
|
}
|
|
385
452
|
case 'unaryFunc': {
|
|
386
453
|
const arg0 = convertTree(node.args![0]);
|
|
387
454
|
// \sqrt{3}{x} -> root(3, x)
|
|
388
455
|
if (node.content === '\\sqrt' && node.data) {
|
|
389
456
|
const data = convertTree(node.data as TexSqrtData); // the number of times to take the root
|
|
390
|
-
return
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
457
|
+
return new TypstNode(
|
|
458
|
+
'binaryFunc',
|
|
459
|
+
'root',
|
|
460
|
+
[data, arg0],
|
|
461
|
+
);
|
|
395
462
|
}
|
|
396
463
|
// \mathbf{a} -> upright(mathbf(a))
|
|
397
464
|
if (node.content === '\\mathbf') {
|
|
398
|
-
const inner: TypstNode =
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
465
|
+
const inner: TypstNode = new TypstNode(
|
|
466
|
+
'unaryFunc',
|
|
467
|
+
'bold',
|
|
468
|
+
[arg0],
|
|
469
|
+
);
|
|
470
|
+
return new TypstNode(
|
|
471
|
+
'unaryFunc',
|
|
472
|
+
'upright',
|
|
473
|
+
[inner],
|
|
474
|
+
);
|
|
408
475
|
}
|
|
409
476
|
// \mathbb{R} -> RR
|
|
410
477
|
if (node.content === '\\mathbb' && arg0.type === 'atom' && /^[A-Z]$/.test(arg0.content)) {
|
|
411
|
-
return
|
|
412
|
-
type: 'symbol',
|
|
413
|
-
content: arg0.content + arg0.content,
|
|
414
|
-
};
|
|
478
|
+
return new TypstNode('symbol', arg0.content + arg0.content);
|
|
415
479
|
}
|
|
416
480
|
// \operatorname{opname} -> op("opname")
|
|
417
481
|
if (node.content === '\\operatorname') {
|
|
@@ -422,54 +486,43 @@ export function convertTree(node: TexNode): TypstNode {
|
|
|
422
486
|
const text = body[0].content;
|
|
423
487
|
|
|
424
488
|
if (TYPST_INTRINSIC_SYMBOLS.includes(text)) {
|
|
425
|
-
return
|
|
426
|
-
type: 'symbol',
|
|
427
|
-
content: text,
|
|
428
|
-
};
|
|
489
|
+
return new TypstNode('symbol', text);
|
|
429
490
|
} else {
|
|
430
|
-
return
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
491
|
+
return new TypstNode(
|
|
492
|
+
'unaryFunc',
|
|
493
|
+
'op',
|
|
494
|
+
[new TypstNode('text', text)],
|
|
495
|
+
);
|
|
435
496
|
}
|
|
436
497
|
}
|
|
437
498
|
|
|
438
499
|
// generic case
|
|
439
|
-
return
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
500
|
+
return new TypstNode(
|
|
501
|
+
'unaryFunc',
|
|
502
|
+
convertToken(node.content),
|
|
503
|
+
node.args!.map(convertTree),
|
|
504
|
+
);
|
|
444
505
|
}
|
|
445
|
-
case 'newline':
|
|
446
|
-
return { type: 'newline', content: '\n' };
|
|
447
506
|
case 'beginend': {
|
|
448
507
|
const matrix = node.data as TexNode[][];
|
|
449
508
|
const data = matrix.map((row) => row.map(convertTree));
|
|
450
509
|
|
|
451
510
|
if (node.content!.startsWith('align')) {
|
|
452
511
|
// align, align*, alignat, alignat*, aligned, etc.
|
|
453
|
-
return
|
|
454
|
-
type: 'align',
|
|
455
|
-
content: '',
|
|
456
|
-
data: data,
|
|
457
|
-
};
|
|
512
|
+
return new TypstNode( 'align', '', [], data);
|
|
458
513
|
} else {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
data: data,
|
|
463
|
-
};
|
|
514
|
+
const res = new TypstNode('matrix', '', [], data);
|
|
515
|
+
res.setOptions({'delim': '#none'});
|
|
516
|
+
return res;
|
|
464
517
|
}
|
|
465
518
|
}
|
|
466
519
|
case 'unknownMacro':
|
|
467
|
-
return
|
|
520
|
+
return new TypstNode('unknown', convertToken(node.content));
|
|
468
521
|
case 'control':
|
|
469
522
|
if (node.content === '\\\\') {
|
|
470
|
-
return
|
|
523
|
+
return new TypstNode('symbol', '\\');
|
|
471
524
|
} else if (node.content === '\\,') {
|
|
472
|
-
return
|
|
525
|
+
return new TypstNode('symbol', 'thin');
|
|
473
526
|
} else {
|
|
474
527
|
throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node);
|
|
475
528
|
}
|
|
@@ -482,10 +535,15 @@ export function convertTree(node: TexNode): TypstNode {
|
|
|
482
535
|
function convertToken(token: string): string {
|
|
483
536
|
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
484
537
|
return token;
|
|
538
|
+
} else if (token === '/') {
|
|
539
|
+
return '\\/';
|
|
540
|
+
} else if (token === '\\|') {
|
|
541
|
+
// \| in LaTeX is double vertical bar looks like ||
|
|
542
|
+
return 'parallel';
|
|
543
|
+
} else if (token === '\\colon') {
|
|
544
|
+
return ':';
|
|
485
545
|
} else if (token === '\\\\') {
|
|
486
546
|
return '\\';
|
|
487
|
-
} else if (token == '/') {
|
|
488
|
-
return '\\/';
|
|
489
547
|
} else if (['\\$', '\\#', '\\&', '\\_'].includes(token)) {
|
|
490
548
|
return token;
|
|
491
549
|
} else if (token.startsWith('\\')) {
|