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