tex2typst 0.3.22 → 0.3.23
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/generic.d.ts +1 -1
- package/dist/index.js +260 -305
- package/dist/jslex.d.ts +2 -0
- package/dist/tex2typst.min.js +12 -12
- package/dist/types.d.ts +11 -13
- package/package.json +1 -1
- package/src/convert.ts +196 -200
- package/src/generic.ts +1 -1
- package/src/jslex.ts +19 -12
- package/src/map.ts +1 -4
- package/src/tex-parser.ts +11 -10
- package/src/tex-tokenizer.ts +26 -23
- package/src/tex-writer.ts +1 -1
- package/src/types.ts +27 -21
- package/src/typst-parser.ts +1 -16
- package/src/typst-tokenizer.ts +9 -0
- package/src/typst-writer.ts +8 -23
package/src/convert.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { TexNode, TypstNode, TexSupsubData, TypstSupsubData, TexSqrtData, Tex2TypstOptions, TYPST_NONE,
|
|
1
|
+
import { TexNode, TypstNode, TexSupsubData, TypstSupsubData, TexSqrtData, Tex2TypstOptions, TYPST_NONE, TypstLrData, TexArrayData, TypstNamedParams } from "./types";
|
|
2
2
|
import { TypstWriterError } from "./typst-writer";
|
|
3
3
|
import { symbolMap, reverseSymbolMap } from "./map";
|
|
4
|
-
import {
|
|
4
|
+
import { array_intersperse } from "./generic";
|
|
5
5
|
import { assert } from "./util";
|
|
6
|
+
import { TEX_BINARY_COMMANDS, TEX_UNARY_COMMANDS } from "./tex-tokenizer";
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
// native textual operators in Typst
|
|
@@ -94,6 +95,44 @@ function convert_underset(node: TexNode, options: Tex2TypstOptions): TypstNode {
|
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
function convert_tex_array_align_literal(alignLiteral: string): TypstNamedParams {
|
|
99
|
+
const np: TypstNamedParams = {};
|
|
100
|
+
const alignMap: Record<string, string> = { l: '#left', c: '#center', r: '#right' };
|
|
101
|
+
const chars = Array.from(alignLiteral);
|
|
102
|
+
|
|
103
|
+
const vlinePositions: number[] = [];
|
|
104
|
+
let columnIndex = 0;
|
|
105
|
+
for (const c of chars) {
|
|
106
|
+
if (c === '|') {
|
|
107
|
+
vlinePositions.push(columnIndex);
|
|
108
|
+
} else if (c === 'l' || c === 'c' || c === 'r') {
|
|
109
|
+
columnIndex++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (vlinePositions.length > 0) {
|
|
114
|
+
let augment_str: string;
|
|
115
|
+
if (vlinePositions.length === 1) {
|
|
116
|
+
augment_str = `#${vlinePositions[0]}`;
|
|
117
|
+
} else {
|
|
118
|
+
augment_str = `#(vline: (${vlinePositions.join(', ')}))`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
np['augment'] = new TypstNode('literal', augment_str);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const alignments = chars
|
|
125
|
+
.map(c => alignMap[c])
|
|
126
|
+
.filter((x) => x !== undefined)
|
|
127
|
+
.map(s => new TypstNode('literal', s!));
|
|
128
|
+
|
|
129
|
+
if (alignments.length > 0) {
|
|
130
|
+
const all_same = alignments.every(item => item.eq(alignments[0]));
|
|
131
|
+
np['align'] = all_same ? alignments[0] : new TypstNode('literal', '#center');
|
|
132
|
+
}
|
|
133
|
+
return np;
|
|
134
|
+
}
|
|
135
|
+
|
|
97
136
|
|
|
98
137
|
export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptions = {}): TypstNode {
|
|
99
138
|
switch (node.type) {
|
|
@@ -121,6 +160,9 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
|
|
|
121
160
|
}
|
|
122
161
|
return new TypstNode('text', node.content);
|
|
123
162
|
}
|
|
163
|
+
case 'literal':
|
|
164
|
+
// This happens, for example, node={type: 'literal', content: 'myop'} as in `\operatorname{myop}`
|
|
165
|
+
return new TypstNode('literal', node.content);
|
|
124
166
|
case 'comment':
|
|
125
167
|
return new TypstNode('comment', node.content);
|
|
126
168
|
case 'supsub': {
|
|
@@ -272,31 +314,19 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
|
|
|
272
314
|
}
|
|
273
315
|
// \operatorname{opname} -> op("opname")
|
|
274
316
|
if (node.content === '\\operatorname') {
|
|
317
|
+
// arg0 must be of type 'literal' in this situation
|
|
275
318
|
if (options.optimize) {
|
|
276
319
|
if (TYPST_INTRINSIC_OP.includes(arg0.content)) {
|
|
277
320
|
return new TypstNode('symbol', arg0.content);
|
|
278
321
|
}
|
|
279
322
|
}
|
|
280
|
-
return new TypstNode('funcCall', 'op', [arg0]);
|
|
281
|
-
}
|
|
282
|
-
// \hspace{1cm} -> #h(1cm)
|
|
283
|
-
// TODO: reverse conversion support for this
|
|
284
|
-
if (node.content === '\\hspace') {
|
|
285
|
-
const text = arg0.content;
|
|
286
|
-
return new TypstNode(
|
|
287
|
-
'funcCall',
|
|
288
|
-
'#h',
|
|
289
|
-
[new TypstNode('symbol', text)]
|
|
290
|
-
);
|
|
323
|
+
return new TypstNode('funcCall', 'op', [new TypstNode('text', arg0.content)]);
|
|
291
324
|
}
|
|
292
325
|
|
|
326
|
+
// \substack{a \\ b} -> `a \ b`
|
|
327
|
+
// as in translation from \sum_{\substack{a \\ b}} to sum_(a \ b)
|
|
293
328
|
if (node.content === '\\substack') {
|
|
294
|
-
|
|
295
|
-
return new TypstNode(
|
|
296
|
-
'group',
|
|
297
|
-
'',
|
|
298
|
-
[arg0]
|
|
299
|
-
);
|
|
329
|
+
return arg0;
|
|
300
330
|
}
|
|
301
331
|
|
|
302
332
|
if(options.optimize) {
|
|
@@ -321,97 +351,57 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
|
|
|
321
351
|
const matrix = node.data as TexNode[][];
|
|
322
352
|
const data = matrix.map((row) => row.map((n) => convert_tex_node_to_typst(n, options)));
|
|
323
353
|
|
|
324
|
-
if (node.content
|
|
354
|
+
if (node.content.startsWith('align')) {
|
|
325
355
|
// align, align*, alignat, alignat*, aligned, etc.
|
|
326
356
|
return new TypstNode('align', '', [], data);
|
|
327
357
|
}
|
|
328
|
-
if (node.content
|
|
358
|
+
if (node.content === 'cases') {
|
|
329
359
|
return new TypstNode('cases', '', [], data);
|
|
330
360
|
}
|
|
331
|
-
if (node.content
|
|
361
|
+
if (node.content === 'subarray') {
|
|
332
362
|
const align_node = node.args![0];
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
363
|
+
switch (align_node.content) {
|
|
364
|
+
case 'r':
|
|
365
|
+
data.forEach(row => row[0].args!.push(new TypstNode('symbol', '&')));
|
|
366
|
+
break;
|
|
367
|
+
case 'l':
|
|
368
|
+
data.forEach(row => row[0].args!.unshift(new TypstNode('symbol', '&')));
|
|
369
|
+
break;
|
|
370
|
+
default:
|
|
371
|
+
break;
|
|
338
372
|
}
|
|
339
|
-
return new TypstNode(
|
|
340
|
-
'group',
|
|
341
|
-
'',
|
|
342
|
-
[new TypstNode('align', '', [], data)]
|
|
343
|
-
);
|
|
373
|
+
return new TypstNode('align', '', [], data);
|
|
344
374
|
}
|
|
345
|
-
if (node.content
|
|
346
|
-
const
|
|
347
|
-
const options: TypstNamedParams = { 'delim': TYPST_NONE };
|
|
348
|
-
|
|
349
|
-
const align_args = node.args!;
|
|
350
|
-
if (align_args.length > 0) {
|
|
351
|
-
const align_node = align_args[0];
|
|
352
|
-
const align_str = (() => {
|
|
353
|
-
if (align_node.type === 'element') return align_node.content;
|
|
354
|
-
if (align_node.type === 'ordgroup') {
|
|
355
|
-
return align_node.args!.map(n => n.type === 'element' ? n.content : '').join('');
|
|
356
|
-
}
|
|
357
|
-
return '';
|
|
358
|
-
})();
|
|
359
|
-
|
|
360
|
-
if (align_str) {
|
|
361
|
-
const alignMap: Record<string, string> = { l: '#left', c: '#center', r: '#right' };
|
|
362
|
-
const chars = Array.from(align_str);
|
|
363
|
-
|
|
364
|
-
const alignments = chars
|
|
365
|
-
.map(c => alignMap[c])
|
|
366
|
-
.filter(Boolean)
|
|
367
|
-
.map(s => new TypstNode('symbol', s!));
|
|
368
|
-
|
|
369
|
-
const vlinePositions: number[] = [];
|
|
370
|
-
let columnIndex = 0;
|
|
371
|
-
for (const c of chars) {
|
|
372
|
-
if (c === '|') {
|
|
373
|
-
vlinePositions.push(columnIndex);
|
|
374
|
-
} else if (c === 'l' || c === 'c' || c === 'r') {
|
|
375
|
-
columnIndex++;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if (vlinePositions.length > 0) {
|
|
380
|
-
if (vlinePositions.length === 1) {
|
|
381
|
-
options['augment'] = new TypstNode('symbol', `#${vlinePositions[0]}`);
|
|
382
|
-
} else {
|
|
383
|
-
options['augment'] = new TypstNode('symbol', `#(vline: (${vlinePositions.join(', ')}))`);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (alignments.length > 0) {
|
|
388
|
-
const first_align = alignments[0].content;
|
|
389
|
-
const all_same = alignments.every(item => item.content === first_align);
|
|
390
|
-
options['align'] = all_same ? alignments[0] : new TypstNode('symbol', '#center');
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
375
|
+
if (node.content === 'array') {
|
|
376
|
+
const np: TypstNamedParams = { 'delim': TYPST_NONE };
|
|
394
377
|
|
|
395
|
-
|
|
378
|
+
assert(node.args!.length > 0 && node.args![0].type === 'literal');
|
|
379
|
+
const np_new = convert_tex_array_align_literal(node.args![0].content);
|
|
380
|
+
Object.assign(np, np_new);
|
|
381
|
+
|
|
382
|
+
const res = new TypstNode('matrix', '', [], data);
|
|
383
|
+
res.setOptions(np);
|
|
396
384
|
return res;
|
|
397
385
|
}
|
|
398
|
-
if (node.content
|
|
399
|
-
|
|
386
|
+
if (node.content.endsWith('matrix')) {
|
|
387
|
+
const res = new TypstNode('matrix', '', [], data);
|
|
388
|
+
let delim: TypstNode;
|
|
400
389
|
switch (node.content) {
|
|
401
390
|
case 'matrix':
|
|
402
391
|
delim = TYPST_NONE;
|
|
403
392
|
break;
|
|
404
393
|
case 'pmatrix':
|
|
405
|
-
delim = '(';
|
|
406
|
-
break;
|
|
394
|
+
// delim = new TypstNode('text', '(');
|
|
395
|
+
// break;
|
|
396
|
+
return res; // typst mat use delim:"(" by default
|
|
407
397
|
case 'bmatrix':
|
|
408
|
-
delim = '[';
|
|
398
|
+
delim = new TypstNode('text', '[');
|
|
409
399
|
break;
|
|
410
400
|
case 'Bmatrix':
|
|
411
|
-
delim = '{';
|
|
401
|
+
delim = new TypstNode('text', '{');
|
|
412
402
|
break;
|
|
413
403
|
case 'vmatrix':
|
|
414
|
-
delim = '|';
|
|
404
|
+
delim = new TypstNode('text', '|');
|
|
415
405
|
break;
|
|
416
406
|
case 'Vmatrix': {
|
|
417
407
|
delim = new TypstNode('symbol', 'bar.v.double');
|
|
@@ -420,7 +410,6 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
|
|
|
420
410
|
default:
|
|
421
411
|
throw new TypstWriterError(`Unimplemented beginend: ${node.content}`, node);
|
|
422
412
|
}
|
|
423
|
-
const res = new TypstNode('matrix', '', [], data);
|
|
424
413
|
res.setOptions({ 'delim': delim });
|
|
425
414
|
return res;
|
|
426
415
|
}
|
|
@@ -430,7 +419,13 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
|
|
|
430
419
|
return new TypstNode('unknown', tex_token_to_typst(node.content));
|
|
431
420
|
case 'control':
|
|
432
421
|
if (node.content === '\\\\') {
|
|
422
|
+
// \\ -> \
|
|
433
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
|
+
]);
|
|
434
429
|
} else if (symbolMap.has(node.content.substring(1))) {
|
|
435
430
|
// node.content is one of \, \: \;
|
|
436
431
|
const typst_symbol = symbolMap.get(node.content.substring(1))!;
|
|
@@ -444,7 +439,7 @@ export function convert_tex_node_to_typst(node: TexNode, options: Tex2TypstOptio
|
|
|
444
439
|
}
|
|
445
440
|
|
|
446
441
|
|
|
447
|
-
|
|
442
|
+
/*
|
|
448
443
|
const TYPST_UNARY_FUNCTIONS: string[] = [
|
|
449
444
|
'sqrt',
|
|
450
445
|
'bold',
|
|
@@ -466,6 +461,7 @@ const TYPST_UNARY_FUNCTIONS: string[] = [
|
|
|
466
461
|
'ceil',
|
|
467
462
|
'norm',
|
|
468
463
|
'limits',
|
|
464
|
+
'#h',
|
|
469
465
|
];
|
|
470
466
|
|
|
471
467
|
const TYPST_BINARY_FUNCTIONS: string[] = [
|
|
@@ -474,6 +470,7 @@ const TYPST_BINARY_FUNCTIONS: string[] = [
|
|
|
474
470
|
'overbrace',
|
|
475
471
|
'underbrace',
|
|
476
472
|
];
|
|
473
|
+
*/
|
|
477
474
|
|
|
478
475
|
function apply_escape_if_needed(c: string) {
|
|
479
476
|
if (['{', '}', '%'].includes(c)) {
|
|
@@ -515,6 +512,10 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
|
|
|
515
512
|
if(node.content === 'comma') {
|
|
516
513
|
return new TexNode('element', ',');
|
|
517
514
|
}
|
|
515
|
+
// special hook for dif
|
|
516
|
+
if(node.content === 'dif') {
|
|
517
|
+
return new TexNode('unaryFunc', '\\mathrm', [new TexNode('element', 'd')]);
|
|
518
|
+
}
|
|
518
519
|
// special hook for hyph and hyph.minus
|
|
519
520
|
if(node.content === 'hyph' || node.content === 'hyph.minus') {
|
|
520
521
|
return new TexNode('text', '-');
|
|
@@ -527,6 +528,8 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
|
|
|
527
528
|
}
|
|
528
529
|
return new TexNode('symbol', typst_token_to_tex(node.content));
|
|
529
530
|
}
|
|
531
|
+
case 'literal':
|
|
532
|
+
return new TexNode('literal', node.content);
|
|
530
533
|
case 'text':
|
|
531
534
|
return new TexNode('text', node.content);
|
|
532
535
|
case 'comment':
|
|
@@ -543,83 +546,85 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
|
|
|
543
546
|
return new TexNode('ordgroup', node.content, args);
|
|
544
547
|
}
|
|
545
548
|
case 'funcCall': {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
new TexNode('element', '\\right' + right_delim)
|
|
560
|
-
]);
|
|
561
|
-
} else {
|
|
562
|
-
return new TexNode('ordgroup', '', node.args!.map(convert_typst_node_to_tex));
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
// special hook for norm
|
|
566
|
-
// `\| a \|` <- `norm(a)`
|
|
567
|
-
// `\left\| a + \frac{1}{3} \right\|` <- `norm(a + 1/3)`
|
|
568
|
-
if (node.content === 'norm') {
|
|
569
|
-
const arg0 = node.args![0];
|
|
570
|
-
const tex_node_type = node.isOverHigh() ? 'leftright' : 'ordgroup';
|
|
571
|
-
return new TexNode(tex_node_type, '', [
|
|
572
|
-
new TexNode('symbol', "\\|"),
|
|
573
|
-
convert_typst_node_to_tex(arg0),
|
|
574
|
-
new TexNode('symbol', "\\|")
|
|
575
|
-
]);
|
|
576
|
-
}
|
|
577
|
-
// special hook for floor, ceil
|
|
578
|
-
// `\lfloor a \rfloor` <- `floor(a)`
|
|
579
|
-
// `\lceil a \rceil` <- `ceil(a)`
|
|
580
|
-
// `\left\lfloor a \right\rfloor` <- `floor(a)`
|
|
581
|
-
// `\left\lceil a \right\rceil` <- `ceil(a)`
|
|
582
|
-
if (node.content === 'floor' || node.content === 'ceil') {
|
|
583
|
-
const left = "\\l" + node.content;
|
|
584
|
-
const right = "\\r" + node.content;
|
|
585
|
-
const arg0 = node.args![0];
|
|
586
|
-
const tex_node_type = node.isOverHigh() ? 'leftright' : 'ordgroup';
|
|
587
|
-
return new TexNode(tex_node_type, '', [
|
|
588
|
-
new TexNode('symbol', left),
|
|
589
|
-
convert_typst_node_to_tex(arg0),
|
|
590
|
-
new TexNode('symbol', right)
|
|
549
|
+
// special hook for lr
|
|
550
|
+
if (node.content === 'lr') {
|
|
551
|
+
const data = node.data as TypstLrData;
|
|
552
|
+
if (data.leftDelim !== null) {
|
|
553
|
+
let left_delim = apply_escape_if_needed(data.leftDelim);
|
|
554
|
+
assert(data.rightDelim !== null, "leftDelim has value but rightDelim not");
|
|
555
|
+
let right_delim = apply_escape_if_needed(data.rightDelim!);
|
|
556
|
+
// TODO: should be TeXNode('leftright', ...)
|
|
557
|
+
// But currently writer will output `\left |` while people commonly prefer `\left|`.
|
|
558
|
+
return new TexNode('ordgroup', '', [
|
|
559
|
+
new TexNode('element', '\\left' + left_delim),
|
|
560
|
+
...node.args!.map(convert_typst_node_to_tex),
|
|
561
|
+
new TexNode('element', '\\right' + right_delim)
|
|
591
562
|
]);
|
|
563
|
+
} else {
|
|
564
|
+
return new TexNode('ordgroup', '', node.args!.map(convert_typst_node_to_tex));
|
|
592
565
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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));
|
|
612
623
|
} else {
|
|
613
|
-
// special hook for vec
|
|
614
|
-
// "vec(a, b, c)" -> "\begin{pmatrix}a\\ b\\ c\end{pmatrix}"
|
|
615
|
-
if (node.content === 'vec') {
|
|
616
|
-
const tex_data = node.args!.map(convert_typst_node_to_tex).map((n) => [n]) as TexArrayData;
|
|
617
|
-
return new TexNode('beginend', 'pmatrix', [], tex_data);
|
|
618
|
-
}
|
|
619
624
|
return new TexNode('ordgroup', '', [
|
|
620
625
|
new TexNode('symbol', typst_token_to_tex(node.content)),
|
|
621
626
|
new TexNode('element', '('),
|
|
622
|
-
...
|
|
627
|
+
...array_intersperse(node.args!.map(convert_typst_node_to_tex), TEX_NODE_COMMA),
|
|
623
628
|
new TexNode('element', ')')
|
|
624
629
|
]);
|
|
625
630
|
}
|
|
@@ -664,47 +669,38 @@ export function convert_typst_node_to_tex(node: TypstNode): TexNode {
|
|
|
664
669
|
case 'matrix': {
|
|
665
670
|
const typst_data = node.data as TypstNode[][];
|
|
666
671
|
const tex_data = typst_data.map(row => row.map(convert_typst_node_to_tex));
|
|
667
|
-
let env_type = 'pmatrix';
|
|
672
|
+
let env_type = 'pmatrix'; // typst mat use delim:"(" by default
|
|
668
673
|
if (node.options) {
|
|
669
674
|
if ('delim' in node.options) {
|
|
670
675
|
const delim = node.options.delim;
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
break;
|
|
700
|
-
case '|':
|
|
701
|
-
env_type = 'vmatrix';
|
|
702
|
-
break;
|
|
703
|
-
case ')':
|
|
704
|
-
case '(':
|
|
705
|
-
default:
|
|
706
|
-
env_type = 'pmatrix';
|
|
707
|
-
}
|
|
676
|
+
switch (delim.content) {
|
|
677
|
+
case '#none':
|
|
678
|
+
env_type = 'matrix';
|
|
679
|
+
break;
|
|
680
|
+
case '[':
|
|
681
|
+
case ']':
|
|
682
|
+
env_type = 'bmatrix';
|
|
683
|
+
break;
|
|
684
|
+
case '(':
|
|
685
|
+
case ')':
|
|
686
|
+
env_type = 'pmatrix';
|
|
687
|
+
break;
|
|
688
|
+
case '{':
|
|
689
|
+
case '}':
|
|
690
|
+
env_type = 'Bmatrix';
|
|
691
|
+
break;
|
|
692
|
+
case '|':
|
|
693
|
+
env_type = 'vmatrix';
|
|
694
|
+
break;
|
|
695
|
+
case 'bar':
|
|
696
|
+
case 'bar.v':
|
|
697
|
+
env_type = 'vmatrix';
|
|
698
|
+
break;
|
|
699
|
+
case 'bar.v.double':
|
|
700
|
+
env_type = 'Vmatrix';
|
|
701
|
+
break;
|
|
702
|
+
default:
|
|
703
|
+
throw new Error(`Unexpected delimiter ${delim.content}`);
|
|
708
704
|
}
|
|
709
705
|
}
|
|
710
706
|
}
|
package/src/generic.ts
CHANGED
|
@@ -38,7 +38,7 @@ export function array_split<T extends IEquatable>(array: T[], sep: T): T[][] {
|
|
|
38
38
|
|
|
39
39
|
// e.g. input array=['a', 'b', 'c'], sep = '+'
|
|
40
40
|
// return ['a','+', 'b', '+','c']
|
|
41
|
-
export function
|
|
41
|
+
export function array_intersperse<T>(array: T[], sep: T): T[] {
|
|
42
42
|
const res: T[] = [];
|
|
43
43
|
for (let i = 0; i < array.length; i++) {
|
|
44
44
|
res.push(array[i]);
|
package/src/jslex.ts
CHANGED
|
@@ -15,9 +15,8 @@ interface IRule<T> {
|
|
|
15
15
|
|
|
16
16
|
interface IMatch<T> {
|
|
17
17
|
index: number;
|
|
18
|
-
text: string;
|
|
19
|
-
len: number;
|
|
20
18
|
rule: IRule<T>;
|
|
19
|
+
reMatchArray: RegExpMatchArray;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
|
|
@@ -31,8 +30,10 @@ const EOF = {};
|
|
|
31
30
|
* @return {int} Difference between the matches.
|
|
32
31
|
*/
|
|
33
32
|
function matchcompare<T>(m1: IMatch<T>, m2: IMatch<T>): number {
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const m1_len = m1.reMatchArray[0].length;
|
|
34
|
+
const m2_len = m2.reMatchArray[0].length;
|
|
35
|
+
if(m2_len !== m1_len) {
|
|
36
|
+
return m2_len - m1_len;
|
|
36
37
|
} else {
|
|
37
38
|
return m1.index - m2.index;
|
|
38
39
|
}
|
|
@@ -59,6 +60,7 @@ export class Scanner<T> {
|
|
|
59
60
|
|
|
60
61
|
private _text: string | null = null;
|
|
61
62
|
private _leng: number | null = null;
|
|
63
|
+
private _reMatchArray: RegExpMatchArray | null = null;
|
|
62
64
|
|
|
63
65
|
constructor(input: string, lexer: JSLex<T>) {
|
|
64
66
|
this._input = input;
|
|
@@ -77,6 +79,10 @@ export class Scanner<T> {
|
|
|
77
79
|
return this._leng;
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
public reMatchArray(): RegExpMatchArray | null {
|
|
83
|
+
return this._reMatchArray;
|
|
84
|
+
}
|
|
85
|
+
|
|
80
86
|
/**
|
|
81
87
|
* Position of in stream, line number and column number of match.
|
|
82
88
|
*/
|
|
@@ -180,9 +186,8 @@ export class Scanner<T> {
|
|
|
180
186
|
if (mt !== null && mt[0].length > 0) {
|
|
181
187
|
matches.push({
|
|
182
188
|
index: i,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
rule: rule
|
|
189
|
+
rule: rule,
|
|
190
|
+
reMatchArray: mt,
|
|
186
191
|
});
|
|
187
192
|
}
|
|
188
193
|
}
|
|
@@ -193,22 +198,24 @@ export class Scanner<T> {
|
|
|
193
198
|
this._go = true;
|
|
194
199
|
|
|
195
200
|
let result: T | T[];
|
|
196
|
-
let
|
|
201
|
+
let matched_text: string;
|
|
197
202
|
for (let j = 0, n = matches.length; j < n && this._go; j++) {
|
|
198
203
|
this._offset = 0;
|
|
199
204
|
this._less = null;
|
|
200
205
|
this._go = false;
|
|
201
206
|
this._newstate = null;
|
|
202
|
-
m = matches[j];
|
|
203
|
-
|
|
204
|
-
this.
|
|
207
|
+
const m = matches[j];
|
|
208
|
+
matched_text = m.reMatchArray[0];
|
|
209
|
+
this._text = matched_text;
|
|
210
|
+
this._leng = matched_text.length;
|
|
211
|
+
this._reMatchArray = m.reMatchArray;
|
|
205
212
|
result = m.rule.action(this);
|
|
206
213
|
if (this._newstate && this._newstate != this._state) {
|
|
207
214
|
this._state = this._newstate;
|
|
208
215
|
break;
|
|
209
216
|
}
|
|
210
217
|
}
|
|
211
|
-
const text = this._less === null ?
|
|
218
|
+
const text = this._less === null ? matched_text! : matched_text!.substring(0, this._less);
|
|
212
219
|
const len = text.length;
|
|
213
220
|
this._pos += len + this._offset;
|
|
214
221
|
|
package/src/map.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const symbolMap = new Map<string, string>([
|
|
2
2
|
['displaystyle', 'display'],
|
|
3
|
+
['hspace', '#h'],
|
|
3
4
|
|
|
4
5
|
['|', 'bar.v.double'],
|
|
5
|
-
['!', '#h(-math.thin.amount)'],
|
|
6
6
|
[',', 'thin'],
|
|
7
7
|
[':', 'med'],
|
|
8
8
|
[';', 'thick'],
|
|
@@ -1066,7 +1066,6 @@ const reverseSymbolMap = new Map<string, string>();
|
|
|
1066
1066
|
for(const [key, value] of Array.from(symbolMap.entries()).reverse()) {
|
|
1067
1067
|
reverseSymbolMap.set(value, key);
|
|
1068
1068
|
}
|
|
1069
|
-
reverseSymbolMap.set('dif', 'mathrm{d}');
|
|
1070
1069
|
reverseSymbolMap.set('oo', 'infty');
|
|
1071
1070
|
|
|
1072
1071
|
// force override some one-to-multiple mappings
|
|
@@ -1078,8 +1077,6 @@ const typst_to_tex_map = new Map<string, string>([
|
|
|
1078
1077
|
['upright', 'mathrm'],
|
|
1079
1078
|
['bold', 'boldsymbol'],
|
|
1080
1079
|
['infinity', 'infty'],
|
|
1081
|
-
|
|
1082
|
-
['hyph.minus', '\\text{-}'],
|
|
1083
1080
|
]);
|
|
1084
1081
|
|
|
1085
1082
|
for(const [key, value] of typst_to_tex_map) {
|