tex2typst 0.3.23 → 0.3.24
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/convert.d.ts +9 -3
- package/dist/generic.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2092 -2009
- package/dist/tex-parser.d.ts +1 -1
- package/dist/tex-tokenizer.d.ts +1 -1
- package/dist/tex-types.d.ts +103 -0
- package/dist/tex-writer.d.ts +1 -2
- package/dist/tex2typst.min.js +13 -13
- package/dist/typst-parser.d.ts +6 -4
- package/dist/typst-tokenizer.d.ts +1 -1
- package/dist/typst-types.d.ts +95 -0
- package/dist/typst-writer.d.ts +4 -2
- package/package.json +1 -1
- package/src/convert.ts +455 -393
- package/src/generic.ts +28 -2
- package/src/index.ts +1 -1
- package/src/map.ts +5 -0
- package/src/tex-parser.ts +45 -54
- package/src/tex-tokenizer.ts +1 -1
- package/src/tex-types.ts +358 -0
- package/src/tex-writer.ts +3 -51
- package/src/typst-parser.ts +53 -55
- package/src/typst-tokenizer.ts +86 -85
- package/src/typst-types.ts +221 -0
- package/src/typst-writer.ts +79 -65
- package/src/util.ts +1 -1
- package/dist/types.d.ts +0 -109
- package/src/types.ts +0 -414
package/src/convert.ts
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
|
-
import { TexNode,
|
|
2
|
-
|
|
1
|
+
import { TexNode, Tex2TypstOptions,
|
|
2
|
+
TexToken, TexTokenType, TexFuncCall, TexGroup, TexSupSub,
|
|
3
|
+
TexText, TexBeginEnd, TexLeftRight,
|
|
4
|
+
TexTerminal} from "./tex-types";
|
|
5
|
+
import { TypstAlign, TypstCases, TypstFraction, TypstFuncCall, TypstGroup, TypstLeftright, TypstMatrix, TypstNode, TypstSupsub, TypstTerminal } from "./typst-types";
|
|
6
|
+
import { TypstNamedParams } from "./typst-types";
|
|
7
|
+
import { TypstSupsubData } from "./typst-types";
|
|
8
|
+
import { TypstToken } from "./typst-types";
|
|
9
|
+
import { TypstTokenType } from "./typst-types";
|
|
3
10
|
import { symbolMap, reverseSymbolMap } from "./map";
|
|
4
|
-
import { array_intersperse } from "./generic";
|
|
11
|
+
import { array_includes, array_intersperse, array_split } from "./generic";
|
|
5
12
|
import { assert } from "./util";
|
|
6
13
|
import { TEX_BINARY_COMMANDS, TEX_UNARY_COMMANDS } from "./tex-tokenizer";
|
|
7
14
|
|
|
8
15
|
|
|
16
|
+
export class ConverterError extends Error {
|
|
17
|
+
node: TexNode | TypstNode | TexToken | TypstToken | null;
|
|
18
|
+
|
|
19
|
+
constructor(message: string, node: TexNode | TypstNode | TexToken | TypstToken | null = null) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "ConverterError";
|
|
22
|
+
this.node = node;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const TYPST_NONE = TypstToken.NONE.toNode();
|
|
27
|
+
|
|
9
28
|
// native textual operators in Typst
|
|
10
29
|
const TYPST_INTRINSIC_OP = [
|
|
11
30
|
'dim',
|
|
@@ -18,13 +37,13 @@ const TYPST_INTRINSIC_OP = [
|
|
|
18
37
|
// 'sgn
|
|
19
38
|
];
|
|
20
39
|
|
|
21
|
-
function
|
|
40
|
+
function _tex_token_str_to_typst(token: string): string | null {
|
|
22
41
|
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
23
42
|
return token;
|
|
24
43
|
} else if (token === '/') {
|
|
25
44
|
return '\\/';
|
|
26
|
-
} else if (
|
|
27
|
-
return
|
|
45
|
+
} else if (['\\\\', '\\{', '\\}', '\\%'].includes(token)) {
|
|
46
|
+
return token.substring(1);
|
|
28
47
|
} else if (['\\$', '\\#', '\\&', '\\_'].includes(token)) {
|
|
29
48
|
return token;
|
|
30
49
|
} else if (token.startsWith('\\')) {
|
|
@@ -35,12 +54,65 @@ function tex_token_to_typst(token: string): string {
|
|
|
35
54
|
// Fall back to the original macro.
|
|
36
55
|
// This works for \alpha, \beta, \gamma, etc.
|
|
37
56
|
// If this.nonStrict is true, this also works for all unknown macros.
|
|
38
|
-
return
|
|
57
|
+
return null;
|
|
39
58
|
}
|
|
40
59
|
}
|
|
41
60
|
return token;
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
function tex_token_to_typst(token: TexToken, options: Tex2TypstOptions): TypstToken {
|
|
64
|
+
let token_type: TypstTokenType;
|
|
65
|
+
switch (token.type) {
|
|
66
|
+
case TexTokenType.EMPTY:
|
|
67
|
+
return TypstToken.NONE;
|
|
68
|
+
case TexTokenType.COMMAND:
|
|
69
|
+
token_type = TypstTokenType.SYMBOL;
|
|
70
|
+
break;
|
|
71
|
+
case TexTokenType.ELEMENT:
|
|
72
|
+
token_type = TypstTokenType.ELEMENT;
|
|
73
|
+
break;
|
|
74
|
+
case TexTokenType.LITERAL:
|
|
75
|
+
// This happens, for example, node={type: 'literal', content: 'myop'} as in `\operatorname{myop}`
|
|
76
|
+
token_type = TypstTokenType.LITERAL;
|
|
77
|
+
break;
|
|
78
|
+
case TexTokenType.COMMENT:
|
|
79
|
+
token_type = TypstTokenType.COMMENT;
|
|
80
|
+
break;
|
|
81
|
+
case TexTokenType.SPACE:
|
|
82
|
+
token_type = TypstTokenType.SPACE;
|
|
83
|
+
break;
|
|
84
|
+
case TexTokenType.NEWLINE:
|
|
85
|
+
token_type = TypstTokenType.NEWLINE;
|
|
86
|
+
break;
|
|
87
|
+
case TexTokenType.CONTROL: {
|
|
88
|
+
if (token.value === '\\\\') {
|
|
89
|
+
// \\ -> \
|
|
90
|
+
return new TypstToken(TypstTokenType.CONTROL, '\\');
|
|
91
|
+
} else if (token.value === '\\!') {
|
|
92
|
+
// \! -> #h(-math.thin.amount)
|
|
93
|
+
return new TypstToken(TypstTokenType.SYMBOL, '#h(-math.thin.amount)');
|
|
94
|
+
} else if (symbolMap.has(token.value.substring(1))) {
|
|
95
|
+
// node.content is one of \, \: \;
|
|
96
|
+
const typst_symbol = symbolMap.get(token.value.substring(1))!;
|
|
97
|
+
return new TypstToken(TypstTokenType.SYMBOL, typst_symbol);
|
|
98
|
+
} else {
|
|
99
|
+
throw new Error(`Unknown control sequence: ${token.value}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
default:
|
|
103
|
+
throw Error(`Unknown token type: ${token.type}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const typst_str = _tex_token_str_to_typst(token.value);
|
|
107
|
+
if (typst_str === null) {
|
|
108
|
+
if (options.nonStrict) {
|
|
109
|
+
return new TypstToken(token_type, token.value.substring(1));
|
|
110
|
+
} else {
|
|
111
|
+
throw new ConverterError(`Unknown token: ${token.value}`, token);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return new TypstToken(token_type, typst_str);
|
|
115
|
+
}
|
|
44
116
|
|
|
45
117
|
// \overset{X}{Y} -> limits(Y)^X
|
|
46
118
|
// and with special case \overset{\text{def}}{=} -> eq.def
|
|
@@ -48,35 +120,19 @@ function convert_overset(node: TexNode, options: Tex2TypstOptions): TypstNode {
|
|
|
48
120
|
const [sup, base] = node.args!;
|
|
49
121
|
|
|
50
122
|
if (options.optimize) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
// \overset{def}{=} is also considered as eq.def
|
|
56
|
-
if (n.type === 'ordgroup' && n.args!.length === 3) {
|
|
57
|
-
const [a1, a2, a3] = n.args!;
|
|
58
|
-
const d = new TexNode('element', 'd');
|
|
59
|
-
const e = new TexNode('element', 'e');
|
|
60
|
-
const f = new TexNode('element', 'f');
|
|
61
|
-
if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return false;
|
|
66
|
-
};
|
|
67
|
-
const is_eq = (n: TexNode): boolean => n.eq(new TexNode('element', '='));
|
|
68
|
-
if (is_def(sup) && is_eq(base)) {
|
|
69
|
-
return new TypstNode('symbol', 'eq.def');
|
|
123
|
+
// \overset{\text{def}}{=} or \overset{def}{=} are considered as eq.def
|
|
124
|
+
if (["\\overset{\\text{def}}{=}", "\\overset{d e f}{=}"].includes(node.toString())) {
|
|
125
|
+
return new TypstToken(TypstTokenType.SYMBOL, 'eq.def').toNode();
|
|
70
126
|
}
|
|
71
127
|
}
|
|
72
|
-
const limits_call = new
|
|
73
|
-
'
|
|
74
|
-
'limits',
|
|
128
|
+
const limits_call = new TypstFuncCall(
|
|
129
|
+
new TypstToken(TypstTokenType.SYMBOL, 'limits'),
|
|
75
130
|
[convert_tex_node_to_typst(base, options)]
|
|
76
131
|
);
|
|
77
|
-
return new
|
|
132
|
+
return new TypstSupsub({
|
|
78
133
|
base: limits_call,
|
|
79
134
|
sup: convert_tex_node_to_typst(sup, options),
|
|
135
|
+
sub: null,
|
|
80
136
|
});
|
|
81
137
|
}
|
|
82
138
|
|
|
@@ -84,14 +140,14 @@ function convert_overset(node: TexNode, options: Tex2TypstOptions): TypstNode {
|
|
|
84
140
|
function convert_underset(node: TexNode, options: Tex2TypstOptions): TypstNode {
|
|
85
141
|
const [sub, base] = node.args!;
|
|
86
142
|
|
|
87
|
-
const limits_call = new
|
|
88
|
-
'
|
|
89
|
-
'limits',
|
|
143
|
+
const limits_call = new TypstFuncCall(
|
|
144
|
+
new TypstToken(TypstTokenType.SYMBOL, 'limits'),
|
|
90
145
|
[convert_tex_node_to_typst(base, options)]
|
|
91
146
|
);
|
|
92
|
-
return new
|
|
147
|
+
return new TypstSupsub({
|
|
93
148
|
base: limits_call,
|
|
94
149
|
sub: convert_tex_node_to_typst(sub, options),
|
|
150
|
+
sup: null,
|
|
95
151
|
});
|
|
96
152
|
}
|
|
97
153
|
|
|
@@ -118,323 +174,280 @@ function convert_tex_array_align_literal(alignLiteral: string): TypstNamedParams
|
|
|
118
174
|
augment_str = `#(vline: (${vlinePositions.join(', ')}))`;
|
|
119
175
|
}
|
|
120
176
|
|
|
121
|
-
np['augment'] = new
|
|
177
|
+
np['augment'] = new TypstToken(TypstTokenType.LITERAL, augment_str).toNode();
|
|
122
178
|
}
|
|
123
179
|
|
|
124
180
|
const alignments = chars
|
|
125
181
|
.map(c => alignMap[c])
|
|
126
182
|
.filter((x) => x !== undefined)
|
|
127
|
-
.map(s => new
|
|
183
|
+
.map(s => new TypstToken(TypstTokenType.LITERAL, s!).toNode());
|
|
128
184
|
|
|
129
185
|
if (alignments.length > 0) {
|
|
130
186
|
const all_same = alignments.every(item => item.eq(alignments[0]));
|
|
131
|
-
np['align'] = all_same ? alignments[0] : new
|
|
187
|
+
np['align'] = all_same ? alignments[0] : new TypstToken(TypstTokenType.LITERAL, '#center').toNode();
|
|
132
188
|
}
|
|
133
189
|
return np;
|
|
134
190
|
}
|
|
135
191
|
|
|
136
192
|
|
|
137
|
-
export function convert_tex_node_to_typst(
|
|
138
|
-
switch (
|
|
139
|
-
case '
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
case 'ordgroup':
|
|
144
|
-
return new TypstNode(
|
|
145
|
-
'group',
|
|
146
|
-
'',
|
|
147
|
-
node.args!.map((n) => convert_tex_node_to_typst(n, options))
|
|
148
|
-
);
|
|
149
|
-
case 'element':
|
|
150
|
-
return new TypstNode('atom', tex_token_to_typst(node.content));
|
|
151
|
-
case 'symbol':
|
|
152
|
-
return new TypstNode('symbol', tex_token_to_typst(node.content));
|
|
193
|
+
export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2TypstOptions = {}): TypstNode {
|
|
194
|
+
switch (abstractNode.type) {
|
|
195
|
+
case 'terminal': {
|
|
196
|
+
const node = abstractNode as TexTerminal;
|
|
197
|
+
return tex_token_to_typst(node.head, options).toNode();
|
|
198
|
+
}
|
|
153
199
|
case 'text': {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
options.nonAsciiWrapper
|
|
158
|
-
[new
|
|
200
|
+
const node = abstractNode as TexText;
|
|
201
|
+
if ((/[^\x00-\x7F]+/).test(node.head.value) && options.nonAsciiWrapper !== "") {
|
|
202
|
+
return new TypstFuncCall(
|
|
203
|
+
new TypstToken(TypstTokenType.SYMBOL, options.nonAsciiWrapper!),
|
|
204
|
+
[new TypstToken(TypstTokenType.TEXT, node.head.value).toNode()]
|
|
159
205
|
);
|
|
160
206
|
}
|
|
161
|
-
return new
|
|
207
|
+
return new TypstToken(TypstTokenType.TEXT, node.head.value).toNode();
|
|
162
208
|
}
|
|
163
|
-
case '
|
|
164
|
-
|
|
165
|
-
return new
|
|
166
|
-
|
|
167
|
-
|
|
209
|
+
case 'ordgroup':
|
|
210
|
+
const node = abstractNode as TexGroup;
|
|
211
|
+
return new TypstGroup(
|
|
212
|
+
node.args!.map((n) => convert_tex_node_to_typst(n, options))
|
|
213
|
+
);
|
|
168
214
|
case 'supsub': {
|
|
169
|
-
|
|
215
|
+
const node = abstractNode as TexSupSub;
|
|
216
|
+
let { base, sup, sub } = node;
|
|
170
217
|
|
|
171
218
|
// special hook for overbrace
|
|
172
|
-
if (base && base.type === '
|
|
173
|
-
return new
|
|
174
|
-
'
|
|
175
|
-
'overbrace',
|
|
219
|
+
if (base && base.type === 'funcCall' && base.head.value === '\\overbrace' && sup) {
|
|
220
|
+
return new TypstFuncCall(
|
|
221
|
+
new TypstToken(TypstTokenType.SYMBOL, 'overbrace'),
|
|
176
222
|
[convert_tex_node_to_typst(base.args![0], options), convert_tex_node_to_typst(sup, options)]
|
|
177
223
|
);
|
|
178
|
-
} else if (base && base.type === '
|
|
179
|
-
return new
|
|
180
|
-
'
|
|
181
|
-
'underbrace',
|
|
224
|
+
} else if (base && base.type === 'funcCall' && base.head.value === '\\underbrace' && sub) {
|
|
225
|
+
return new TypstFuncCall(
|
|
226
|
+
new TypstToken(TypstTokenType.SYMBOL, 'underbrace'),
|
|
182
227
|
[convert_tex_node_to_typst(base.args![0], options), convert_tex_node_to_typst(sub, options)]
|
|
183
228
|
);
|
|
184
229
|
}
|
|
185
230
|
|
|
186
231
|
const data: TypstSupsubData = {
|
|
187
232
|
base: convert_tex_node_to_typst(base, options),
|
|
233
|
+
sup: sup? convert_tex_node_to_typst(sup, options) : null,
|
|
234
|
+
sub: sub? convert_tex_node_to_typst(sub, options) : null,
|
|
188
235
|
};
|
|
189
|
-
if (data.base.type === 'none') {
|
|
190
|
-
data.base = new TypstNode('none', '');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (sup) {
|
|
194
|
-
data.sup = convert_tex_node_to_typst(sup, options);
|
|
195
|
-
}
|
|
196
236
|
|
|
197
|
-
|
|
198
|
-
data.sub = convert_tex_node_to_typst(sub, options);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return new TypstNode('supsub', '', [], data);
|
|
237
|
+
return new TypstSupsub(data);
|
|
202
238
|
}
|
|
203
239
|
case 'leftright': {
|
|
204
|
-
const
|
|
205
|
-
const
|
|
240
|
+
const node = abstractNode as TexLeftRight;
|
|
241
|
+
const { left, right } = node;
|
|
242
|
+
const [_body] = node.args!;
|
|
243
|
+
// const [typ_left, typ_body, typ_right] = node.args!.map((n) => convert_tex_node_to_typst(n, options));
|
|
244
|
+
const typ_body = convert_tex_node_to_typst(_body, options);
|
|
245
|
+
|
|
246
|
+
|
|
206
247
|
|
|
207
248
|
if (options.optimize) {
|
|
208
249
|
// optimization off: "lr(bar.v.double a + 1/2 bar.v.double)"
|
|
209
250
|
// optimization on : "norm(a + 1/2)"
|
|
210
|
-
if (left
|
|
211
|
-
|
|
212
|
-
|
|
251
|
+
if (left !== null && right !== null) {
|
|
252
|
+
const typ_left = tex_token_to_typst(left, options);
|
|
253
|
+
const typ_right = tex_token_to_typst(right, options);
|
|
254
|
+
if (left.value === '\\|' && right.value === '\\|') {
|
|
255
|
+
return new TypstFuncCall(new TypstToken(TypstTokenType.SYMBOL, 'norm'), [typ_body]);
|
|
256
|
+
}
|
|
213
257
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
258
|
+
// These pairs will be handled by Typst compiler by default. No need to add lr()
|
|
259
|
+
if ([
|
|
260
|
+
"[]", "()", "\\{\\}",
|
|
261
|
+
"\\lfloor\\rfloor",
|
|
262
|
+
"\\lceil\\rceil",
|
|
263
|
+
"\\lfloor\\rceil",
|
|
264
|
+
].includes(left.value + right.value)) {
|
|
265
|
+
return new TypstGroup([typ_left.toNode(), typ_body, typ_right.toNode()]);
|
|
266
|
+
}
|
|
222
267
|
}
|
|
223
268
|
}
|
|
224
269
|
|
|
225
|
-
const group = new TypstNode(
|
|
226
|
-
'group',
|
|
227
|
-
'',
|
|
228
|
-
[typ_left, typ_body, typ_right]
|
|
229
|
-
);
|
|
230
|
-
|
|
231
270
|
// "\left\{ a + \frac{1}{3} \right." -> "lr(\{ a + 1/3)"
|
|
232
|
-
// "\left. a + \frac{1}{3} \right\}" -> "lr( a +
|
|
271
|
+
// "\left. a + \frac{1}{3} \right\}" -> "lr( a + 1/3 \})"
|
|
233
272
|
// Note that: In lr(), if one side of delimiter doesn't present (i.e. derived from "\\left." or "\\right."),
|
|
234
273
|
// "(", ")", "{", "[", should be escaped with "\" to be the other side of delimiter.
|
|
235
274
|
// Simple "lr({ a+1/3)" doesn't compile in Typst.
|
|
236
|
-
const escape_curly_or_paren = function(s:
|
|
237
|
-
if (["(", ")", "{", "["].includes(s)) {
|
|
238
|
-
return "\\" + s;
|
|
275
|
+
const escape_curly_or_paren = function(s: TypstToken): TypstToken {
|
|
276
|
+
if (["(", ")", "{", "["].includes(s.value)) {
|
|
277
|
+
return new TypstToken(TypstTokenType.ELEMENT, "\\" + s.value);
|
|
239
278
|
} else {
|
|
240
279
|
return s;
|
|
241
280
|
}
|
|
242
281
|
};
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
typ_right
|
|
248
|
-
group.args = [typ_body, typ_right];
|
|
249
|
-
}
|
|
250
|
-
return new TypstNode('funcCall', 'lr', [group]);
|
|
251
|
-
}
|
|
252
|
-
case 'binaryFunc': {
|
|
253
|
-
if (node.content === '\\overset') {
|
|
254
|
-
return convert_overset(node, options);
|
|
255
|
-
}
|
|
256
|
-
if (node.content === '\\underset') {
|
|
257
|
-
return convert_underset(node, options);
|
|
282
|
+
|
|
283
|
+
let typ_left = left? tex_token_to_typst(left, options) : null;
|
|
284
|
+
let typ_right = right? tex_token_to_typst(right, options) : null;
|
|
285
|
+
if (typ_left === null && typ_right !== null) { // left.
|
|
286
|
+
typ_right = escape_curly_or_paren(typ_right);
|
|
258
287
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (options.fracToSlash) {
|
|
262
|
-
return new TypstNode(
|
|
263
|
-
'fraction',
|
|
264
|
-
'',
|
|
265
|
-
node.args!.map((n) => convert_tex_node_to_typst(n, options))
|
|
266
|
-
);
|
|
267
|
-
}
|
|
288
|
+
if (typ_right === null && typ_left !== null) { // right.
|
|
289
|
+
typ_left = escape_curly_or_paren(typ_left);
|
|
268
290
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
291
|
+
|
|
292
|
+
return new TypstLeftright(
|
|
293
|
+
new TypstToken(TypstTokenType.SYMBOL, 'lr'),
|
|
294
|
+
[typ_body],
|
|
295
|
+
{ left: typ_left, right: typ_right }
|
|
273
296
|
);
|
|
274
297
|
}
|
|
275
|
-
case '
|
|
298
|
+
case 'funcCall': {
|
|
299
|
+
const node = abstractNode as TexFuncCall;
|
|
276
300
|
const arg0 = convert_tex_node_to_typst(node.args![0], options);
|
|
277
|
-
// \sqrt
|
|
278
|
-
if (node.
|
|
279
|
-
const data = convert_tex_node_to_typst(node.data
|
|
280
|
-
return new
|
|
281
|
-
'
|
|
282
|
-
'root',
|
|
301
|
+
// \sqrt[3]{x} -> root(3, x)
|
|
302
|
+
if (node.head.value === '\\sqrt' && node.data) {
|
|
303
|
+
const data = convert_tex_node_to_typst(node.data, options); // the number of times to take the root
|
|
304
|
+
return new TypstFuncCall(
|
|
305
|
+
new TypstToken(TypstTokenType.SYMBOL, 'root'),
|
|
283
306
|
[data, arg0]
|
|
284
307
|
);
|
|
285
308
|
}
|
|
286
309
|
// \mathbf{a} -> upright(bold(a))
|
|
287
|
-
if (node.
|
|
288
|
-
const inner: TypstNode = new
|
|
289
|
-
'
|
|
290
|
-
'bold',
|
|
310
|
+
if (node.head.value === '\\mathbf') {
|
|
311
|
+
const inner: TypstNode = new TypstFuncCall(
|
|
312
|
+
new TypstToken(TypstTokenType.SYMBOL, 'bold'),
|
|
291
313
|
[arg0]
|
|
292
314
|
);
|
|
293
|
-
return new
|
|
294
|
-
'
|
|
295
|
-
'upright',
|
|
315
|
+
return new TypstFuncCall(
|
|
316
|
+
new TypstToken(TypstTokenType.SYMBOL, 'upright'),
|
|
296
317
|
[inner]
|
|
297
318
|
);
|
|
298
319
|
}
|
|
299
320
|
// \overrightarrow{AB} -> arrow(A B)
|
|
300
|
-
if (node.
|
|
301
|
-
return new
|
|
302
|
-
'
|
|
303
|
-
'arrow',
|
|
321
|
+
if (node.head.value === '\\overrightarrow') {
|
|
322
|
+
return new TypstFuncCall(
|
|
323
|
+
new TypstToken(TypstTokenType.SYMBOL, 'arrow'),
|
|
304
324
|
[arg0]
|
|
305
325
|
);
|
|
306
326
|
}
|
|
307
327
|
// \overleftarrow{AB} -> accent(A B, arrow.l)
|
|
308
|
-
if (node.
|
|
309
|
-
return new
|
|
310
|
-
'
|
|
311
|
-
'
|
|
312
|
-
[arg0, new TypstNode('symbol', 'arrow.l')]
|
|
328
|
+
if (node.head.value === '\\overleftarrow') {
|
|
329
|
+
return new TypstFuncCall(
|
|
330
|
+
new TypstToken(TypstTokenType.SYMBOL, 'accent'),
|
|
331
|
+
[arg0, new TypstToken(TypstTokenType.SYMBOL, 'arrow.l').toNode()]
|
|
313
332
|
);
|
|
314
333
|
}
|
|
315
334
|
// \operatorname{opname} -> op("opname")
|
|
316
|
-
if (node.
|
|
335
|
+
if (node.head.value === '\\operatorname') {
|
|
317
336
|
// arg0 must be of type 'literal' in this situation
|
|
318
337
|
if (options.optimize) {
|
|
319
|
-
if (TYPST_INTRINSIC_OP.includes(arg0.
|
|
320
|
-
return new
|
|
338
|
+
if (TYPST_INTRINSIC_OP.includes(arg0.head.value)) {
|
|
339
|
+
return new TypstToken(TypstTokenType.SYMBOL, arg0.head.value).toNode();
|
|
321
340
|
}
|
|
322
341
|
}
|
|
323
|
-
return new
|
|
342
|
+
return new TypstFuncCall(new TypstToken(TypstTokenType.SYMBOL, 'op'), [new TypstToken(TypstTokenType.TEXT, arg0.head.value).toNode()]);
|
|
324
343
|
}
|
|
325
344
|
|
|
326
345
|
// \substack{a \\ b} -> `a \ b`
|
|
327
346
|
// as in translation from \sum_{\substack{a \\ b}} to sum_(a \ b)
|
|
328
|
-
if (node.
|
|
347
|
+
if (node.head.value === '\\substack') {
|
|
329
348
|
return arg0;
|
|
330
349
|
}
|
|
331
350
|
|
|
351
|
+
if (node.head.value === '\\overset') {
|
|
352
|
+
return convert_overset(node, options);
|
|
353
|
+
}
|
|
354
|
+
if (node.head.value === '\\underset') {
|
|
355
|
+
return convert_underset(node, options);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// \frac{a}{b} -> a / b
|
|
359
|
+
if (node.head.value === '\\frac') {
|
|
360
|
+
if (options.fracToSlash) {
|
|
361
|
+
return new TypstFraction(node.args!.map((n) => convert_tex_node_to_typst(n, options)));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
332
364
|
if(options.optimize) {
|
|
333
365
|
// \mathbb{R} -> RR
|
|
334
|
-
if (node.
|
|
335
|
-
return new
|
|
366
|
+
if (node.head.value === '\\mathbb' && /^\\mathbb{[A-Z]}$/.test(node.toString())) {
|
|
367
|
+
return new TypstToken(TypstTokenType.SYMBOL, arg0.head.value.repeat(2)).toNode();
|
|
336
368
|
}
|
|
337
369
|
// \mathrm{d} -> dif
|
|
338
|
-
if (node.
|
|
339
|
-
return new
|
|
370
|
+
if (node.head.value === '\\mathrm' && node.toString() === '\\mathrm{d}') {
|
|
371
|
+
return new TypstToken(TypstTokenType.SYMBOL, 'dif').toNode();
|
|
340
372
|
}
|
|
341
373
|
}
|
|
342
374
|
|
|
343
375
|
// generic case
|
|
344
|
-
return new
|
|
345
|
-
|
|
346
|
-
tex_token_to_typst(node.content),
|
|
376
|
+
return new TypstFuncCall(
|
|
377
|
+
tex_token_to_typst(node.head, options),
|
|
347
378
|
node.args!.map((n) => convert_tex_node_to_typst(n, options))
|
|
348
379
|
);
|
|
349
380
|
}
|
|
350
381
|
case 'beginend': {
|
|
351
|
-
const
|
|
352
|
-
const data = matrix.map((row) => row.map((n) => convert_tex_node_to_typst(n, options)));
|
|
382
|
+
const node = abstractNode as TexBeginEnd;
|
|
383
|
+
const data = node.matrix.map((row) => row.map((n) => convert_tex_node_to_typst(n, options)));
|
|
353
384
|
|
|
354
|
-
if (node.
|
|
385
|
+
if (node.head.value.startsWith('align')) {
|
|
355
386
|
// align, align*, alignat, alignat*, aligned, etc.
|
|
356
|
-
return new
|
|
387
|
+
return new TypstAlign(data);
|
|
357
388
|
}
|
|
358
|
-
if (node.
|
|
359
|
-
return new
|
|
389
|
+
if (node.head.value === 'cases') {
|
|
390
|
+
return new TypstCases(data);
|
|
360
391
|
}
|
|
361
|
-
if (node.
|
|
392
|
+
if (node.head.value === 'subarray') {
|
|
362
393
|
const align_node = node.args![0];
|
|
363
|
-
switch (align_node.
|
|
394
|
+
switch (align_node.head.value) {
|
|
364
395
|
case 'r':
|
|
365
|
-
data.forEach(row => row[0].args!.push(new
|
|
396
|
+
data.forEach(row => row[0].args!.push(new TypstToken(TypstTokenType.CONTROL, '&').toNode()));
|
|
366
397
|
break;
|
|
367
398
|
case 'l':
|
|
368
|
-
data.forEach(row => row[0].args!.unshift(new
|
|
399
|
+
data.forEach(row => row[0].args!.unshift(new TypstToken(TypstTokenType.CONTROL, '&').toNode()));
|
|
369
400
|
break;
|
|
370
401
|
default:
|
|
371
402
|
break;
|
|
372
403
|
}
|
|
373
|
-
return new
|
|
404
|
+
return new TypstAlign(data);
|
|
374
405
|
}
|
|
375
|
-
if (node.
|
|
406
|
+
if (node.head.value === 'array') {
|
|
376
407
|
const np: TypstNamedParams = { 'delim': TYPST_NONE };
|
|
377
408
|
|
|
378
|
-
assert(node.args!.length > 0 && node.args![0].type ===
|
|
379
|
-
const np_new = convert_tex_array_align_literal(node.args![0].
|
|
409
|
+
assert(node.args!.length > 0 && node.args![0].head.type === TexTokenType.LITERAL);
|
|
410
|
+
const np_new = convert_tex_array_align_literal(node.args![0].head.value);
|
|
380
411
|
Object.assign(np, np_new);
|
|
381
412
|
|
|
382
|
-
const res = new
|
|
413
|
+
const res = new TypstMatrix(data);
|
|
383
414
|
res.setOptions(np);
|
|
384
415
|
return res;
|
|
385
416
|
}
|
|
386
|
-
if (node.
|
|
387
|
-
const res = new
|
|
388
|
-
let delim:
|
|
389
|
-
switch (node.
|
|
417
|
+
if (node.head.value.endsWith('matrix')) {
|
|
418
|
+
const res = new TypstMatrix(data);
|
|
419
|
+
let delim: TypstToken;
|
|
420
|
+
switch (node.head.value) {
|
|
390
421
|
case 'matrix':
|
|
391
|
-
delim =
|
|
422
|
+
delim = TypstToken.NONE;
|
|
392
423
|
break;
|
|
393
424
|
case 'pmatrix':
|
|
394
|
-
// delim = new
|
|
425
|
+
// delim = new TypstToken(TypstTokenType.TEXT, '(');
|
|
395
426
|
// break;
|
|
396
427
|
return res; // typst mat use delim:"(" by default
|
|
397
428
|
case 'bmatrix':
|
|
398
|
-
delim = new
|
|
429
|
+
delim = new TypstToken(TypstTokenType.TEXT, '[');
|
|
399
430
|
break;
|
|
400
431
|
case 'Bmatrix':
|
|
401
|
-
delim = new
|
|
432
|
+
delim = new TypstToken(TypstTokenType.TEXT, '{');
|
|
402
433
|
break;
|
|
403
434
|
case 'vmatrix':
|
|
404
|
-
delim = new
|
|
435
|
+
delim = new TypstToken(TypstTokenType.TEXT, '|');
|
|
405
436
|
break;
|
|
406
437
|
case 'Vmatrix': {
|
|
407
|
-
delim = new
|
|
438
|
+
delim = new TypstToken(TypstTokenType.SYMBOL, 'bar.v.double');
|
|
408
439
|
break;
|
|
409
440
|
}
|
|
410
441
|
default:
|
|
411
|
-
throw new
|
|
442
|
+
throw new ConverterError(`Unimplemented beginend: ${node.head}`, node);
|
|
412
443
|
}
|
|
413
|
-
res.setOptions({ 'delim': delim
|
|
444
|
+
res.setOptions({ 'delim': delim.toNode()});
|
|
414
445
|
return res;
|
|
415
446
|
}
|
|
416
|
-
throw new
|
|
447
|
+
throw new ConverterError(`Unimplemented beginend: ${node.head}`, node);
|
|
417
448
|
}
|
|
418
|
-
case 'unknownMacro':
|
|
419
|
-
return new TypstNode('unknown', tex_token_to_typst(node.content));
|
|
420
|
-
case 'control':
|
|
421
|
-
if (node.content === '\\\\') {
|
|
422
|
-
// \\ -> \
|
|
423
|
-
return new TypstNode('symbol', '\\');
|
|
424
|
-
} else if (node.content === '\\!') {
|
|
425
|
-
// \! -> #h(-math.thin.amount)
|
|
426
|
-
return new TypstNode('funcCall', '#h', [
|
|
427
|
-
new TypstNode('literal', '-math.thin.amount')
|
|
428
|
-
]);
|
|
429
|
-
} else if (symbolMap.has(node.content.substring(1))) {
|
|
430
|
-
// node.content is one of \, \: \;
|
|
431
|
-
const typst_symbol = symbolMap.get(node.content.substring(1))!;
|
|
432
|
-
return new TypstNode('symbol', typst_symbol);
|
|
433
|
-
} else {
|
|
434
|
-
throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node);
|
|
435
|
-
}
|
|
436
449
|
default:
|
|
437
|
-
throw new
|
|
450
|
+
throw new ConverterError(`Unimplemented node type: ${abstractNode.type}`, abstractNode);
|
|
438
451
|
}
|
|
439
452
|
}
|
|
440
453
|
|
|
@@ -472,194 +485,252 @@ const TYPST_BINARY_FUNCTIONS: string[] = [
|
|
|
472
485
|
];
|
|
473
486
|
*/
|
|
474
487
|
|
|
475
|
-
function apply_escape_if_needed(c:
|
|
476
|
-
if (['{', '}', '%'].includes(c)) {
|
|
477
|
-
return '\\' + c;
|
|
488
|
+
function apply_escape_if_needed(c: TexToken): TexToken {
|
|
489
|
+
if (['{', '}', '%'].includes(c.value)) {
|
|
490
|
+
return new TexToken(TexTokenType.ELEMENT, '\\' + c.value);
|
|
478
491
|
}
|
|
479
492
|
return c;
|
|
480
493
|
}
|
|
481
494
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
495
|
+
|
|
496
|
+
function typst_token_to_tex(token: TypstToken): TexToken {
|
|
497
|
+
switch (token.type) {
|
|
498
|
+
case TypstTokenType.NONE:
|
|
499
|
+
// e.g. Typst `#none^2` is converted to TeX `^2`
|
|
500
|
+
return TexToken.EMPTY;
|
|
501
|
+
case TypstTokenType.SYMBOL: {
|
|
502
|
+
const _typst_symbol_to_tex = function(symbol: string): string {
|
|
503
|
+
if (reverseSymbolMap.has(symbol)) {
|
|
504
|
+
return '\\' + reverseSymbolMap.get(symbol)!;
|
|
505
|
+
} else {
|
|
506
|
+
return '\\' + symbol;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return new TexToken(TexTokenType.COMMAND, _typst_symbol_to_tex(token.value));
|
|
510
|
+
}
|
|
511
|
+
case TypstTokenType.ELEMENT: {
|
|
512
|
+
let value: string;
|
|
513
|
+
if (['{', '}', '%'].includes(token.value)) {
|
|
514
|
+
value = '\\' + token.value;
|
|
515
|
+
} else {
|
|
516
|
+
value = token.value;
|
|
517
|
+
}
|
|
518
|
+
return new TexToken(TexTokenType.ELEMENT, value);
|
|
519
|
+
}
|
|
520
|
+
case TypstTokenType.LITERAL:
|
|
521
|
+
return new TexToken(TexTokenType.LITERAL, token.value);
|
|
522
|
+
case TypstTokenType.TEXT:
|
|
523
|
+
return new TexToken(TexTokenType.LITERAL, token.value);
|
|
524
|
+
case TypstTokenType.COMMENT:
|
|
525
|
+
return new TexToken(TexTokenType.COMMENT, token.value);
|
|
526
|
+
case TypstTokenType.SPACE:
|
|
527
|
+
return new TexToken(TexTokenType.SPACE, token.value);
|
|
528
|
+
case TypstTokenType.CONTROL: {
|
|
529
|
+
let value: string;
|
|
530
|
+
switch(token.value) {
|
|
531
|
+
case '\\':
|
|
532
|
+
value = '\\\\';
|
|
533
|
+
break;
|
|
534
|
+
case '&':
|
|
535
|
+
value = '&';
|
|
536
|
+
break;
|
|
537
|
+
default:
|
|
538
|
+
throw new Error(`[typst_token_to_tex]Unimplemented control sequence: ${token.value}`);
|
|
539
|
+
}
|
|
540
|
+
return new TexToken(TexTokenType.CONTROL, value);
|
|
541
|
+
}
|
|
542
|
+
case TypstTokenType.NEWLINE:
|
|
543
|
+
return new TexToken(TexTokenType.NEWLINE, token.value);
|
|
544
|
+
default:
|
|
545
|
+
throw new Error(`Unimplemented token type: ${token.type}`);
|
|
487
546
|
}
|
|
488
|
-
return '\\' + token;
|
|
489
547
|
}
|
|
490
548
|
|
|
491
549
|
|
|
492
|
-
const TEX_NODE_COMMA = new
|
|
550
|
+
const TEX_NODE_COMMA = new TexToken(TexTokenType.ELEMENT, ',').toNode();
|
|
493
551
|
|
|
494
|
-
export function convert_typst_node_to_tex(
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
552
|
+
export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
|
|
553
|
+
switch (abstractNode.type) {
|
|
554
|
+
case 'terminal': {
|
|
555
|
+
const node = abstractNode as TypstTerminal;
|
|
556
|
+
if (node.head.type === TypstTokenType.SYMBOL) {
|
|
557
|
+
// special hook for eq.def
|
|
558
|
+
if (node.head.value === 'eq.def') {
|
|
559
|
+
return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\overset'), [
|
|
560
|
+
new TexText(new TexToken(TexTokenType.LITERAL, 'def')),
|
|
561
|
+
new TexToken(TexTokenType.ELEMENT, '=').toNode()
|
|
562
|
+
]);
|
|
563
|
+
}
|
|
564
|
+
// special hook for comma
|
|
565
|
+
if(node.head.value === 'comma') {
|
|
566
|
+
return new TexToken(TexTokenType.ELEMENT, ',').toNode();
|
|
567
|
+
}
|
|
568
|
+
// special hook for dif
|
|
569
|
+
if(node.head.value === 'dif') {
|
|
570
|
+
return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\mathrm'), [new TexToken(TexTokenType.ELEMENT, 'd').toNode()]);
|
|
571
|
+
}
|
|
572
|
+
// special hook for hyph and hyph.minus
|
|
573
|
+
if(node.head.value === 'hyph' || node.head.value === 'hyph.minus') {
|
|
574
|
+
return new TexText(new TexToken(TexTokenType.LITERAL, '-'));
|
|
575
|
+
}
|
|
576
|
+
// special hook for mathbb{R} <-- RR
|
|
577
|
+
if(/^([A-Z])\1$/.test(node.head.value)) {
|
|
578
|
+
return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\mathbb'), [
|
|
579
|
+
new TexToken(TexTokenType.ELEMENT, node.head.value[0]).toNode()
|
|
580
|
+
]);
|
|
581
|
+
}
|
|
522
582
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
return new TexNode('unaryFunc', '\\mathbb', [
|
|
526
|
-
new TexNode('element', node.content[0])
|
|
527
|
-
]);
|
|
583
|
+
if (node.head.type === TypstTokenType.TEXT) {
|
|
584
|
+
return new TexText(new TexToken(TexTokenType.LITERAL, node.head.value));
|
|
528
585
|
}
|
|
529
|
-
return
|
|
586
|
+
return typst_token_to_tex(node.head).toNode();
|
|
530
587
|
}
|
|
531
|
-
|
|
532
|
-
return new TexNode('literal', node.content);
|
|
533
|
-
case 'text':
|
|
534
|
-
return new TexNode('text', node.content);
|
|
535
|
-
case 'comment':
|
|
536
|
-
return new TexNode('comment', node.content);
|
|
588
|
+
|
|
537
589
|
case 'group': {
|
|
590
|
+
const node = abstractNode as TypstGroup;
|
|
538
591
|
const args = node.args!.map(convert_typst_node_to_tex);
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
592
|
+
const alignment_char = new TexToken(TexTokenType.CONTROL, '&').toNode();
|
|
593
|
+
const newline_char = new TexToken(TexTokenType.CONTROL, '\\\\').toNode();
|
|
594
|
+
if (array_includes(args, alignment_char)) {
|
|
595
|
+
// wrap the whole math formula with \begin{aligned} and \end{aligned}
|
|
596
|
+
const rows = array_split(args, newline_char);
|
|
597
|
+
const data: TexNode[][] = [];
|
|
598
|
+
for(const row of rows) {
|
|
599
|
+
const cells = array_split(row, alignment_char);
|
|
600
|
+
data.push(cells.map(cell => new TexGroup(cell)));
|
|
601
|
+
}
|
|
602
|
+
return new TexBeginEnd(new TexToken(TexTokenType.LITERAL, 'aligned'), [], data);
|
|
545
603
|
}
|
|
546
|
-
return new
|
|
604
|
+
return new TexGroup(args);
|
|
605
|
+
}
|
|
606
|
+
case 'leftright': {
|
|
607
|
+
const node = abstractNode as TypstLeftright;
|
|
608
|
+
const args = node.args!.map(convert_typst_node_to_tex);
|
|
609
|
+
let left = node.left? typst_token_to_tex(node.left) : new TexToken(TexTokenType.ELEMENT, '.');
|
|
610
|
+
let right = node.right? typst_token_to_tex(node.right) : new TexToken(TexTokenType.ELEMENT, '.');
|
|
611
|
+
// const is_over_high = node.isOverHigh();
|
|
612
|
+
// const left_delim = is_over_high ? '\\left(' : '(';
|
|
613
|
+
// const right_delim = is_over_high ? '\\right)' : ')';
|
|
614
|
+
if (node.isOverHigh()) {
|
|
615
|
+
left.value = '\\left' + left.value;
|
|
616
|
+
right.value = '\\right' + right.value;
|
|
617
|
+
}
|
|
618
|
+
args.unshift(left.toNode());
|
|
619
|
+
args.push(right.toNode());
|
|
620
|
+
// TODO: should be TeXLeftRight(...)
|
|
621
|
+
// But currently writer will output `\left |` while people commonly prefer `\left|`.
|
|
622
|
+
return new TexGroup(args);
|
|
547
623
|
}
|
|
548
624
|
case 'funcCall': {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
625
|
+
const node = abstractNode as TypstFuncCall;
|
|
626
|
+
switch (node.head.value) {
|
|
627
|
+
// special hook for norm
|
|
628
|
+
// `\| a \|` <- `norm(a)`
|
|
629
|
+
// `\left\| a + \frac{1}{3} \right\|` <- `norm(a + 1/3)`
|
|
630
|
+
case 'norm': {
|
|
631
|
+
const arg0 = node.args![0];
|
|
632
|
+
const args = [ convert_typst_node_to_tex(arg0) ];
|
|
633
|
+
if (node.isOverHigh()) {
|
|
634
|
+
return new TexLeftRight(args, {
|
|
635
|
+
left: new TexToken(TexTokenType.COMMAND, "\\|"),
|
|
636
|
+
right: new TexToken(TexTokenType.COMMAND, "\\|")
|
|
637
|
+
});
|
|
638
|
+
} else {
|
|
639
|
+
return new TexGroup(args);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
// special hook for floor, ceil
|
|
643
|
+
// `\lfloor a \rfloor` <- `floor(a)`
|
|
644
|
+
// `\lceil a \rceil` <- `ceil(a)`
|
|
645
|
+
// `\left\lfloor a \right\rfloor` <- `floor(a)`
|
|
646
|
+
// `\left\lceil a \right\rceil` <- `ceil(a)`
|
|
647
|
+
case 'floor':
|
|
648
|
+
case 'ceil': {
|
|
649
|
+
const left = "\\l" + node.head.value;
|
|
650
|
+
const right = "\\r" + node.head.value;
|
|
651
|
+
const arg0 = node.args![0];
|
|
652
|
+
const typ_arg0 = convert_typst_node_to_tex(arg0);
|
|
653
|
+
const left_node = new TexToken(TexTokenType.COMMAND, left);
|
|
654
|
+
const right_node = new TexToken(TexTokenType.COMMAND, right);
|
|
655
|
+
if (node.isOverHigh()) {
|
|
656
|
+
return new TexLeftRight([typ_arg0], {
|
|
657
|
+
left: left_node,
|
|
658
|
+
right: right_node
|
|
659
|
+
});
|
|
660
|
+
} else {
|
|
661
|
+
return new TexGroup([left_node.toNode(), typ_arg0, right_node.toNode()]);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
// special hook for root
|
|
665
|
+
case 'root': {
|
|
666
|
+
const [degree, radicand] = node.args!;
|
|
667
|
+
const data = convert_typst_node_to_tex(degree);
|
|
668
|
+
return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\sqrt'), [convert_typst_node_to_tex(radicand)], data);
|
|
669
|
+
}
|
|
670
|
+
// special hook for overbrace and underbrace
|
|
671
|
+
case 'overbrace':
|
|
672
|
+
case 'underbrace': {
|
|
673
|
+
const [body, label] = node.args!;
|
|
674
|
+
const base = new TexFuncCall(typst_token_to_tex(node.head), [convert_typst_node_to_tex(body)]);
|
|
675
|
+
const script = convert_typst_node_to_tex(label);
|
|
676
|
+
const data = node.head.value === 'overbrace' ? { base, sup: script, sub: null } : { base, sub: script, sup: null };
|
|
677
|
+
return new TexSupSub(data);
|
|
678
|
+
}
|
|
679
|
+
// special hook for vec
|
|
680
|
+
// "vec(a, b, c)" -> "\begin{pmatrix}a\\ b\\ c\end{pmatrix}"
|
|
681
|
+
case 'vec': {
|
|
682
|
+
const tex_data = node.args!.map(convert_typst_node_to_tex).map((n) => [n]);
|
|
683
|
+
return new TexBeginEnd(new TexToken(TexTokenType.LITERAL, 'pmatrix'), [], tex_data);
|
|
684
|
+
}
|
|
685
|
+
// special hook for op
|
|
686
|
+
case 'op': {
|
|
687
|
+
const arg0 = node.args![0];
|
|
688
|
+
assert(arg0.head.type === TypstTokenType.TEXT);
|
|
689
|
+
return new TexFuncCall(typst_token_to_tex(node.head), [new TexToken(TexTokenType.LITERAL, arg0.head.value).toNode()]);
|
|
690
|
+
}
|
|
691
|
+
// general case
|
|
692
|
+
default: {
|
|
693
|
+
const func_name_tex = typst_token_to_tex(node.head);
|
|
694
|
+
const is_known_func = TEX_UNARY_COMMANDS.includes(func_name_tex.value.substring(1))
|
|
695
|
+
|| TEX_BINARY_COMMANDS.includes(func_name_tex.value.substring(1));
|
|
696
|
+
if (func_name_tex.value.length > 0 && is_known_func) {
|
|
697
|
+
return new TexFuncCall(func_name_tex, node.args!.map(convert_typst_node_to_tex));
|
|
698
|
+
} else {
|
|
699
|
+
return new TexGroup([
|
|
700
|
+
typst_token_to_tex(node.head).toNode(),
|
|
701
|
+
new TexToken(TexTokenType.ELEMENT, '(').toNode(),
|
|
702
|
+
...array_intersperse(node.args!.map(convert_typst_node_to_tex), TEX_NODE_COMMA),
|
|
703
|
+
new TexToken(TexTokenType.ELEMENT, ')').toNode()
|
|
704
|
+
]);
|
|
705
|
+
}
|
|
565
706
|
}
|
|
566
|
-
}
|
|
567
|
-
// special hook for norm
|
|
568
|
-
// `\| a \|` <- `norm(a)`
|
|
569
|
-
// `\left\| a + \frac{1}{3} \right\|` <- `norm(a + 1/3)`
|
|
570
|
-
if (node.content === 'norm') {
|
|
571
|
-
const arg0 = node.args![0];
|
|
572
|
-
const tex_node_type = node.isOverHigh() ? 'leftright' : 'ordgroup';
|
|
573
|
-
return new TexNode(tex_node_type, '', [
|
|
574
|
-
new TexNode('symbol', "\\|"),
|
|
575
|
-
convert_typst_node_to_tex(arg0),
|
|
576
|
-
new TexNode('symbol', "\\|")
|
|
577
|
-
]);
|
|
578
|
-
}
|
|
579
|
-
// special hook for floor, ceil
|
|
580
|
-
// `\lfloor a \rfloor` <- `floor(a)`
|
|
581
|
-
// `\lceil a \rceil` <- `ceil(a)`
|
|
582
|
-
// `\left\lfloor a \right\rfloor` <- `floor(a)`
|
|
583
|
-
// `\left\lceil a \right\rceil` <- `ceil(a)`
|
|
584
|
-
if (node.content === 'floor' || node.content === 'ceil') {
|
|
585
|
-
const left = "\\l" + node.content;
|
|
586
|
-
const right = "\\r" + node.content;
|
|
587
|
-
const arg0 = node.args![0];
|
|
588
|
-
const tex_node_type = node.isOverHigh() ? 'leftright' : 'ordgroup';
|
|
589
|
-
return new TexNode(tex_node_type, '', [
|
|
590
|
-
new TexNode('symbol', left),
|
|
591
|
-
convert_typst_node_to_tex(arg0),
|
|
592
|
-
new TexNode('symbol', right)
|
|
593
|
-
]);
|
|
594
|
-
}
|
|
595
|
-
// special hook for root
|
|
596
|
-
if (node.content === 'root') {
|
|
597
|
-
const [degree, radicand] = node.args!;
|
|
598
|
-
const data: TexSqrtData = convert_typst_node_to_tex(degree);
|
|
599
|
-
return new TexNode('unaryFunc', '\\sqrt', [convert_typst_node_to_tex(radicand)], data);
|
|
600
|
-
}
|
|
601
|
-
// special hook for overbrace and underbrace
|
|
602
|
-
if (node.content === 'overbrace' || node.content === 'underbrace') {
|
|
603
|
-
const [body, label] = node.args!;
|
|
604
|
-
const base = new TexNode('unaryFunc', '\\' + node.content, [convert_typst_node_to_tex(body)]);
|
|
605
|
-
const script = convert_typst_node_to_tex(label);
|
|
606
|
-
const data = node.content === 'overbrace' ? { base, sup: script } : { base, sub: script };
|
|
607
|
-
return new TexNode('supsub', '', [], data);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// special hook for vec
|
|
611
|
-
// "vec(a, b, c)" -> "\begin{pmatrix}a\\ b\\ c\end{pmatrix}"
|
|
612
|
-
if (node.content === 'vec') {
|
|
613
|
-
const tex_data = node.args!.map(convert_typst_node_to_tex).map((n) => [n]) as TexArrayData;
|
|
614
|
-
return new TexNode('beginend', 'pmatrix', [], tex_data);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// general case
|
|
618
|
-
const func_name_tex = typst_token_to_tex(node.content);
|
|
619
|
-
if (func_name_tex.length > 0 && TEX_UNARY_COMMANDS.includes(func_name_tex.substring(1))) {
|
|
620
|
-
return new TexNode('unaryFunc', func_name_tex, node.args!.map(convert_typst_node_to_tex));
|
|
621
|
-
} else if (func_name_tex.length > 0 && TEX_BINARY_COMMANDS.includes(func_name_tex.substring(1))) {
|
|
622
|
-
return new TexNode('binaryFunc', func_name_tex, node.args!.map(convert_typst_node_to_tex));
|
|
623
|
-
} else {
|
|
624
|
-
return new TexNode('ordgroup', '', [
|
|
625
|
-
new TexNode('symbol', typst_token_to_tex(node.content)),
|
|
626
|
-
new TexNode('element', '('),
|
|
627
|
-
...array_intersperse(node.args!.map(convert_typst_node_to_tex), TEX_NODE_COMMA),
|
|
628
|
-
new TexNode('element', ')')
|
|
629
|
-
]);
|
|
630
707
|
}
|
|
631
708
|
}
|
|
632
709
|
case 'supsub': {
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (sup) {
|
|
638
|
-
sup_tex = convert_typst_node_to_tex(sup);
|
|
639
|
-
}
|
|
640
|
-
if (sub) {
|
|
641
|
-
sub_tex = convert_typst_node_to_tex(sub);
|
|
642
|
-
}
|
|
710
|
+
const node = abstractNode as TypstSupsub;
|
|
711
|
+
const { base, sup, sub } = node;
|
|
712
|
+
const sup_tex = sup? convert_typst_node_to_tex(sup) : null;
|
|
713
|
+
const sub_tex = sub? convert_typst_node_to_tex(sub) : null;
|
|
643
714
|
|
|
644
715
|
// special hook for limits
|
|
645
716
|
// `limits(+)^a` -> `\overset{a}{+}`
|
|
646
717
|
// `limits(+)_a` -> `\underset{a}{+}`
|
|
647
718
|
// `limits(+)_a^b` -> `\overset{b}{\underset{a}{+}}`
|
|
648
|
-
if (base.eq(new
|
|
719
|
+
if (base.head.eq(new TypstToken(TypstTokenType.SYMBOL, 'limits'))) {
|
|
649
720
|
const body_in_limits = convert_typst_node_to_tex(base.args![0]);
|
|
650
|
-
if (sup_tex !==
|
|
651
|
-
return new
|
|
652
|
-
} else if (sup_tex ===
|
|
653
|
-
return new
|
|
721
|
+
if (sup_tex !== null && sub_tex === null) {
|
|
722
|
+
return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\overset'), [sup_tex, body_in_limits]);
|
|
723
|
+
} else if (sup_tex === null && sub_tex !== null) {
|
|
724
|
+
return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\underset'), [sub_tex, body_in_limits]);
|
|
654
725
|
} else {
|
|
655
|
-
const underset_call = new
|
|
656
|
-
return new
|
|
726
|
+
const underset_call = new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\underset'), [sub_tex!, body_in_limits]);
|
|
727
|
+
return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\overset'), [sup_tex!, underset_call]);
|
|
657
728
|
}
|
|
658
729
|
}
|
|
659
730
|
|
|
660
731
|
const base_tex = convert_typst_node_to_tex(base);
|
|
661
732
|
|
|
662
|
-
const res = new
|
|
733
|
+
const res = new TexSupSub({
|
|
663
734
|
base: base_tex,
|
|
664
735
|
sup: sup_tex,
|
|
665
736
|
sub: sub_tex
|
|
@@ -667,13 +738,13 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
|
|
|
667
738
|
return res;
|
|
668
739
|
}
|
|
669
740
|
case 'matrix': {
|
|
670
|
-
const
|
|
671
|
-
const tex_data =
|
|
741
|
+
const node = abstractNode as TypstMatrix;
|
|
742
|
+
const tex_data = node.matrix.map(row => row.map(convert_typst_node_to_tex));
|
|
672
743
|
let env_type = 'pmatrix'; // typst mat use delim:"(" by default
|
|
673
744
|
if (node.options) {
|
|
674
745
|
if ('delim' in node.options) {
|
|
675
746
|
const delim = node.options.delim;
|
|
676
|
-
switch (delim.
|
|
747
|
+
switch (delim.head.value) {
|
|
677
748
|
case '#none':
|
|
678
749
|
env_type = 'matrix';
|
|
679
750
|
break;
|
|
@@ -700,35 +771,26 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
|
|
|
700
771
|
env_type = 'Vmatrix';
|
|
701
772
|
break;
|
|
702
773
|
default:
|
|
703
|
-
throw new Error(`Unexpected delimiter ${delim.
|
|
774
|
+
throw new Error(`Unexpected delimiter ${delim.head}`);
|
|
704
775
|
}
|
|
705
776
|
}
|
|
706
777
|
}
|
|
707
|
-
return new
|
|
778
|
+
return new TexBeginEnd(new TexToken(TexTokenType.LITERAL, env_type), [], tex_data);
|
|
708
779
|
}
|
|
709
780
|
case 'cases': {
|
|
710
|
-
const
|
|
711
|
-
const tex_data =
|
|
712
|
-
return new
|
|
713
|
-
}
|
|
714
|
-
case 'control': {
|
|
715
|
-
switch (node.content) {
|
|
716
|
-
case '\\':
|
|
717
|
-
return new TexNode('control', '\\\\');
|
|
718
|
-
case '&':
|
|
719
|
-
return new TexNode('control', '&');
|
|
720
|
-
default:
|
|
721
|
-
throw new Error('[convert_typst_node_to_tex] Unimplemented control: ' + node.content);
|
|
722
|
-
}
|
|
781
|
+
const node = abstractNode as TypstCases;
|
|
782
|
+
const tex_data = node.matrix.map(row => row.map(convert_typst_node_to_tex));
|
|
783
|
+
return new TexBeginEnd(new TexToken(TexTokenType.LITERAL, 'cases'), [], tex_data);
|
|
723
784
|
}
|
|
724
785
|
case 'fraction': {
|
|
786
|
+
const node = abstractNode as TypstFraction;
|
|
725
787
|
const [numerator, denominator] = node.args!;
|
|
726
788
|
const num_tex = convert_typst_node_to_tex(numerator);
|
|
727
789
|
const den_tex = convert_typst_node_to_tex(denominator);
|
|
728
|
-
return new
|
|
790
|
+
return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\frac'), [num_tex, den_tex]);
|
|
729
791
|
}
|
|
730
792
|
default:
|
|
731
|
-
throw new Error('[convert_typst_node_to_tex] Unimplemented type: ' +
|
|
793
|
+
throw new Error('[convert_typst_node_to_tex] Unimplemented type: ' + abstractNode.type);
|
|
732
794
|
}
|
|
733
795
|
}
|
|
734
796
|
|