tex2typst 0.3.22 → 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/src/types.ts DELETED
@@ -1,408 +0,0 @@
1
- import { array_includes } from "./generic";
2
-
3
- export enum TexTokenType {
4
- ELEMENT,
5
- COMMAND,
6
- TEXT,
7
- COMMENT,
8
- SPACE,
9
- NEWLINE,
10
- CONTROL,
11
- UNKNOWN,
12
- }
13
-
14
- export class TexToken {
15
- type: TexTokenType;
16
- value: string;
17
-
18
- constructor(type: TexTokenType, value: string) {
19
- this.type = type;
20
- this.value = value;
21
- }
22
-
23
- public eq(token: TexToken): boolean {
24
- return this.type === token.type && this.value === token.value;
25
- }
26
-
27
- public toString(): string {
28
- switch (this.type) {
29
- case TexTokenType.TEXT:
30
- return `\\text{${this.value}}`;
31
- case TexTokenType.COMMENT:
32
- return `%${this.value}`;
33
- default:
34
- return this.value;
35
- }
36
- }
37
- }
38
-
39
-
40
- export interface TexSupsubData {
41
- base: TexNode;
42
- sup?: TexNode;
43
- sub?: TexNode;
44
- }
45
-
46
- export type TexSqrtData = TexNode;
47
-
48
- export type TexArrayData = TexNode[][];
49
-
50
- /**
51
- * element: 0-9, a-z, A-Z, punctuations such as +-/*,:; etc.
52
- * symbol: LaTeX macro with no parameter. e.g. \sin \cos \int \sum
53
- * unaryFunc: LaTeX macro with 1 parameter. e.g. \sqrt{3} \log{x} \exp{x}
54
- * binaryFunc: LaTeX macro with 2 parameters. e.g. \frac{1}{2}
55
- * text: text enclosed by braces. e.g. \text{hello world}
56
- * empty: special type when something is empty. e.g. the base of _{a} or ^{a}
57
- * whitespace: space, tab, newline
58
- */
59
- type TexNodeType = 'element' | 'text' | 'comment' | 'whitespace' | 'control' | 'ordgroup' | 'supsub'
60
- | 'unaryFunc' | 'binaryFunc' | 'leftright' | 'beginend' | 'symbol' | 'empty' | 'unknownMacro';
61
-
62
-
63
- function apply_escape_if_needed(c: string) {
64
- if (['{', '}', '%'].includes(c)) {
65
- return '\\' + c;
66
- }
67
- return c;
68
- }
69
-
70
- export class TexNode {
71
- type: TexNodeType;
72
- content: string;
73
- args?: TexNode[];
74
- // For type="sqrt", it's additional argument wrapped square bracket. e.g. 3 in \sqrt[3]{x}
75
- // For type="supsub", it's base, sup, and sub.
76
- // For type="beginend", it's the 2-dimensional matrix.
77
- data?: TexSqrtData | TexSupsubData | TexArrayData;
78
-
79
- constructor(type: TexNodeType, content: string, args?: TexNode[],
80
- data?: TexSqrtData | TexSupsubData | TexArrayData) {
81
- this.type = type;
82
- this.content = content;
83
- this.args = args;
84
- this.data = data;
85
- }
86
-
87
- // Note that this is only shallow equality.
88
- public eq(other: TexNode): boolean {
89
- return this.type === other.type && this.content === other.content;
90
- }
91
-
92
- public toString(): string {
93
- switch (this.type) {
94
- case 'text':
95
- return `\\text{${this.content}}`;
96
- default:
97
- throw new Error(`toString() is not implemented for type ${this.type}`);
98
- }
99
- }
100
-
101
-
102
- public serialize(): TexToken[] {
103
- switch (this.type) {
104
- case 'empty':
105
- return [];
106
- case 'element': {
107
- let c = this.content;
108
- c = apply_escape_if_needed(c);
109
- return [new TexToken(TexTokenType.ELEMENT, c)];
110
- }
111
- case 'symbol':
112
- return [new TexToken(TexTokenType.COMMAND, this.content)];
113
- case 'text':
114
- return [new TexToken(TexTokenType.TEXT, this.content)];
115
- case 'comment':
116
- return [new TexToken(TexTokenType.COMMENT, this.content)];
117
- case 'whitespace': {
118
- const tokens: TexToken[] = [];
119
- for (const c of this.content) {
120
- const token_type = c === ' ' ? TexTokenType.SPACE : TexTokenType.NEWLINE;
121
- tokens.push(new TexToken(token_type, c));
122
- }
123
- return tokens;
124
- }
125
- case 'ordgroup': {
126
- return this.args!.map((n) => n.serialize()).flat();
127
- }
128
- case 'leftright': {
129
- let tokens = this.args!.map((n) => n.serialize()).flat();
130
- tokens.splice(0, 0, new TexToken(TexTokenType.COMMAND, '\\left'));
131
- tokens.splice(tokens.length - 1, 0, new TexToken(TexTokenType.COMMAND, '\\right'));
132
-
133
- return tokens;
134
- }
135
- case 'unaryFunc': {
136
- let tokens: TexToken[] = [];
137
- tokens.push(new TexToken(TexTokenType.COMMAND, this.content));
138
-
139
- // special hook for \sqrt
140
- if (this.content === '\\sqrt' && this.data) {
141
- tokens.push(new TexToken(TexTokenType.ELEMENT, '['));
142
- tokens = tokens.concat((this.data! as TexSqrtData).serialize());
143
- tokens.push(new TexToken(TexTokenType.ELEMENT, ']'));
144
- }
145
- // special hook for \operatorname
146
- if (this.content === '\\operatorname' && this.args!.length === 1 && this.args![0].type === 'text') {
147
- const text = this.args![0].content;
148
- tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
149
- // this.serialize(new TexNode('symbol', text));
150
- tokens.push(new TexToken(TexTokenType.COMMAND, text));
151
- tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
152
- return tokens;
153
- }
154
-
155
- tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
156
- tokens = tokens.concat(this.args![0].serialize());
157
- tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
158
-
159
- return tokens;
160
- }
161
- case 'binaryFunc': {
162
- let tokens: TexToken[] = [];
163
- tokens.push(new TexToken(TexTokenType.COMMAND, this.content));
164
- tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
165
- tokens = tokens.concat(this.args![0].serialize());
166
- tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
167
- tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
168
- tokens = tokens.concat(this.args![1].serialize());
169
- tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
170
- return tokens;
171
- }
172
- case 'supsub': {
173
- let tokens: TexToken[] = [];
174
- const { base, sup, sub } = this.data! as TexSupsubData;
175
- tokens = tokens.concat(base.serialize());
176
-
177
- // TODO: should return true for more cases e.g. a_{\theta} instead of a_\theta
178
- function should_wrap_in_braces(node: TexNode): boolean {
179
- if(node.type === 'ordgroup' || node.type === 'supsub' || node.type === 'empty') {
180
- return true;
181
- } else if(node.type === 'element' && /\d+(\.\d+)?/.test(node.content) && node.content.length > 1) {
182
- // a number with more than 1 digit as a subscript/superscript should be wrapped in braces
183
- return true;
184
- } else {
185
- return false;
186
- }
187
- }
188
-
189
- if (sub) {
190
- tokens.push(new TexToken(TexTokenType.CONTROL, '_'));
191
- if (should_wrap_in_braces(sub)) {
192
- tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
193
- tokens = tokens.concat(sub.serialize());
194
- tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
195
- } else {
196
- tokens = tokens.concat(sub.serialize());
197
- }
198
- }
199
- if (sup) {
200
- tokens.push(new TexToken(TexTokenType.CONTROL, '^'));
201
- if (should_wrap_in_braces(sup)) {
202
- tokens.push(new TexToken(TexTokenType.ELEMENT, '{'));
203
- tokens = tokens.concat(sup.serialize());
204
- tokens.push(new TexToken(TexTokenType.ELEMENT, '}'));
205
- } else {
206
- tokens = tokens.concat(sup.serialize());
207
- }
208
- }
209
- return tokens;
210
- }
211
- case 'control': {
212
- return [new TexToken(TexTokenType.CONTROL, this.content)];
213
- }
214
- case 'beginend': {
215
- let tokens: TexToken[] = [];
216
- const matrix = this.data as TexArrayData;
217
- tokens.push(new TexToken(TexTokenType.COMMAND, `\\begin{${this.content}}`));
218
- tokens.push(new TexToken(TexTokenType.NEWLINE, '\n'));
219
- for (let i = 0; i < matrix.length; i++) {
220
- const row = matrix[i];
221
- for (let j = 0; j < row.length; j++) {
222
- const cell = row[j];
223
- tokens = tokens.concat(cell.serialize());
224
- if (j !== row.length - 1) {
225
- tokens.push(new TexToken(TexTokenType.CONTROL, '&'));
226
- }
227
- }
228
- if (i !== matrix.length - 1) {
229
- tokens.push(new TexToken(TexTokenType.CONTROL, '\\\\'));
230
- }
231
- }
232
- tokens.push(new TexToken(TexTokenType.NEWLINE, '\n'));
233
- tokens.push(new TexToken(TexTokenType.COMMAND, `\\end{${this.content}}`));
234
- return tokens;
235
- }
236
- default:
237
- throw new Error('[TexNode.serialize] Unimplemented type: ' + this.type);
238
- }
239
- }
240
- }
241
-
242
- export enum TypstTokenType {
243
- NONE,
244
- SYMBOL,
245
- ELEMENT,
246
- TEXT,
247
- COMMENT,
248
- SPACE,
249
- CONTROL,
250
- NEWLINE,
251
- }
252
-
253
- export class TypstToken {
254
- type: TypstTokenType;
255
- value: string;
256
-
257
- constructor(type: TypstTokenType, content: string) {
258
- this.type = type;
259
- this.value = content;
260
- }
261
-
262
- eq(other: TypstToken): boolean {
263
- return this.type === other.type && this.value === other.value;
264
- }
265
-
266
- isOneOf(tokens: TypstToken[]): boolean {
267
- return array_includes(tokens, this);
268
- }
269
-
270
- public toNode(): TypstNode {
271
- switch(this.type) {
272
- case TypstTokenType.NONE:
273
- return new TypstNode('none', '#none');
274
- case TypstTokenType.TEXT:
275
- return new TypstNode('text', this.value);
276
- case TypstTokenType.COMMENT:
277
- return new TypstNode('comment', this.value);
278
- case TypstTokenType.SPACE:
279
- case TypstTokenType.NEWLINE:
280
- return new TypstNode('whitespace', this.value);
281
- case TypstTokenType.ELEMENT:
282
- return new TypstNode('atom', this.value);
283
- case TypstTokenType.SYMBOL:
284
- return new TypstNode('symbol', this.value);
285
- case TypstTokenType.CONTROL: {
286
- const controlChar = this.value;
287
- switch (controlChar) {
288
- case '':
289
- case '_':
290
- case '^':
291
- throw new Error(`Should not convert ${controlChar} to a node`);
292
- case '&':
293
- return new TypstNode('control', '&');
294
- case '\\':
295
- return new TypstNode('control', '\\');
296
- default:
297
- throw new Error(`Unexpected control character ${controlChar}`);
298
- }
299
- }
300
- default:
301
- throw new Error(`Unexpected token type ${this.type}`);
302
- }
303
- }
304
-
305
- public toString(): string {
306
- switch (this.type) {
307
- case TypstTokenType.TEXT:
308
- return `"${this.value}"`;
309
- case TypstTokenType.COMMENT:
310
- return `//${this.value}`;
311
- default:
312
- return this.value;
313
- }
314
- }
315
- }
316
-
317
- export interface TypstSupsubData {
318
- base: TypstNode;
319
- sup?: TypstNode;
320
- sub?: TypstNode;
321
- }
322
-
323
- export type TypstArrayData = TypstNode[][];
324
- export interface TypstLrData {
325
- leftDelim: string | null;
326
- rightDelim: string | null;
327
- }
328
-
329
- type TypstNodeType = 'atom' | 'symbol' | 'text' | 'control' | 'comment' | 'whitespace'
330
- | 'none' | 'group' | 'supsub' | 'funcCall' | 'fraction' | 'align' | 'matrix' | 'cases' | 'unknown';
331
-
332
- export type TypstPrimitiveValue = string | boolean | TypstNode;
333
- export type TypstNamedParams = { [key: string]: TypstPrimitiveValue };
334
-
335
-
336
- export class TypstNode {
337
- type: TypstNodeType;
338
- content: string;
339
- args?: TypstNode[];
340
- data?: TypstSupsubData | TypstArrayData | TypstLrData;
341
- // Some Typst functions accept additional options. e.g. mat() has option "delim", op() has option "limits"
342
- options?: TypstNamedParams;
343
-
344
- constructor(type: TypstNodeType, content: string, args?: TypstNode[],
345
- data?: TypstSupsubData | TypstArrayData| TypstLrData) {
346
- this.type = type;
347
- this.content = content;
348
- this.args = args;
349
- this.data = data;
350
- }
351
-
352
- public setOptions(options: TypstNamedParams) {
353
- this.options = options;
354
- }
355
-
356
- // Note that this is only shallow equality.
357
- public eq(other: TypstNode): boolean {
358
- return this.type === other.type && this.content === other.content;
359
- }
360
-
361
- // whether the node is over high so that if it's wrapped in braces, \left and \right should be used in its TeX form
362
- // e.g. 1/2 is over high, "2" is not.
363
- public isOverHigh(): boolean {
364
- switch (this.type) {
365
- case 'fraction':
366
- return true;
367
- case 'funcCall': {
368
- if (this.content === 'frac') {
369
- return true;
370
- }
371
- return this.args!.some((n) => n.isOverHigh());
372
- }
373
- case 'group':
374
- return this.args!.some((n) => n.isOverHigh());
375
- case 'supsub':
376
- return (this.data as TypstSupsubData).base.isOverHigh();
377
- case 'align':
378
- case 'cases':
379
- case 'matrix':
380
- return true;
381
- default:
382
- return false;
383
- }
384
- }
385
- }
386
-
387
- // #none
388
- export const TYPST_NONE = new TypstNode('none', '#none');
389
- export const TYPST_TRUE: TypstPrimitiveValue = true;
390
- export const TYPST_FALSE: TypstPrimitiveValue = false;
391
-
392
- /**
393
- * ATTENTION:
394
- * Don't use any options except those explicitly documented in
395
- * https://github.com/qwinsi/tex2typst/blob/main/docs/api-reference.md
396
- * Any undocumented options may be not working at present or break in the future!
397
- */
398
- export interface Tex2TypstOptions {
399
- nonStrict?: boolean; /** default is true */
400
- preferShorthands?: boolean; /** default is true */
401
- keepSpaces?: boolean; /** default is false */
402
- fracToSlash?: boolean; /** default is true */
403
- inftyToOo?: boolean; /** default is false */
404
- optimize?: boolean; /** default is true */
405
- nonAsciiWrapper?: string; /** default is "" */
406
- customTexMacros?: { [key: string]: string };
407
- // TODO: custom typst functions
408
- }