tex2typst 0.3.27-beta.1 → 0.3.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +23 -24
- package/dist/index.js +338 -314
- package/dist/parser.js +23 -0
- package/dist/tex2typst.min.js +12 -12
- package/package.json +1 -1
- package/src/convert.ts +53 -2
- package/src/exposed-types.ts +23 -24
- package/src/index.ts +7 -3
- package/src/jslex.ts +1 -1
- package/src/tex-tokenizer.ts +1 -0
- package/src/tex2typst.ts +2 -4
- package/src/typst-parser.ts +9 -10
- package/src/typst-types.ts +484 -230
- package/src/typst-writer.ts +28 -274
- package/tests/cheat-sheet.test.ts +42 -0
- package/tests/cheat-sheet.toml +304 -0
- package/tests/example.ts +15 -0
- package/tests/general-symbols.test.ts +22 -0
- package/tests/general-symbols.toml +755 -0
- package/tests/integration-tex2typst.yaml +89 -0
- package/tests/struct-bidirection.yaml +179 -0
- package/tests/struct-tex2typst.yaml +443 -0
- package/tests/struct-typst2tex.yaml +412 -0
- package/tests/symbol.yml +123 -0
- package/tests/test-common.ts +26 -0
- package/tests/tex-parser.test.ts +57 -0
- package/tests/tex-to-typst.test.ts +143 -0
- package/tests/typst-parser.test.ts +134 -0
- package/tests/typst-to-tex.test.ts +76 -0
- package/tsconfig.json +4 -4
package/src/typst-types.ts
CHANGED
|
@@ -1,230 +1,484 @@
|
|
|
1
|
-
import { array_includes } from "./generic";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
public static readonly
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
public
|
|
143
|
-
return this.
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
public
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
1
|
+
import { array_includes } from "./generic";
|
|
2
|
+
import { shorthandMap } from "./typst-shorthands";
|
|
3
|
+
|
|
4
|
+
export enum TypstTokenType {
|
|
5
|
+
NONE,
|
|
6
|
+
SYMBOL,
|
|
7
|
+
ELEMENT,
|
|
8
|
+
LITERAL,
|
|
9
|
+
TEXT,
|
|
10
|
+
COMMENT,
|
|
11
|
+
SPACE,
|
|
12
|
+
CONTROL,
|
|
13
|
+
NEWLINE
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export class TypstToken {
|
|
18
|
+
readonly type: TypstTokenType;
|
|
19
|
+
value: string;
|
|
20
|
+
|
|
21
|
+
constructor(type: TypstTokenType, content: string) {
|
|
22
|
+
this.type = type;
|
|
23
|
+
this.value = content;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
eq(other: TypstToken): boolean {
|
|
27
|
+
return this.type === other.type && this.value === other.value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
isOneOf(tokens: TypstToken[]): boolean {
|
|
31
|
+
return array_includes(tokens, this);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public toNode(): TypstNode {
|
|
35
|
+
return new TypstTerminal(this);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public toString(): string {
|
|
39
|
+
switch (this.type) {
|
|
40
|
+
case TypstTokenType.TEXT:
|
|
41
|
+
return `"${this.value}"`;
|
|
42
|
+
case TypstTokenType.COMMENT:
|
|
43
|
+
return `//${this.value}`;
|
|
44
|
+
default:
|
|
45
|
+
return this.value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static readonly NONE = new TypstToken(TypstTokenType.NONE, '#none');
|
|
50
|
+
public static readonly EMPTY = new TypstToken(TypstTokenType.ELEMENT, '');
|
|
51
|
+
public static readonly LEFT_BRACE = new TypstToken(TypstTokenType.ELEMENT, '{');
|
|
52
|
+
public static readonly RIGHT_BRACE = new TypstToken(TypstTokenType.ELEMENT, '}');
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
public static readonly LEFT_DELIMITERS = [
|
|
56
|
+
new TypstToken(TypstTokenType.ELEMENT, '('),
|
|
57
|
+
new TypstToken(TypstTokenType.ELEMENT, '['),
|
|
58
|
+
new TypstToken(TypstTokenType.ELEMENT, '{'),
|
|
59
|
+
new TypstToken(TypstTokenType.ELEMENT, '|'),
|
|
60
|
+
new TypstToken(TypstTokenType.SYMBOL, 'angle.l'),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
public static readonly RIGHT_DELIMITERS = [
|
|
64
|
+
new TypstToken(TypstTokenType.ELEMENT, ')'),
|
|
65
|
+
new TypstToken(TypstTokenType.ELEMENT, ']'),
|
|
66
|
+
new TypstToken(TypstTokenType.ELEMENT, '}'),
|
|
67
|
+
new TypstToken(TypstTokenType.ELEMENT, '|'),
|
|
68
|
+
new TypstToken(TypstTokenType.SYMBOL, 'angle.r'),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface TypstSupsubData {
|
|
73
|
+
base: TypstNode;
|
|
74
|
+
sup: TypstNode | null;
|
|
75
|
+
sub: TypstNode | null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
export interface TypstLeftRightData {
|
|
80
|
+
body: TypstNode;
|
|
81
|
+
left: TypstToken | null;
|
|
82
|
+
right: TypstToken | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface TypstWriterEnvironment {
|
|
86
|
+
insideFunctionDepth: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface TypstWriterOptions {
|
|
90
|
+
nonStrict: boolean;
|
|
91
|
+
preferShorthands: boolean;
|
|
92
|
+
keepSpaces: boolean;
|
|
93
|
+
inftyToOo: boolean;
|
|
94
|
+
optimize: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class TypstWriterError extends Error {
|
|
98
|
+
node: TypstNode | TypstToken;
|
|
99
|
+
|
|
100
|
+
constructor(message: string, node: TypstNode | TypstToken) {
|
|
101
|
+
super(message);
|
|
102
|
+
this.name = "TypstWriterError";
|
|
103
|
+
this.node = node;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const SOFT_SPACE = new TypstToken(TypstTokenType.CONTROL, ' ');
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* fraction: `1/2`, `(x + y)/2`, `(1+x)/(1-x)`
|
|
111
|
+
* group: `a + 1/3`
|
|
112
|
+
* leftright: `(a + 1/3)`, `[a + 1/3)`, `lr(]sum_(x=1)^n])`
|
|
113
|
+
* markupFunc: `#heading(level: 2)[something]`, `#text(fill: red)[some text and math $x + y$]`
|
|
114
|
+
*/
|
|
115
|
+
export type TypstNodeType = 'terminal' | 'group' | 'supsub' | 'funcCall' | 'fraction'| 'leftright' | 'matrixLike'| 'markupFunc';
|
|
116
|
+
|
|
117
|
+
export type TypstNamedParams = { [key: string]: TypstNode; };
|
|
118
|
+
|
|
119
|
+
export abstract class TypstNode {
|
|
120
|
+
readonly type: TypstNodeType;
|
|
121
|
+
head: TypstToken;
|
|
122
|
+
// Some Typst functions accept additional options. e.g. mat() has option "delim", op() has option "limits"
|
|
123
|
+
options?: TypstNamedParams;
|
|
124
|
+
|
|
125
|
+
constructor(type: TypstNodeType, head: TypstToken | null) {
|
|
126
|
+
this.type = type;
|
|
127
|
+
this.head = head ? head : TypstToken.NONE;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// whether the node is over high so that if it's wrapped in braces, \left and \right should be used in its TeX form
|
|
131
|
+
// e.g. 1/2 is over high, "2" is not.
|
|
132
|
+
abstract isOverHigh(): boolean;
|
|
133
|
+
|
|
134
|
+
// Serialize a tree of TypstNode into a list of TypstToken
|
|
135
|
+
abstract serialize(env: TypstWriterEnvironment, options: TypstWriterOptions): TypstToken[];
|
|
136
|
+
|
|
137
|
+
public setOptions(options: TypstNamedParams) {
|
|
138
|
+
this.options = options;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Note that this is only shallow equality.
|
|
142
|
+
public eq(other: TypstNode): boolean {
|
|
143
|
+
return this.type === other.type && this.head.eq(other.head);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public toString(): string {
|
|
147
|
+
throw new Error(`Unimplemented toString() in base class TypstNode`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export class TypstTerminal extends TypstNode {
|
|
152
|
+
constructor(head: TypstToken) {
|
|
153
|
+
super('terminal', head);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public isOverHigh(): boolean {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public toString(): string {
|
|
161
|
+
return this.head.toString();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public serialize(env: TypstWriterEnvironment, options: TypstWriterOptions): TypstToken[] {
|
|
165
|
+
if (this.head.type === TypstTokenType.ELEMENT) {
|
|
166
|
+
if (this.head.value === ',' && env.insideFunctionDepth > 0) {
|
|
167
|
+
return [new TypstToken(TypstTokenType.SYMBOL, 'comma')];
|
|
168
|
+
}
|
|
169
|
+
} else if (this.head.type === TypstTokenType.SYMBOL) {
|
|
170
|
+
let symbol_name = this.head.value;
|
|
171
|
+
if (options.preferShorthands) {
|
|
172
|
+
if (shorthandMap.has(symbol_name)) {
|
|
173
|
+
symbol_name = shorthandMap.get(symbol_name)!;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (options.inftyToOo && symbol_name === 'infinity') {
|
|
177
|
+
symbol_name = 'oo';
|
|
178
|
+
}
|
|
179
|
+
return [new TypstToken(TypstTokenType.SYMBOL, symbol_name)];
|
|
180
|
+
} else if (this.head.type === TypstTokenType.SPACE || this.head.type === TypstTokenType.NEWLINE) {
|
|
181
|
+
const queue: TypstToken[] = [];
|
|
182
|
+
for (const c of this.head.value) {
|
|
183
|
+
if (c === ' ') {
|
|
184
|
+
if (options.keepSpaces) {
|
|
185
|
+
queue.push(new TypstToken(TypstTokenType.SPACE, c));
|
|
186
|
+
}
|
|
187
|
+
} else if (c === '\n') {
|
|
188
|
+
queue.push(new TypstToken(TypstTokenType.SYMBOL, c));
|
|
189
|
+
} else {
|
|
190
|
+
throw new TypstWriterError(`Unexpected whitespace character: ${c}`, this);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return queue;
|
|
194
|
+
}
|
|
195
|
+
return [this.head];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export class TypstGroup extends TypstNode {
|
|
200
|
+
public items: TypstNode[];
|
|
201
|
+
constructor(items: TypstNode[]) {
|
|
202
|
+
super('group', TypstToken.NONE);
|
|
203
|
+
this.items = items;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public isOverHigh(): boolean {
|
|
207
|
+
return this.items.some((n) => n.isOverHigh());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public serialize(env: TypstWriterEnvironment, options: TypstWriterOptions): TypstToken[] {
|
|
211
|
+
const queue = this.items.flatMap((n) => n.serialize(env, options));
|
|
212
|
+
// remove soft space at the start and end
|
|
213
|
+
if (queue.length > 0 && queue[0].eq(SOFT_SPACE)) {
|
|
214
|
+
queue.shift();
|
|
215
|
+
}
|
|
216
|
+
if (queue.length > 0 && queue[queue.length - 1].eq(SOFT_SPACE)) {
|
|
217
|
+
queue.pop();
|
|
218
|
+
}
|
|
219
|
+
return queue;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
export class TypstSupsub extends TypstNode {
|
|
225
|
+
public base: TypstNode;
|
|
226
|
+
public sup: TypstNode | null;
|
|
227
|
+
public sub: TypstNode | null;
|
|
228
|
+
|
|
229
|
+
constructor(data: TypstSupsubData) {
|
|
230
|
+
super('supsub', TypstToken.NONE);
|
|
231
|
+
this.base = data.base;
|
|
232
|
+
this.sup = data.sup;
|
|
233
|
+
this.sub = data.sub;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public isOverHigh(): boolean {
|
|
237
|
+
return this.base.isOverHigh();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public serialize(env: TypstWriterEnvironment, options: TypstWriterOptions): TypstToken[] {
|
|
241
|
+
const queue: TypstToken[] = [];
|
|
242
|
+
let { base, sup, sub } = this;
|
|
243
|
+
|
|
244
|
+
queue.push(...base.serialize(env, options));
|
|
245
|
+
|
|
246
|
+
const has_prime = (sup && sup.head.eq(new TypstToken(TypstTokenType.ELEMENT, "'")));
|
|
247
|
+
if (has_prime) {
|
|
248
|
+
// Put prime symbol before '_'. Because $y_1'$ is not displayed properly in Typst (so far)
|
|
249
|
+
// e.g.
|
|
250
|
+
// y_1' -> y'_1
|
|
251
|
+
// y_{a_1}' -> y'_(a_1)
|
|
252
|
+
queue.push(new TypstToken(TypstTokenType.ELEMENT, '\''));
|
|
253
|
+
}
|
|
254
|
+
if (sub) {
|
|
255
|
+
queue.push(new TypstToken(TypstTokenType.ELEMENT, '_'));
|
|
256
|
+
queue.push(...sub.serialize(env, options));
|
|
257
|
+
}
|
|
258
|
+
if (sup && !has_prime) {
|
|
259
|
+
queue.push(new TypstToken(TypstTokenType.ELEMENT, '^'));
|
|
260
|
+
queue.push(...sup.serialize(env, options));
|
|
261
|
+
}
|
|
262
|
+
queue.push(SOFT_SPACE);
|
|
263
|
+
return queue;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export class TypstFuncCall extends TypstNode {
|
|
268
|
+
public args: TypstNode[];
|
|
269
|
+
constructor(head: TypstToken, args: TypstNode[]) {
|
|
270
|
+
super('funcCall', head);
|
|
271
|
+
this.args = args;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
public isOverHigh(): boolean {
|
|
275
|
+
if (this.head.value === 'frac') {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
return this.args.some((n) => n.isOverHigh());
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
public serialize(env: TypstWriterEnvironment, options: TypstWriterOptions): TypstToken[] {
|
|
282
|
+
const queue: TypstToken[] = [];
|
|
283
|
+
|
|
284
|
+
const func_symbol: TypstToken = this.head;
|
|
285
|
+
queue.push(func_symbol);
|
|
286
|
+
env.insideFunctionDepth++;
|
|
287
|
+
queue.push(TYPST_LEFT_PARENTHESIS);
|
|
288
|
+
for (let i = 0; i < this.args.length; i++) {
|
|
289
|
+
queue.push(...this.args[i].serialize(env, options));
|
|
290
|
+
if (i < this.args.length - 1) {
|
|
291
|
+
queue.push(new TypstToken(TypstTokenType.ELEMENT, ','));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (this.options) {
|
|
295
|
+
for (const [key, value] of Object.entries(this.options)) {
|
|
296
|
+
queue.push(new TypstToken(TypstTokenType.LITERAL, `, ${key}: ${value.toString()}`));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
300
|
+
env.insideFunctionDepth--;
|
|
301
|
+
return queue;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export class TypstFraction extends TypstNode {
|
|
306
|
+
public args: TypstNode[];
|
|
307
|
+
|
|
308
|
+
constructor(args: TypstNode[]) {
|
|
309
|
+
super('fraction', TypstToken.NONE);
|
|
310
|
+
this.args = args;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
public isOverHigh(): boolean {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public serialize(env: TypstWriterEnvironment, options: TypstWriterOptions): TypstToken[] {
|
|
318
|
+
const queue: TypstToken[] = [];
|
|
319
|
+
|
|
320
|
+
const [numerator, denominator] = this.args;
|
|
321
|
+
queue.push(SOFT_SPACE);
|
|
322
|
+
queue.push(...numerator.serialize(env, options));
|
|
323
|
+
queue.push(new TypstToken(TypstTokenType.ELEMENT, '/'));
|
|
324
|
+
queue.push(...denominator.serialize(env, options));
|
|
325
|
+
queue.push(SOFT_SPACE);
|
|
326
|
+
return queue;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const TYPST_LEFT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '(');
|
|
331
|
+
const TYPST_RIGHT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ELEMENT, ')');
|
|
332
|
+
|
|
333
|
+
export class TypstLeftright extends TypstNode {
|
|
334
|
+
public body: TypstNode;
|
|
335
|
+
public left: TypstToken | null;
|
|
336
|
+
public right: TypstToken | null;
|
|
337
|
+
|
|
338
|
+
// head is either null or 'lr'
|
|
339
|
+
constructor(head: TypstToken | null, data: TypstLeftRightData) {
|
|
340
|
+
super('leftright', head);
|
|
341
|
+
this.body = data.body;
|
|
342
|
+
this.left = data.left;
|
|
343
|
+
this.right = data.right;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
public isOverHigh(): boolean {
|
|
347
|
+
return this.body.isOverHigh();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
public serialize(env: TypstWriterEnvironment, options: TypstWriterOptions): TypstToken[] {
|
|
351
|
+
const queue: TypstToken[] = [];
|
|
352
|
+
const LR = new TypstToken(TypstTokenType.SYMBOL, 'lr');
|
|
353
|
+
const {left, right} = this;
|
|
354
|
+
if (this.head.eq(LR)) {
|
|
355
|
+
queue.push(LR);
|
|
356
|
+
queue.push(TYPST_LEFT_PARENTHESIS);
|
|
357
|
+
}
|
|
358
|
+
if (left) {
|
|
359
|
+
queue.push(left);
|
|
360
|
+
}
|
|
361
|
+
queue.push(...this.body.serialize(env, options));
|
|
362
|
+
if (right) {
|
|
363
|
+
queue.push(right);
|
|
364
|
+
}
|
|
365
|
+
if (this.head.eq(LR)) {
|
|
366
|
+
queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
367
|
+
}
|
|
368
|
+
return queue;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
export class TypstMatrixLike extends TypstNode {
|
|
374
|
+
public matrix: TypstNode[][];
|
|
375
|
+
|
|
376
|
+
// head is 'mat', 'cases' or null
|
|
377
|
+
constructor(head: TypstToken | null, data: TypstNode[][]) {
|
|
378
|
+
super('matrixLike', head);
|
|
379
|
+
this.matrix = data;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
public isOverHigh(): boolean {
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
public serialize(env: TypstWriterEnvironment, options: TypstWriterOptions): TypstToken[] {
|
|
387
|
+
const queue: TypstToken[] = [];
|
|
388
|
+
|
|
389
|
+
let cell_sep: TypstToken;
|
|
390
|
+
let row_sep: TypstToken;
|
|
391
|
+
if (this.head.eq(TypstMatrixLike.MAT)) {
|
|
392
|
+
cell_sep = new TypstToken(TypstTokenType.ELEMENT, ',');
|
|
393
|
+
row_sep = new TypstToken(TypstTokenType.ELEMENT, ';');
|
|
394
|
+
} else if (this.head.eq(TypstMatrixLike.CASES)) {
|
|
395
|
+
cell_sep = new TypstToken(TypstTokenType.ELEMENT, '&');
|
|
396
|
+
row_sep = new TypstToken(TypstTokenType.ELEMENT, ',');
|
|
397
|
+
} else if (this.head.eq(TypstToken.NONE)){ // head is null
|
|
398
|
+
cell_sep = new TypstToken(TypstTokenType.ELEMENT, '&');
|
|
399
|
+
row_sep = new TypstToken(TypstTokenType.SYMBOL, '\\');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (!this.head.eq(TypstToken.NONE)) {
|
|
403
|
+
queue.push(this.head);
|
|
404
|
+
env.insideFunctionDepth++;
|
|
405
|
+
queue.push(TYPST_LEFT_PARENTHESIS);
|
|
406
|
+
if (this.options) {
|
|
407
|
+
for (const [key, value] of Object.entries(this.options)) {
|
|
408
|
+
queue.push(new TypstToken(TypstTokenType.LITERAL, `${key}: ${value.toString()}, `));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
this.matrix.forEach((row, i) => {
|
|
414
|
+
row.forEach((cell, j) => {
|
|
415
|
+
queue.push(...cell.serialize(env, options));
|
|
416
|
+
if (j < row.length - 1) {
|
|
417
|
+
queue.push(cell_sep);
|
|
418
|
+
} else {
|
|
419
|
+
if (i < this.matrix.length - 1) {
|
|
420
|
+
queue.push(row_sep);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
if (!this.head.eq(TypstToken.NONE)) {
|
|
427
|
+
queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
428
|
+
env.insideFunctionDepth--;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return queue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
static readonly MAT = new TypstToken(TypstTokenType.SYMBOL, 'mat');
|
|
435
|
+
static readonly CASES = new TypstToken(TypstTokenType.SYMBOL, 'cases');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export class TypstMarkupFunc extends TypstNode {
|
|
439
|
+
/*
|
|
440
|
+
In idealized situations, for `#heading([some text and math $x + y$ example])`,
|
|
441
|
+
fragments would be [TypstMarkup{"some text and math "}, TypstNode{"x + y"}, TypstMarkup{" example"}]
|
|
442
|
+
At present, we haven't implemented anything about TypstMarkup.
|
|
443
|
+
So only pattens like `#heading(level: 2)[$x+y$]`, `#text(fill: red)[$x + y$]` are supported.
|
|
444
|
+
Therefore, fragments is always a list containing exactly 1 TypstNode in well-working situations.
|
|
445
|
+
*/
|
|
446
|
+
public fragments: TypstNode[];
|
|
447
|
+
|
|
448
|
+
constructor(head: TypstToken, fragments: TypstNode[]) {
|
|
449
|
+
super('markupFunc', head);
|
|
450
|
+
this.fragments = fragments;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
public isOverHigh(): boolean {
|
|
454
|
+
return this.fragments.some((n) => n.isOverHigh());
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
public serialize(env: TypstWriterEnvironment, options: TypstWriterOptions): TypstToken[] {
|
|
458
|
+
const queue: TypstToken[] = [];
|
|
459
|
+
|
|
460
|
+
queue.push(this.head);
|
|
461
|
+
env.insideFunctionDepth++;
|
|
462
|
+
queue.push(TYPST_LEFT_PARENTHESIS);
|
|
463
|
+
if (this.options) {
|
|
464
|
+
const entries = Object.entries(this.options);
|
|
465
|
+
for (let i = 0; i < entries.length; i++) {
|
|
466
|
+
const [key, value] = entries[i];
|
|
467
|
+
queue.push(new TypstToken(TypstTokenType.LITERAL, `${key}: ${value.toString()}`));
|
|
468
|
+
if (i < entries.length - 1) {
|
|
469
|
+
queue.push(new TypstToken(TypstTokenType.ELEMENT, ','));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
queue.push(TYPST_RIGHT_PARENTHESIS);
|
|
474
|
+
|
|
475
|
+
queue.push(new TypstToken(TypstTokenType.LITERAL, '['));
|
|
476
|
+
for (const frag of this.fragments) {
|
|
477
|
+
queue.push(new TypstToken(TypstTokenType.LITERAL, '$'));
|
|
478
|
+
queue.push(...frag.serialize(env, options));
|
|
479
|
+
queue.push(new TypstToken(TypstTokenType.LITERAL, '$'));
|
|
480
|
+
}
|
|
481
|
+
queue.push(new TypstToken(TypstTokenType.LITERAL, ']'));
|
|
482
|
+
return queue;
|
|
483
|
+
}
|
|
484
|
+
}
|