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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +21 -4
  3. package/dist/index.js +409 -340
  4. package/dist/parser.js +23 -0
  5. package/dist/tex2typst.min.js +12 -12
  6. package/package.json +6 -7
  7. package/src/convert.ts +56 -6
  8. package/src/exposed-types.ts +23 -0
  9. package/src/index.ts +9 -5
  10. package/src/jslex.ts +1 -1
  11. package/src/tex-tokenizer.ts +1 -0
  12. package/src/tex-types.ts +1 -17
  13. package/src/tex2typst.ts +2 -4
  14. package/src/typst-parser.ts +9 -10
  15. package/src/typst-types.ts +484 -230
  16. package/src/typst-writer.ts +28 -274
  17. package/tests/cheat-sheet.test.ts +42 -0
  18. package/tests/cheat-sheet.toml +304 -0
  19. package/tests/example.ts +15 -0
  20. package/tests/general-symbols.test.ts +22 -0
  21. package/tests/general-symbols.toml +755 -0
  22. package/tests/integration-tex2typst.yaml +89 -0
  23. package/tests/struct-bidirection.yaml +179 -0
  24. package/tests/struct-tex2typst.yaml +443 -0
  25. package/tests/struct-typst2tex.yaml +412 -0
  26. package/tests/symbol.yml +123 -0
  27. package/tests/test-common.ts +26 -0
  28. package/tests/tex-parser.test.ts +57 -0
  29. package/tests/tex-to-typst.test.ts +143 -0
  30. package/tests/typst-parser.test.ts +134 -0
  31. package/tests/typst-to-tex.test.ts +76 -0
  32. package/tsconfig.json +11 -16
  33. package/dist/convert.d.ts +0 -9
  34. package/dist/generic.d.ts +0 -9
  35. package/dist/jslex.d.ts +0 -107
  36. package/dist/map.d.ts +0 -5
  37. package/dist/tex-parser.d.ts +0 -23
  38. package/dist/tex-tokenizer.d.ts +0 -4
  39. package/dist/tex-types.d.ts +0 -107
  40. package/dist/tex-writer.d.ts +0 -8
  41. package/dist/typst-parser.d.ts +0 -23
  42. package/dist/typst-shorthands.d.ts +0 -3
  43. package/dist/typst-tokenizer.d.ts +0 -2
  44. package/dist/typst-types.d.ts +0 -98
  45. package/dist/typst-writer.d.ts +0 -30
  46. 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.26",
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
- "prebuild": "rimraf dist/",
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": "tsc --project ./tsconfig.json",
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, Tex2TypstOptions,
2
- TexToken, TexTokenType, TexFuncCall, TexGroup, TexSupSub,
3
- TexText, TexBeginEnd, TexLeftRight,
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} -> `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 "./tex-types";
3
- import { TypstWriter, type TypstWriterOptions } from "./typst-writer";
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 (options[key] !== undefined) {
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 { Tex2TypstOptions, symbolMap, shorthandMap };
50
+ export { symbolMap, shorthandMap };
package/src/jslex.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
  interface ILexSpec<T> {
8
- start: Map<string, (arg0: Scanner<T>) => T | T[]>;
8
+ [key: string]: Map<string, (arg0: Scanner<T>) => T | T[]>;
9
9
  }
10
10
 
11
11
  interface IRule<T> {
@@ -35,6 +35,7 @@ export const TEX_UNARY_COMMANDS = [
35
35
  'overrightarrow',
36
36
  'hspace',
37
37
  'substack',
38
+ 'set',
38
39
  ]
39
40
 
40
41
  export const TEX_BINARY_COMMANDS = [
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
@@ -4,7 +4,5 @@
4
4
 
5
5
  import { tex2typst, typst2tex } from './index';
6
6
 
7
- if(typeof window !== 'undefined') {
8
- (window as any).tex2typst = tex2typst;
9
- (window as any).typst2tex = typst2tex;
10
- }
7
+ (window as any).tex2typst = tex2typst;
8
+ (window as any).typst2tex = typst2tex;
@@ -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
- [LEFT_PARENTHESES, LEFT_BRACKET, LEFT_CURLY_BRACKET, VERTICAL_BAR],
57
- [RIGHT_PARENTHESES, RIGHT_BRACKET, RIGHT_CURLY_BRACKET, VERTICAL_BAR]
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
- if (tokens[start + 1].isOneOf([LEFT_PARENTHESES, LEFT_BRACKET, LEFT_CURLY_BRACKET, VERTICAL_BAR])) {
377
- const end = find_closing_match(tokens, start);
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.parseArgumentsWithSeparator(tokens, inner_start + 1, inner_end, COMMA);
379
+ const [inner_args, _]= this.parseGroup(tokens, inner_start + 1, inner_end);
381
380
  return [
382
- new TypstLeftright(lr_token, { body: new TypstGroup(inner_args), left: tokens[inner_start], right: tokens[inner_end]}),
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 [args, end] = this.parseArguments(tokens, start);
385
+ const [inner_args, _] = this.parseGroup(tokens, start + 1, end - 1);
387
386
  return [
388
- new TypstLeftright(lr_token, { body: new TypstGroup(args), left: null, right: null }),
389
- end,
387
+ new TypstLeftright(lr_token, { body: inner_args, left: null, right: null }),
388
+ end + 1,
390
389
  ];
391
390
  }
392
391
  }