tex2typst 0.5.4 → 0.5.6
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 +114 -79
- package/dist/tex2typst.min.js +9 -9
- package/package.json +1 -1
- package/src/convert.ts +30 -7
- package/src/map.ts +3 -0
- package/src/tex-parser.ts +25 -32
- package/src/tex-tokenizer.ts +2 -0
- package/src/tex-writer.ts +7 -9
- package/src/typst-shorthands.ts +1 -2
- package/src/typst-types.ts +55 -21
- package/src/typst-writer.ts +4 -10
package/src/tex-parser.ts
CHANGED
|
@@ -40,7 +40,7 @@ function eat_whitespaces(tokens: TexToken[], start: number): TexToken[] {
|
|
|
40
40
|
|
|
41
41
|
function eat_parenthesis(tokens: TexToken[], start: number): TexToken | null {
|
|
42
42
|
const firstToken = tokens[start];
|
|
43
|
-
if (firstToken.type === TexTokenType.ELEMENT && ['(', ')', '[', ']', '|', '\\{', '\\}', '.', '\\|'].includes(firstToken.value)) {
|
|
43
|
+
if (firstToken.type === TexTokenType.ELEMENT && ['(', ')', '[', ']', '|', '\\{', '\\}', '.', '\\|', '<', '>'].includes(firstToken.value)) {
|
|
44
44
|
return firstToken;
|
|
45
45
|
} else if (firstToken.type === TexTokenType.COMMAND && ['lfloor', 'rfloor', 'lceil', 'rceil', 'langle', 'rangle', 'lparen', 'rparen', 'lbrace', 'rbrace'].includes(firstToken.value.slice(1))) {
|
|
46
46
|
return firstToken;
|
|
@@ -58,6 +58,29 @@ function eat_primes(tokens: TexToken[], start: number): number {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
function process_styled_parts(nodes: TexNode[]): TexNode[] {
|
|
62
|
+
let style_token: TexToken | null = null;
|
|
63
|
+
let bucket: TexNode[] = [];
|
|
64
|
+
let res: TexNode[] = [];
|
|
65
|
+
let i = 0;
|
|
66
|
+
while(true) {
|
|
67
|
+
if (i === nodes.length || nodes[i].head.eq(TexToken.COMMAND_DISPLAYSTYLE) || nodes[i].head.eq(TexToken.COMMAND_TEXTSTYLE)) {
|
|
68
|
+
if(bucket.length > 0) {
|
|
69
|
+
const g = (bucket.length === 1)? bucket[0]: new TexGroup(bucket);
|
|
70
|
+
res.push(style_token? new TexFuncCall(style_token, [g]): g);
|
|
71
|
+
}
|
|
72
|
+
if (i === nodes.length) {
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
bucket = [];
|
|
76
|
+
style_token = nodes[i].head;
|
|
77
|
+
} else {
|
|
78
|
+
bucket.push(nodes[i]);
|
|
79
|
+
}
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
return res;
|
|
83
|
+
}
|
|
61
84
|
|
|
62
85
|
const LEFT_COMMAND: TexToken = new TexToken(TexTokenType.COMMAND, '\\left');
|
|
63
86
|
const RIGHT_COMMAND: TexToken = new TexToken(TexTokenType.COMMAND, '\\right');
|
|
@@ -137,7 +160,7 @@ export class LatexParser {
|
|
|
137
160
|
return [EMPTY_NODE, -1];
|
|
138
161
|
}
|
|
139
162
|
|
|
140
|
-
const styledResults =
|
|
163
|
+
const styledResults = process_styled_parts(results);
|
|
141
164
|
|
|
142
165
|
let node: TexNode;
|
|
143
166
|
if (styledResults.length === 1) {
|
|
@@ -462,36 +485,6 @@ export class LatexParser {
|
|
|
462
485
|
this.alignmentDepth--;
|
|
463
486
|
return [allRows, pos];
|
|
464
487
|
}
|
|
465
|
-
|
|
466
|
-
private applyStyleCommands(nodes: TexNode[]): TexNode[] {
|
|
467
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
468
|
-
const styleToken = this.getStyleToken(nodes[i]);
|
|
469
|
-
if (styleToken) {
|
|
470
|
-
const before = this.applyStyleCommands(nodes.slice(0, i));
|
|
471
|
-
const after = this.applyStyleCommands(nodes.slice(i + 1));
|
|
472
|
-
let body: TexNode;
|
|
473
|
-
if (after.length === 0) {
|
|
474
|
-
body = EMPTY_NODE;
|
|
475
|
-
} else if (after.length === 1) {
|
|
476
|
-
body = after[0];
|
|
477
|
-
} else {
|
|
478
|
-
body = new TexGroup(after);
|
|
479
|
-
}
|
|
480
|
-
const funcCall = new TexFuncCall(styleToken, [body]);
|
|
481
|
-
return before.concat(funcCall);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
return nodes;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
private getStyleToken(node: TexNode): TexToken | null {
|
|
488
|
-
if (node.type === 'terminal') {
|
|
489
|
-
if (node.head.eq(TexToken.COMMAND_DISPLAYSTYLE) || node.head.eq(TexToken.COMMAND_TEXTSTYLE)) {
|
|
490
|
-
return node.head;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return null;
|
|
494
|
-
}
|
|
495
488
|
}
|
|
496
489
|
|
|
497
490
|
// Remove all whitespace before or after _ or ^
|
package/src/tex-tokenizer.ts
CHANGED
package/src/tex-writer.ts
CHANGED
|
@@ -12,15 +12,6 @@ export class TexWriter {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
protected flushQueue() {
|
|
15
|
-
// remove \textstyle or \displaystyle if it is the end of the math code
|
|
16
|
-
while (this.queue.length > 0) {
|
|
17
|
-
const last_token = this.queue[this.queue.length - 1];
|
|
18
|
-
if (last_token.eq(TexToken.COMMAND_DISPLAYSTYLE) || last_token.eq(TexToken.COMMAND_TEXTSTYLE)) {
|
|
19
|
-
this.queue.pop();
|
|
20
|
-
} else {
|
|
21
|
-
break;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
15
|
for (let i = 0; i < this.queue.length; i++) {
|
|
25
16
|
this.buffer = writeTexTokenBuffer(this.buffer, this.queue[i]);
|
|
26
17
|
}
|
|
@@ -29,6 +20,13 @@ export class TexWriter {
|
|
|
29
20
|
|
|
30
21
|
public finalize(): string {
|
|
31
22
|
this.flushQueue();
|
|
23
|
+
// "\displaystyle \displaystyle" -> "\displaystyle"
|
|
24
|
+
this.buffer = this.buffer.replace(/\\displaystyle \\displaystyle/g, "\\displaystyle");
|
|
25
|
+
// "\textstyle \textstyle" -> "\textstyle"
|
|
26
|
+
this.buffer = this.buffer.replace(/\\textstyle \\textstyle/g, "\\textstyle");
|
|
27
|
+
// remove \textstyle or \displaystyle if it is the end
|
|
28
|
+
this.buffer = this.buffer.replace(/\s?\\textstyle\s?$/, "");
|
|
29
|
+
this.buffer = this.buffer.replace(/\s?\\displaystyle\s?$/, "");
|
|
32
30
|
return this.buffer;
|
|
33
31
|
}
|
|
34
32
|
}
|
package/src/typst-shorthands.ts
CHANGED
|
@@ -19,6 +19,7 @@ const shorthandMap = new Map<string, string>([
|
|
|
19
19
|
['dots.h', '...'],
|
|
20
20
|
['gt.triple', '>>>'],
|
|
21
21
|
['lt.triple', '<<<'],
|
|
22
|
+
['arrow.l.r', '<->'], // Typst's documentation doesn't include this. Wondering why
|
|
22
23
|
['arrow.r', '->'],
|
|
23
24
|
['arrow.r.double', '=>'],
|
|
24
25
|
['arrow.r.squiggly', '~>'],
|
|
@@ -38,8 +39,6 @@ const shorthandMap = new Map<string, string>([
|
|
|
38
39
|
['minus', '-'],
|
|
39
40
|
['tilde.op', '~'],
|
|
40
41
|
|
|
41
|
-
// Typst's documentation doesn't include this. Wondering why
|
|
42
|
-
['arrow.l.r', '<->'],
|
|
43
42
|
]);
|
|
44
43
|
|
|
45
44
|
|
package/src/typst-types.ts
CHANGED
|
@@ -51,6 +51,8 @@ export class TypstToken {
|
|
|
51
51
|
public static readonly EMPTY = new TypstToken(TypstTokenType.ELEMENT, '');
|
|
52
52
|
public static readonly LEFT_BRACE = new TypstToken(TypstTokenType.ELEMENT, '{');
|
|
53
53
|
public static readonly RIGHT_BRACE = new TypstToken(TypstTokenType.ELEMENT, '}');
|
|
54
|
+
public static readonly LEFT_PAREN = new TypstToken(TypstTokenType.ELEMENT, '(');
|
|
55
|
+
public static readonly RIGHT_PAREN = new TypstToken(TypstTokenType.ELEMENT, ')');
|
|
54
56
|
public static readonly LEFT_ANGLE = new TypstToken(TypstTokenType.SYMBOL, 'chevron.l');
|
|
55
57
|
public static readonly RIGHT_ANGLE = new TypstToken(TypstTokenType.SYMBOL, 'chevron.r');
|
|
56
58
|
public static readonly VERTICAL_BAR = new TypstToken(TypstTokenType.ELEMENT, '|');
|
|
@@ -60,7 +62,7 @@ export class TypstToken {
|
|
|
60
62
|
|
|
61
63
|
|
|
62
64
|
public static readonly LEFT_DELIMITERS = [
|
|
63
|
-
|
|
65
|
+
TypstToken.LEFT_PAREN,
|
|
64
66
|
new TypstToken(TypstTokenType.ELEMENT, '['),
|
|
65
67
|
TypstToken.LEFT_BRACE,
|
|
66
68
|
TypstToken.VERTICAL_BAR,
|
|
@@ -70,7 +72,7 @@ export class TypstToken {
|
|
|
70
72
|
];
|
|
71
73
|
|
|
72
74
|
public static readonly RIGHT_DELIMITERS = [
|
|
73
|
-
|
|
75
|
+
TypstToken.RIGHT_PAREN,
|
|
74
76
|
new TypstToken(TypstTokenType.ELEMENT, ']'),
|
|
75
77
|
TypstToken.RIGHT_BRACE,
|
|
76
78
|
TypstToken.VERTICAL_BAR,
|
|
@@ -180,7 +182,7 @@ export class TypstTerminal extends TypstNode {
|
|
|
180
182
|
return true;
|
|
181
183
|
case TypstTokenType.SYMBOL:
|
|
182
184
|
case TypstTokenType.ELEMENT: {
|
|
183
|
-
if(['(', '!', '}', ']'].includes(this.head.value)) {
|
|
185
|
+
if(['(', '!', ',', ')', '}', ']'].includes(this.head.value)) {
|
|
184
186
|
return false;
|
|
185
187
|
}
|
|
186
188
|
return true;
|
|
@@ -214,7 +216,7 @@ export class TypstTerminal extends TypstNode {
|
|
|
214
216
|
public serialize(env: TypstWriterEnvironment, options: TypstWriterOptions): TypstToken[] {
|
|
215
217
|
if (this.head.type === TypstTokenType.ELEMENT) {
|
|
216
218
|
if (this.head.value === ',' && env.insideFunctionDepth > 0) {
|
|
217
|
-
return [new TypstToken(TypstTokenType.SYMBOL, 'comma')];
|
|
219
|
+
return [SOFT_SPACE, new TypstToken(TypstTokenType.SYMBOL, 'comma')];
|
|
218
220
|
}
|
|
219
221
|
} else if (this.head.type === TypstTokenType.SYMBOL) {
|
|
220
222
|
let symbol_name = this.head.value;
|
|
@@ -246,6 +248,43 @@ export class TypstTerminal extends TypstNode {
|
|
|
246
248
|
}
|
|
247
249
|
}
|
|
248
250
|
|
|
251
|
+
class TypstTokenQueue {
|
|
252
|
+
private queue: TypstToken[] = [];
|
|
253
|
+
|
|
254
|
+
public pushSoftSpace() {
|
|
255
|
+
if (this.queue.length === 0) {
|
|
256
|
+
return;
|
|
257
|
+
} else if (this.queue.at(-1)!.eq(SOFT_SPACE)) {
|
|
258
|
+
return;
|
|
259
|
+
} else if (['(', '{', '['].includes(this.queue.at(-1)!.value)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
this.queue.push(SOFT_SPACE);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public pushAll(tokens: TypstToken[]) {
|
|
266
|
+
if (tokens.length == 0) {
|
|
267
|
+
return;
|
|
268
|
+
} else if (tokens[0].eq(SOFT_SPACE) && this.queue.length === 0) {
|
|
269
|
+
this.queue.push(...tokens.slice(1));
|
|
270
|
+
} else {
|
|
271
|
+
if ([')', '}', ']'].includes(tokens[0].value)) {
|
|
272
|
+
while (this.queue.at(-1)?.eq(SOFT_SPACE)) {
|
|
273
|
+
this.queue.pop();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
this.queue.push(...tokens);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
public getQueue(): TypstToken[] {
|
|
281
|
+
const res = Array.from(this.queue);
|
|
282
|
+
while (res.at(-1)?.eq(SOFT_SPACE)) {
|
|
283
|
+
res.pop();
|
|
284
|
+
}
|
|
285
|
+
return res;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
249
288
|
|
|
250
289
|
|
|
251
290
|
export class TypstGroup extends TypstNode {
|
|
@@ -277,28 +316,21 @@ export class TypstGroup extends TypstNode {
|
|
|
277
316
|
if (this.items.length === 0) {
|
|
278
317
|
return [];
|
|
279
318
|
}
|
|
280
|
-
|
|
319
|
+
|
|
320
|
+
const q = new TypstTokenQueue();
|
|
281
321
|
for(let i = 0; i < this.items.length; i++) {
|
|
282
322
|
const n = this.items[i];
|
|
283
323
|
const tokens = n.serialize(env, options);
|
|
284
|
-
if (n.isLeftSpaceful()
|
|
285
|
-
|
|
286
|
-
const top = queue.at(-1)!;
|
|
287
|
-
let no_need_space = false;
|
|
288
|
-
no_need_space ||= top.eq(SOFT_SPACE);
|
|
289
|
-
no_need_space ||= ['{', '['].includes(top.value);
|
|
290
|
-
if (!no_need_space) {
|
|
291
|
-
queue.push(SOFT_SPACE);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
324
|
+
if (n.isLeftSpaceful()) {
|
|
325
|
+
q.pushSoftSpace();
|
|
294
326
|
}
|
|
295
|
-
|
|
296
|
-
if (n.isRightSpaceful()
|
|
297
|
-
|
|
298
|
-
queue.push(SOFT_SPACE);
|
|
299
|
-
}
|
|
327
|
+
q.pushAll(tokens);
|
|
328
|
+
if (n.isRightSpaceful()) {
|
|
329
|
+
q.pushSoftSpace();
|
|
300
330
|
}
|
|
301
331
|
}
|
|
332
|
+
|
|
333
|
+
const queue = q.getQueue();
|
|
302
334
|
// "- a" -> "-a"
|
|
303
335
|
// "+ a" -> "+a"
|
|
304
336
|
if (queue.length > 0 && (queue[0].eq(TypstToken.MINUS) || queue[0].eq(TypstToken.PLUS))) {
|
|
@@ -552,7 +584,9 @@ export class TypstMatrixLike extends TypstNode {
|
|
|
552
584
|
row.forEach((cell, j) => {
|
|
553
585
|
queue.push(...cell.serialize(env, options));
|
|
554
586
|
if (j < row.length - 1) {
|
|
555
|
-
|
|
587
|
+
if (cell_sep.value === '&') {
|
|
588
|
+
queue.push(SOFT_SPACE);
|
|
589
|
+
}
|
|
556
590
|
queue.push(cell_sep);
|
|
557
591
|
queue.push(SOFT_SPACE);
|
|
558
592
|
} else {
|
package/src/typst-writer.ts
CHANGED
|
@@ -4,9 +4,6 @@ import { TypstToken } from "./typst-types";
|
|
|
4
4
|
import { TypstTokenType } from "./typst-types";
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
const TYPST_LEFT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '(');
|
|
8
|
-
const TYPST_RIGHT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ELEMENT, ')');
|
|
9
|
-
const TYPST_COMMA: TypstToken = new TypstToken(TypstTokenType.ELEMENT, ',');
|
|
10
7
|
const TYPST_NEWLINE: TypstToken = new TypstToken(TypstTokenType.SYMBOL, '\n');
|
|
11
8
|
|
|
12
9
|
const SOFT_SPACE = new TypstToken(TypstTokenType.CONTROL, ' ');
|
|
@@ -53,7 +50,7 @@ export class TypstWriter {
|
|
|
53
50
|
qu.push(token);
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
// delete soft spaces
|
|
53
|
+
// delete soft spaces before or after a newline
|
|
57
54
|
const dummy_token = new TypstToken(TypstTokenType.SYMBOL, '');
|
|
58
55
|
for(let i = 0; i < qu.length; i++) {
|
|
59
56
|
let token = qu[i];
|
|
@@ -61,18 +58,15 @@ export class TypstWriter {
|
|
|
61
58
|
const to_delete = (i === 0)
|
|
62
59
|
|| (i === qu.length - 1)
|
|
63
60
|
|| (qu[i - 1].type === TypstTokenType.SPACE)
|
|
64
|
-
|| qu[i - 1].
|
|
65
|
-
|| qu[i + 1].
|
|
61
|
+
|| qu[i - 1].eq(TYPST_NEWLINE)
|
|
62
|
+
|| qu[i + 1].eq(TYPST_NEWLINE);
|
|
66
63
|
if (to_delete) {
|
|
67
64
|
qu[i] = dummy_token;
|
|
68
65
|
}
|
|
69
66
|
}
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
for(let i = 0; i < qu.length; i++) {
|
|
75
|
-
let token = qu[i];
|
|
69
|
+
for(const token of qu) {
|
|
76
70
|
this.buffer += token.toString();
|
|
77
71
|
}
|
|
78
72
|
|