tex2typst 0.3.27-beta.1 → 0.3.28
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 +22 -24
- package/dist/index.js +509 -417
- package/dist/parser.js +23 -0
- package/dist/tex2typst.min.js +13 -13
- package/package.json +1 -1
- package/src/convert.ts +112 -8
- package/src/exposed-types.ts +22 -24
- package/src/generic.ts +16 -0
- package/src/index.ts +7 -4
- package/src/jslex.ts +1 -1
- package/src/map.ts +1 -1
- package/src/tex-parser.ts +128 -107
- package/src/tex-tokenizer.ts +6 -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 +188 -0
- package/tests/struct-tex2typst.yaml +463 -0
- package/tests/struct-typst2tex.yaml +412 -0
- package/tests/symbol.yml +126 -0
- package/tests/test-common.ts +26 -0
- package/tests/tex-parser.test.ts +97 -0
- package/tests/tex-to-typst.test.ts +136 -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/convert.ts
CHANGED
|
@@ -188,6 +188,41 @@ function convert_tex_array_align_literal(alignLiteral: string): TypstNamedParams
|
|
|
188
188
|
return np;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
const TYPST_LEFT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '(');
|
|
192
|
+
const TYPST_RIGHT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ELEMENT, ')');
|
|
193
|
+
|
|
194
|
+
function is_delimiter(c: TypstNode): boolean {
|
|
195
|
+
return c.head.type === TypstTokenType.ELEMENT && ['(', ')', '[', ']', '{', '}', '|', '⌊', '⌋', '⌈', '⌉'].includes(c.head.value);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function appendWithBracketsIfNeeded(node: TypstNode): TypstNode {
|
|
199
|
+
let need_to_wrap = ['group', 'supsub', 'matrixLike', 'fraction','empty'].includes(node.type);
|
|
200
|
+
|
|
201
|
+
if (node.type === 'group') {
|
|
202
|
+
const group = node as TypstGroup;
|
|
203
|
+
if (group.items.length === 0) {
|
|
204
|
+
// e.g. TeX `P_{}` converts to Typst `P_()`
|
|
205
|
+
need_to_wrap = true;
|
|
206
|
+
} else {
|
|
207
|
+
const first = group.items[0];
|
|
208
|
+
const last = group.items[group.items.length - 1];
|
|
209
|
+
if (is_delimiter(first) && is_delimiter(last)) {
|
|
210
|
+
need_to_wrap = false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (need_to_wrap) {
|
|
216
|
+
return new TypstLeftright(null, {
|
|
217
|
+
left: TYPST_LEFT_PARENTHESIS,
|
|
218
|
+
right: TYPST_RIGHT_PARENTHESIS,
|
|
219
|
+
body: node,
|
|
220
|
+
|
|
221
|
+
});
|
|
222
|
+
} else {
|
|
223
|
+
return node;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
191
226
|
|
|
192
227
|
export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2TypstOptions = {}): TypstNode {
|
|
193
228
|
switch (abstractNode.type) {
|
|
@@ -197,12 +232,6 @@ export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2Ty
|
|
|
197
232
|
}
|
|
198
233
|
case 'text': {
|
|
199
234
|
const node = abstractNode as TexText;
|
|
200
|
-
if ((/[^\x00-\x7F]+/).test(node.head.value) && options.nonAsciiWrapper !== "") {
|
|
201
|
-
return new TypstFuncCall(
|
|
202
|
-
new TypstToken(TypstTokenType.SYMBOL, options.nonAsciiWrapper!),
|
|
203
|
-
[new TypstToken(TypstTokenType.TEXT, node.head.value).toNode()]
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
235
|
return new TypstToken(TypstTokenType.TEXT, node.head.value).toNode();
|
|
207
236
|
}
|
|
208
237
|
case 'ordgroup':
|
|
@@ -234,6 +263,14 @@ export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2Ty
|
|
|
234
263
|
sub: sub? convert_tex_node_to_typst(sub, options) : null,
|
|
235
264
|
};
|
|
236
265
|
|
|
266
|
+
data.base = appendWithBracketsIfNeeded(data.base);
|
|
267
|
+
if (data.sup) {
|
|
268
|
+
data.sup = appendWithBracketsIfNeeded(data.sup);
|
|
269
|
+
}
|
|
270
|
+
if (data.sub) {
|
|
271
|
+
data.sub = appendWithBracketsIfNeeded(data.sub);
|
|
272
|
+
}
|
|
273
|
+
|
|
237
274
|
return new TypstSupsub(data);
|
|
238
275
|
}
|
|
239
276
|
case 'leftright': {
|
|
@@ -348,12 +385,54 @@ export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2Ty
|
|
|
348
385
|
return res;
|
|
349
386
|
}
|
|
350
387
|
|
|
351
|
-
// \substack{a \\ b} ->
|
|
388
|
+
// \substack{a \\ b} -> a \ b
|
|
352
389
|
// as in translation from \sum_{\substack{a \\ b}} to sum_(a \ b)
|
|
353
390
|
if (node.head.value === '\\substack') {
|
|
354
391
|
return arg0;
|
|
355
392
|
}
|
|
356
393
|
|
|
394
|
+
// \displaylines{...} -> ...
|
|
395
|
+
if (node.head.value === '\\displaylines') {
|
|
396
|
+
return arg0;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// \mathinner{...} -> ...
|
|
400
|
+
if (node.head.value === '\\mathinner') {
|
|
401
|
+
return arg0;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// \mathrel{X} -> class("relation", X)
|
|
405
|
+
if (node.head.value === '\\mathrel') {
|
|
406
|
+
return new TypstFuncCall(
|
|
407
|
+
new TypstToken(TypstTokenType.SYMBOL, 'class'),
|
|
408
|
+
[new TypstToken(TypstTokenType.TEXT, 'relation').toNode(), arg0]
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// \mathbin{X} -> class("binary", X)
|
|
413
|
+
if (node.head.value === '\\mathbin') {
|
|
414
|
+
return new TypstFuncCall(
|
|
415
|
+
new TypstToken(TypstTokenType.SYMBOL, 'class'),
|
|
416
|
+
[new TypstToken(TypstTokenType.TEXT, 'binary').toNode(), arg0]
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// \mathop{X} -> class("large", X)
|
|
421
|
+
if (node.head.value === '\\mathop') {
|
|
422
|
+
return new TypstFuncCall(
|
|
423
|
+
new TypstToken(TypstTokenType.SYMBOL, 'class'),
|
|
424
|
+
[new TypstToken(TypstTokenType.TEXT, 'large').toNode(), arg0]
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// \set{a, b, c} -> {a, b, c}
|
|
429
|
+
if (node.head.value === '\\set') {
|
|
430
|
+
return new TypstLeftright(
|
|
431
|
+
null,
|
|
432
|
+
{ body: arg0, left: TypstToken.LEFT_BRACE, right: TypstToken.RIGHT_BRACE }
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
357
436
|
if (node.head.value === '\\overset') {
|
|
358
437
|
return convert_overset(node, options);
|
|
359
438
|
}
|
|
@@ -364,7 +443,7 @@ export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2Ty
|
|
|
364
443
|
// \frac{a}{b} -> a / b
|
|
365
444
|
if (node.head.value === '\\frac') {
|
|
366
445
|
if (options.fracToSlash) {
|
|
367
|
-
return new TypstFraction(node.args.map((n) => convert_tex_node_to_typst(n, options)));
|
|
446
|
+
return new TypstFraction(node.args.map((n) => convert_tex_node_to_typst(n, options)).map(appendWithBracketsIfNeeded));
|
|
368
447
|
}
|
|
369
448
|
}
|
|
370
449
|
|
|
@@ -697,6 +776,31 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
|
|
|
697
776
|
assert(arg0.head.type === TypstTokenType.TEXT);
|
|
698
777
|
return new TexFuncCall(typst_token_to_tex(node.head), [new TexToken(TexTokenType.LITERAL, arg0.head.value).toNode()]);
|
|
699
778
|
}
|
|
779
|
+
case 'class': {
|
|
780
|
+
const arg0 = node.args[0];
|
|
781
|
+
assert(arg0.head.type === TypstTokenType.TEXT);
|
|
782
|
+
let command: string;
|
|
783
|
+
switch (arg0.head.value) {
|
|
784
|
+
// \mathrel{X} <- class("relation", X)
|
|
785
|
+
case 'relation':
|
|
786
|
+
command = '\\mathrel';
|
|
787
|
+
break;
|
|
788
|
+
// \mathbin{X} <- class("binary", X)
|
|
789
|
+
case 'binary':
|
|
790
|
+
command = '\\mathbin';
|
|
791
|
+
break;
|
|
792
|
+
// \mathop{X} <- class("large", X)
|
|
793
|
+
case 'large':
|
|
794
|
+
command = '\\mathop';
|
|
795
|
+
break;
|
|
796
|
+
default:
|
|
797
|
+
throw new Error(`Unimplemented class: ${arg0.head.value}`);
|
|
798
|
+
}
|
|
799
|
+
return new TexFuncCall(
|
|
800
|
+
new TexToken(TexTokenType.COMMAND, command),
|
|
801
|
+
[convert_typst_node_to_tex(node.args[1])]
|
|
802
|
+
);
|
|
803
|
+
}
|
|
700
804
|
// general case
|
|
701
805
|
default: {
|
|
702
806
|
const func_name_tex = typst_token_to_tex(node.head);
|
package/src/exposed-types.ts
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* ATTENTION:
|
|
4
|
-
* Don't use any options except those explicitly documented in
|
|
5
|
-
* https://github.com/qwinsi/tex2typst/blob/main/docs/api-reference.md
|
|
6
|
-
* Any undocumented options may be not working at present or break in the future!
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export declare
|
|
22
|
-
|
|
23
|
-
export declare const symbolMap: Map<string, string>;
|
|
24
|
-
export declare const shorthandMap: Map<string, string>;
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* ATTENTION:
|
|
4
|
+
* Don't use any options except those explicitly documented in
|
|
5
|
+
* https://github.com/qwinsi/tex2typst/blob/main/docs/api-reference.md
|
|
6
|
+
* Any undocumented options may be not working at present or break in the future!
|
|
7
|
+
*/
|
|
8
|
+
export interface Tex2TypstOptions {
|
|
9
|
+
nonStrict?: boolean; /** default is true */
|
|
10
|
+
preferShorthands?: boolean; /** default is true */
|
|
11
|
+
keepSpaces?: boolean; /** default is false */
|
|
12
|
+
fracToSlash?: boolean; /** default is true */
|
|
13
|
+
inftyToOo?: boolean; /** default is false */
|
|
14
|
+
optimize?: boolean; /** default is true */
|
|
15
|
+
customTexMacros?: { [key: string]: string; };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export declare function tex2typst(tex: string, options?: Tex2TypstOptions): string;
|
|
19
|
+
export declare function typst2tex(typst: string): string;
|
|
20
|
+
|
|
21
|
+
export declare const symbolMap: Map<string, string>;
|
|
22
|
+
export declare const shorthandMap: Map<string, string>;
|
package/src/generic.ts
CHANGED
|
@@ -59,6 +59,22 @@ export function array_split<T extends IEquatable>(array: T[], sep: T): T[][] {
|
|
|
59
59
|
return res;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// e.g. input array=[['a', 'b'], ['c'], ['d', 'e']], sep = '+'
|
|
63
|
+
// return ['a', 'b', '+', 'c', '+', 'd', 'e']
|
|
64
|
+
export function array_join<T>(arrays: T[][], sep: T): T[] {
|
|
65
|
+
/*
|
|
66
|
+
const res: T[] = [];
|
|
67
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
68
|
+
res.push(...arrays[i]);
|
|
69
|
+
if (i !== arrays.length - 1) {
|
|
70
|
+
res.push(sep);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return res;
|
|
74
|
+
*/
|
|
75
|
+
return arrays.flatMap((arr, i) => i !== arrays.length - 1? [...arr, sep]: arr);
|
|
76
|
+
}
|
|
77
|
+
|
|
62
78
|
// e.g. input array=['a', 'b', 'c'], sep = '+'
|
|
63
79
|
// return ['a','+', 'b', '+','c']
|
|
64
80
|
export function array_intersperse<T>(array: T[], sep: T): T[] {
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseTex } from "./tex-parser";
|
|
2
2
|
import type { Tex2TypstOptions } from "./exposed-types";
|
|
3
|
-
import { TypstWriter
|
|
3
|
+
import { TypstWriter } from "./typst-writer";
|
|
4
|
+
import { type TypstWriterOptions } from "./typst-types";
|
|
4
5
|
import { convert_tex_node_to_typst, convert_typst_node_to_tex } from "./convert";
|
|
5
6
|
import { symbolMap } from "./map";
|
|
6
7
|
import { parseTypst } from "./typst-parser";
|
|
@@ -16,14 +17,16 @@ export function tex2typst(tex: string, options?: Tex2TypstOptions): string {
|
|
|
16
17
|
fracToSlash: true,
|
|
17
18
|
inftyToOo: false,
|
|
18
19
|
optimize: true,
|
|
19
|
-
nonAsciiWrapper: "",
|
|
20
20
|
customTexMacros: {}
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
if(options !== undefined) {
|
|
24
|
+
if (typeof options !== 'object') {
|
|
25
|
+
throw new Error("options must be an object");
|
|
26
|
+
}
|
|
24
27
|
for (const key in opt) {
|
|
25
|
-
if (
|
|
26
|
-
opt[key] = options[key];
|
|
28
|
+
if (key in options) {
|
|
29
|
+
opt[key as keyof Tex2TypstOptions] = options[key as keyof Tex2TypstOptions] as any;
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
}
|
package/src/jslex.ts
CHANGED
package/src/map.ts
CHANGED
|
@@ -56,6 +56,7 @@ const symbolMap = new Map<string, string>([
|
|
|
56
56
|
['mathbb', 'bb'],
|
|
57
57
|
['mathbf', 'bold'],
|
|
58
58
|
['mathcal', 'cal'],
|
|
59
|
+
['mathscr', 'scr'],
|
|
59
60
|
['mathit', 'italic'],
|
|
60
61
|
['mathfrak', 'frak'],
|
|
61
62
|
['mathrm', 'upright'],
|
|
@@ -260,7 +261,6 @@ const symbolMap = new Map<string, string>([
|
|
|
260
261
|
['intop', 'limits(integral)'],
|
|
261
262
|
|
|
262
263
|
// extended
|
|
263
|
-
['mathscr', 'scr'],
|
|
264
264
|
['LaTeX', '#LaTeX'],
|
|
265
265
|
['TeX', '#TeX'],
|
|
266
266
|
]);
|