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/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 = this.applyStyleCommands(results);
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 ^
@@ -45,6 +45,8 @@ export const TEX_UNARY_COMMANDS = [
45
45
  // the braket package
46
46
  'bra', 'ket', 'braket', 'set',
47
47
  'Bra', 'Ket', 'Braket', 'Set',
48
+ 'pmod',
49
+ 'cancel',
48
50
  ]
49
51
 
50
52
  export const TEX_BINARY_COMMANDS = [
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
  }
@@ -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
 
@@ -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
- new TypstToken(TypstTokenType.ELEMENT, '('),
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
- new TypstToken(TypstTokenType.ELEMENT, ')'),
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
- const queue: TypstToken[] = [];
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() && i > 0) {
285
- if (queue.length > 0) {
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
- queue.push(...tokens);
296
- if (n.isRightSpaceful() && i < this.items.length - 1) {
297
- if(!(queue.at(-1)?.eq(SOFT_SPACE))) {
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
- queue.push(SOFT_SPACE);
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 {
@@ -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 if they are not needed
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].isOneOf([TYPST_LEFT_PARENTHESIS, TYPST_NEWLINE])
65
- || qu[i + 1].isOneOf([TYPST_RIGHT_PARENTHESIS, TYPST_COMMA, TYPST_NEWLINE]);
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
- qu = qu.filter((token) => !token.eq(dummy_token));
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