tex2typst 0.3.26 → 0.3.27
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 +21 -4
- package/dist/index.js +409 -340
- package/dist/parser.js +23 -0
- package/dist/tex2typst.min.js +12 -12
- package/package.json +6 -7
- package/src/convert.ts +56 -6
- package/src/exposed-types.ts +23 -0
- package/src/index.ts +9 -5
- package/src/jslex.ts +1 -1
- package/src/tex-tokenizer.ts +1 -0
- package/src/tex-types.ts +1 -17
- 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 +179 -0
- package/tests/struct-tex2typst.yaml +443 -0
- package/tests/struct-typst2tex.yaml +412 -0
- package/tests/symbol.yml +123 -0
- package/tests/test-common.ts +26 -0
- package/tests/tex-parser.test.ts +57 -0
- package/tests/tex-to-typst.test.ts +143 -0
- package/tests/typst-parser.test.ts +134 -0
- package/tests/typst-to-tex.test.ts +76 -0
- package/tsconfig.json +11 -16
- package/dist/convert.d.ts +0 -9
- package/dist/generic.d.ts +0 -9
- package/dist/jslex.d.ts +0 -107
- package/dist/map.d.ts +0 -5
- package/dist/tex-parser.d.ts +0 -23
- package/dist/tex-tokenizer.d.ts +0 -4
- package/dist/tex-types.d.ts +0 -107
- package/dist/tex-writer.d.ts +0 -8
- package/dist/typst-parser.d.ts +0 -23
- package/dist/typst-shorthands.d.ts +0 -3
- package/dist/typst-tokenizer.d.ts +0 -2
- package/dist/typst-types.d.ts +0 -98
- package/dist/typst-writer.d.ts +0 -30
- package/dist/util.d.ts +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tex2typst",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.27",
|
|
4
4
|
"description": "JavaScript library for converting TeX code to Typst",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,22 +14,21 @@
|
|
|
14
14
|
"Markdown"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"
|
|
17
|
+
"type-check": "npx tsc --project tsconfig.json --noEmit",
|
|
18
18
|
"build:node": "esbuild src/index.ts --bundle --platform=node --outfile=dist/index.js --format=esm",
|
|
19
19
|
"build:browser": "esbuild src/tex2typst.ts --bundle --platform=browser --outfile=dist/tex2typst.min.js --minify",
|
|
20
|
-
"build:types": "
|
|
21
|
-
"build": "npm run build:node && npm run build:browser && npm run build:types",
|
|
20
|
+
"build:types": "cp src/exposed-types.ts dist/index.d.ts",
|
|
21
|
+
"build": "npm run type-check && npm run build:node && npm run build:browser && npm run build:types",
|
|
22
22
|
"test": "vitest run",
|
|
23
23
|
"pub": "npm run build && npm run test && npm publish"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/node": "^20.14.10",
|
|
27
|
+
"esbuild": "^0.25.1",
|
|
27
28
|
"js-yaml": "^4.1.0",
|
|
28
|
-
"rimraf": "^3.0.2",
|
|
29
29
|
"toml": "^3.0.0",
|
|
30
30
|
"typescript": "^5.5.3",
|
|
31
|
-
"vitest": "^2.0.2"
|
|
32
|
-
"esbuild": "^0.25.1"
|
|
31
|
+
"vitest": "^2.0.2"
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {}
|
|
35
34
|
}
|
package/src/convert.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { TexNode,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
TexTerminal} from "./tex-types";
|
|
1
|
+
import { TexNode, TexToken, TexTokenType, TexFuncCall, TexGroup, TexSupSub,
|
|
2
|
+
TexText, TexBeginEnd, TexLeftRight, TexTerminal} from "./tex-types";
|
|
3
|
+
import type { Tex2TypstOptions } from "./exposed-types";
|
|
5
4
|
import { TypstFraction, TypstFuncCall, TypstGroup, TypstLeftright, TypstMarkupFunc, TypstMatrixLike, TypstNode, TypstSupsub, TypstTerminal } from "./typst-types";
|
|
6
5
|
import { TypstNamedParams } from "./typst-types";
|
|
7
6
|
import { TypstSupsubData } from "./typst-types";
|
|
@@ -189,6 +188,41 @@ function convert_tex_array_align_literal(alignLiteral: string): TypstNamedParams
|
|
|
189
188
|
return np;
|
|
190
189
|
}
|
|
191
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
|
+
}
|
|
192
226
|
|
|
193
227
|
export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2TypstOptions = {}): TypstNode {
|
|
194
228
|
switch (abstractNode.type) {
|
|
@@ -235,6 +269,14 @@ export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2Ty
|
|
|
235
269
|
sub: sub? convert_tex_node_to_typst(sub, options) : null,
|
|
236
270
|
};
|
|
237
271
|
|
|
272
|
+
data.base = appendWithBracketsIfNeeded(data.base);
|
|
273
|
+
if (data.sup) {
|
|
274
|
+
data.sup = appendWithBracketsIfNeeded(data.sup);
|
|
275
|
+
}
|
|
276
|
+
if (data.sub) {
|
|
277
|
+
data.sub = appendWithBracketsIfNeeded(data.sub);
|
|
278
|
+
}
|
|
279
|
+
|
|
238
280
|
return new TypstSupsub(data);
|
|
239
281
|
}
|
|
240
282
|
case 'leftright': {
|
|
@@ -349,12 +391,20 @@ export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2Ty
|
|
|
349
391
|
return res;
|
|
350
392
|
}
|
|
351
393
|
|
|
352
|
-
// \substack{a \\ b} ->
|
|
394
|
+
// \substack{a \\ b} -> a \ b
|
|
353
395
|
// as in translation from \sum_{\substack{a \\ b}} to sum_(a \ b)
|
|
354
396
|
if (node.head.value === '\\substack') {
|
|
355
397
|
return arg0;
|
|
356
398
|
}
|
|
357
399
|
|
|
400
|
+
// \set{a, b, c} -> {a, b, c}
|
|
401
|
+
if (node.head.value === '\\set') {
|
|
402
|
+
return new TypstLeftright(
|
|
403
|
+
null,
|
|
404
|
+
{ body: arg0, left: TypstToken.LEFT_BRACE, right: TypstToken.RIGHT_BRACE }
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
358
408
|
if (node.head.value === '\\overset') {
|
|
359
409
|
return convert_overset(node, options);
|
|
360
410
|
}
|
|
@@ -365,7 +415,7 @@ export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2Ty
|
|
|
365
415
|
// \frac{a}{b} -> a / b
|
|
366
416
|
if (node.head.value === '\\frac') {
|
|
367
417
|
if (options.fracToSlash) {
|
|
368
|
-
return new TypstFraction(node.args.map((n) => convert_tex_node_to_typst(n, options)));
|
|
418
|
+
return new TypstFraction(node.args.map((n) => convert_tex_node_to_typst(n, options)).map(appendWithBracketsIfNeeded));
|
|
369
419
|
}
|
|
370
420
|
}
|
|
371
421
|
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
nonAsciiWrapper?: string; /** default is "" */
|
|
16
|
+
customTexMacros?: { [key: string]: string; };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export declare function tex2typst(tex: string, options?: Tex2TypstOptions): string;
|
|
20
|
+
export declare function typst2tex(typst: string): string;
|
|
21
|
+
|
|
22
|
+
export declare const symbolMap: Map<string, string>;
|
|
23
|
+
export declare const shorthandMap: Map<string, string>;
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseTex } from "./tex-parser";
|
|
2
|
-
import type { Tex2TypstOptions } from "./
|
|
3
|
-
import { TypstWriter
|
|
2
|
+
import type { Tex2TypstOptions } from "./exposed-types";
|
|
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";
|
|
@@ -21,9 +22,12 @@ export function tex2typst(tex: string, options?: Tex2TypstOptions): string {
|
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
if(options !== undefined) {
|
|
25
|
+
if (typeof options !== 'object') {
|
|
26
|
+
throw new Error("options must be an object");
|
|
27
|
+
}
|
|
24
28
|
for (const key in opt) {
|
|
25
|
-
if (
|
|
26
|
-
opt[key] = options[key];
|
|
29
|
+
if (key in options) {
|
|
30
|
+
opt[key as keyof Tex2TypstOptions] = options[key as keyof Tex2TypstOptions] as any;
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
}
|
|
@@ -43,4 +47,4 @@ export function typst2tex(typst: string): string {
|
|
|
43
47
|
return writer.finalize();
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
export {
|
|
50
|
+
export { symbolMap, shorthandMap };
|
package/src/jslex.ts
CHANGED
package/src/tex-tokenizer.ts
CHANGED
package/src/tex-types.ts
CHANGED
|
@@ -350,20 +350,4 @@ export function writeTexTokenBuffer(buffer: string, token: TexToken): string {
|
|
|
350
350
|
return buffer + str;
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
|
|
354
|
-
* ATTENTION:
|
|
355
|
-
* Don't use any options except those explicitly documented in
|
|
356
|
-
* https://github.com/qwinsi/tex2typst/blob/main/docs/api-reference.md
|
|
357
|
-
* Any undocumented options may be not working at present or break in the future!
|
|
358
|
-
*/
|
|
359
|
-
export interface Tex2TypstOptions {
|
|
360
|
-
nonStrict?: boolean; /** default is true */
|
|
361
|
-
preferShorthands?: boolean; /** default is true */
|
|
362
|
-
keepSpaces?: boolean; /** default is false */
|
|
363
|
-
fracToSlash?: boolean; /** default is true */
|
|
364
|
-
inftyToOo?: boolean; /** default is false */
|
|
365
|
-
optimize?: boolean; /** default is true */
|
|
366
|
-
nonAsciiWrapper?: string; /** default is "" */
|
|
367
|
-
customTexMacros?: { [key: string]: string };
|
|
368
|
-
// TODO: custom typst functions
|
|
369
|
-
}
|
|
353
|
+
|
package/src/tex2typst.ts
CHANGED
package/src/typst-parser.ts
CHANGED
|
@@ -53,8 +53,8 @@ function find_closing_delim(tokens: TypstToken[], start: number): number {
|
|
|
53
53
|
return _find_closing_match(
|
|
54
54
|
tokens,
|
|
55
55
|
start,
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
TypstToken.LEFT_DELIMITERS,
|
|
57
|
+
TypstToken.RIGHT_DELIMITERS
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -218,7 +218,6 @@ const LEFT_BRACKET: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '[');
|
|
|
218
218
|
const RIGHT_BRACKET: TypstToken = new TypstToken(TypstTokenType.ELEMENT, ']');
|
|
219
219
|
const LEFT_CURLY_BRACKET: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '{');
|
|
220
220
|
const RIGHT_CURLY_BRACKET: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '}');
|
|
221
|
-
const VERTICAL_BAR = new TypstToken(TypstTokenType.ELEMENT, '|');
|
|
222
221
|
const COMMA = new TypstToken(TypstTokenType.ELEMENT, ',');
|
|
223
222
|
const SEMICOLON = new TypstToken(TypstTokenType.ELEMENT, ';');
|
|
224
223
|
const SINGLE_SPACE = new TypstToken(TypstTokenType.SPACE, ' ');
|
|
@@ -373,20 +372,20 @@ export class TypstParser {
|
|
|
373
372
|
// start: the position of the left parentheses
|
|
374
373
|
parseLrArguments(tokens: TypstToken[], start: number): [TypstNode, number] {
|
|
375
374
|
const lr_token = tokens[start];
|
|
376
|
-
|
|
377
|
-
|
|
375
|
+
const end = find_closing_match(tokens, start);
|
|
376
|
+
if (tokens[start + 1].isOneOf(TypstToken.LEFT_DELIMITERS)) {
|
|
378
377
|
const inner_start = start + 1;
|
|
379
378
|
const inner_end = find_closing_delim(tokens, inner_start);
|
|
380
|
-
const inner_args= this.
|
|
379
|
+
const [inner_args, _]= this.parseGroup(tokens, inner_start + 1, inner_end);
|
|
381
380
|
return [
|
|
382
|
-
new TypstLeftright(lr_token, { body:
|
|
381
|
+
new TypstLeftright(lr_token, { body: inner_args, left: tokens[inner_start], right: tokens[inner_end]}),
|
|
383
382
|
end + 1,
|
|
384
383
|
];
|
|
385
384
|
} else {
|
|
386
|
-
const [
|
|
385
|
+
const [inner_args, _] = this.parseGroup(tokens, start + 1, end - 1);
|
|
387
386
|
return [
|
|
388
|
-
new TypstLeftright(lr_token, { body:
|
|
389
|
-
end,
|
|
387
|
+
new TypstLeftright(lr_token, { body: inner_args, left: null, right: null }),
|
|
388
|
+
end + 1,
|
|
390
389
|
];
|
|
391
390
|
}
|
|
392
391
|
}
|