tex2typst 0.3.23 → 0.3.24

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.
@@ -0,0 +1,221 @@
1
+ import { array_includes } from "./generic";
2
+
3
+ export enum TypstTokenType {
4
+ NONE,
5
+ SYMBOL,
6
+ ELEMENT,
7
+ LITERAL,
8
+ TEXT,
9
+ COMMENT,
10
+ SPACE,
11
+ CONTROL,
12
+ NEWLINE
13
+ }
14
+
15
+
16
+ export class TypstToken {
17
+ type: TypstTokenType;
18
+ value: string;
19
+
20
+ constructor(type: TypstTokenType, content: string) {
21
+ this.type = type;
22
+ this.value = content;
23
+ }
24
+
25
+ eq(other: TypstToken): boolean {
26
+ return this.type === other.type && this.value === other.value;
27
+ }
28
+
29
+ isOneOf(tokens: TypstToken[]): boolean {
30
+ return array_includes(tokens, this);
31
+ }
32
+
33
+ public toNode(): TypstNode {
34
+ return new TypstTerminal(this);
35
+ }
36
+
37
+ public toString(): string {
38
+ switch (this.type) {
39
+ case TypstTokenType.TEXT:
40
+ return `"${this.value}"`;
41
+ case TypstTokenType.COMMENT:
42
+ return `//${this.value}`;
43
+ default:
44
+ return this.value;
45
+ }
46
+ }
47
+
48
+ public static readonly NONE = new TypstToken(TypstTokenType.NONE, '#none');
49
+ }
50
+
51
+ export interface TypstSupsubData {
52
+ base: TypstNode;
53
+ sup: TypstNode | null;
54
+ sub: TypstNode | null;
55
+ }
56
+
57
+
58
+ export interface TypstLeftRightData {
59
+ left: TypstToken | null;
60
+ right: TypstToken | null;
61
+ }
62
+
63
+ /**
64
+ * fraction: `1/2`, `(x + y)/2`, `(1+x)/(1-x)`
65
+ * group: `a + 1/3`
66
+ * leftright: `(a + 1/3)`, `[a + 1/3)`, `lr(]sum_(x=1)^n])`
67
+ */
68
+ export type TypstNodeType = 'terminal' | 'group' | 'supsub' | 'funcCall' | 'fraction'| 'leftright' | 'align' | 'matrix' | 'cases';
69
+
70
+ export type TypstNamedParams = { [key: string]: TypstNode; };
71
+
72
+ export abstract class TypstNode {
73
+ readonly type: TypstNodeType;
74
+ head: TypstToken;
75
+ args?: TypstNode[];
76
+ // Some Typst functions accept additional options. e.g. mat() has option "delim", op() has option "limits"
77
+ options?: TypstNamedParams;
78
+
79
+ constructor(type: TypstNodeType, head: TypstToken | null, args?: TypstNode[]) {
80
+ this.type = type;
81
+ this.head = head ? head : TypstToken.NONE;
82
+ this.args = args;
83
+ }
84
+
85
+ // whether the node is over high so that if it's wrapped in braces, \left and \right should be used in its TeX form
86
+ // e.g. 1/2 is over high, "2" is not.
87
+ abstract isOverHigh(): boolean;
88
+
89
+ public setOptions(options: TypstNamedParams) {
90
+ this.options = options;
91
+ }
92
+
93
+ // Note that this is only shallow equality.
94
+ public eq(other: TypstNode): boolean {
95
+ return this.type === other.type && this.head.eq(other.head);
96
+ }
97
+
98
+ public toString(): string {
99
+ throw new Error(`Unimplemented toString() in base class TypstNode`);
100
+ }
101
+ }
102
+
103
+ export class TypstTerminal extends TypstNode {
104
+ constructor(head: TypstToken) {
105
+ super('terminal', head);
106
+ }
107
+
108
+ public isOverHigh(): boolean {
109
+ return false;
110
+ }
111
+
112
+ public toString(): string {
113
+ return this.head.toString();
114
+ }
115
+ }
116
+
117
+ export class TypstGroup extends TypstNode {
118
+ constructor(args: TypstNode[]) {
119
+ super('group', TypstToken.NONE, args);
120
+ }
121
+
122
+ public isOverHigh(): boolean {
123
+ return this.args!.some((n) => n.isOverHigh());
124
+ }
125
+ }
126
+
127
+ export class TypstSupsub extends TypstNode {
128
+ public base: TypstNode;
129
+ public sup: TypstNode | null;
130
+ public sub: TypstNode | null;
131
+
132
+ constructor(data: TypstSupsubData) {
133
+ super('supsub', TypstToken.NONE, []);
134
+ this.base = data.base;
135
+ this.sup = data.sup;
136
+ this.sub = data.sub;
137
+ }
138
+
139
+ public isOverHigh(): boolean {
140
+ return this.base.isOverHigh();
141
+ }
142
+ }
143
+
144
+ export class TypstFuncCall extends TypstNode {
145
+ constructor(head: TypstToken, args: TypstNode[]) {
146
+ super('funcCall', head, args);
147
+ }
148
+
149
+ public isOverHigh(): boolean {
150
+ if (this.head.value === 'frac') {
151
+ return true;
152
+ }
153
+ return this.args!.some((n) => n.isOverHigh());
154
+ }
155
+ }
156
+
157
+ export class TypstFraction extends TypstNode {
158
+ constructor(args: TypstNode[]) {
159
+ super('fraction', TypstToken.NONE, args);
160
+ }
161
+
162
+ public isOverHigh(): boolean {
163
+ return true;
164
+ }
165
+ }
166
+
167
+
168
+ export class TypstLeftright extends TypstNode {
169
+ public left: TypstToken | null;
170
+ public right: TypstToken | null;
171
+
172
+ constructor(head: TypstToken | null, args: TypstNode[], data: TypstLeftRightData) {
173
+ super('leftright', head, args);
174
+ this.left = data.left;
175
+ this.right = data.right;
176
+ }
177
+
178
+ public isOverHigh(): boolean {
179
+ return this.args!.some((n) => n.isOverHigh());
180
+ }
181
+ }
182
+
183
+ export class TypstAlign extends TypstNode {
184
+ public matrix: TypstNode[][];
185
+
186
+ constructor(data: TypstNode[][]) {
187
+ super('align', TypstToken.NONE, []);
188
+ this.matrix = data;
189
+ }
190
+
191
+ public isOverHigh(): boolean {
192
+ return true;
193
+ }
194
+ }
195
+
196
+ export class TypstMatrix extends TypstNode {
197
+ public matrix: TypstNode[][];
198
+
199
+ constructor(data: TypstNode[][]) {
200
+ super('matrix', TypstToken.NONE, []);
201
+ this.matrix = data;
202
+ }
203
+
204
+ public isOverHigh(): boolean {
205
+ return true;
206
+ }
207
+ }
208
+
209
+ export class TypstCases extends TypstNode {
210
+ public matrix: TypstNode[][];
211
+
212
+ constructor(data: TypstNode[][]) {
213
+ super('cases', TypstToken.NONE, []);
214
+ this.matrix = data;
215
+ }
216
+
217
+ public isOverHigh(): boolean {
218
+ return true;
219
+ }
220
+ }
221
+
@@ -1,8 +1,11 @@
1
- import { TexNode, TypstNode, TypstSupsubData, TypstToken, TypstTokenType } from "./types";
1
+ import { TexNode } from "./tex-types";
2
+ import { TypstAlign, TypstCases, TypstFraction, TypstFuncCall, TypstGroup, TypstLeftright, TypstMatrix, TypstNode, TypstSupsub, TypstTerminal } from "./typst-types";
3
+ import { TypstToken } from "./typst-types";
4
+ import { TypstTokenType } from "./typst-types";
2
5
  import { shorthandMap } from "./typst-shorthands";
3
6
 
4
7
  function is_delimiter(c: TypstNode): boolean {
5
- return c.type === 'atom' && ['(', ')', '[', ']', '{', '}', '|', '⌊', '⌋', '⌈', '⌉'].includes(c.content);
8
+ return c.head.type === TypstTokenType.ELEMENT && ['(', ')', '[', ']', '{', '}', '|', '⌊', '⌋', '⌈', '⌉'].includes(c.head.value);
6
9
  }
7
10
 
8
11
  const TYPST_LEFT_PARENTHESIS: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '(');
@@ -91,65 +94,83 @@ export class TypstWriter {
91
94
  }
92
95
 
93
96
  // Serialize a tree of TypstNode into a list of TypstToken
94
- public serialize(node: TypstNode) {
95
- switch (node.type) {
96
- case 'none':
97
- this.queue.push(new TypstToken(TypstTokenType.NONE, '#none'));
98
- break;
99
- case 'atom': {
100
- if (node.content === ',' && this.insideFunctionDepth > 0) {
101
- this.queue.push(new TypstToken(TypstTokenType.SYMBOL, 'comma'));
97
+ public serialize(abstractNode: TypstNode) {
98
+ switch (abstractNode.type) {
99
+ case 'terminal': {
100
+ const node = abstractNode as TypstTerminal;
101
+ if (node.head.type === TypstTokenType.ELEMENT) {
102
+ if (node.head.value === ',' && this.insideFunctionDepth > 0) {
103
+ this.queue.push(new TypstToken(TypstTokenType.SYMBOL, 'comma'));
104
+ } else {
105
+ this.queue.push(node.head);
106
+ }
107
+ break;
108
+ } else if (node.head.type === TypstTokenType.SYMBOL) {
109
+ let symbol_name = node.head.value;
110
+ if(this.preferShorthands) {
111
+ if (shorthandMap.has(symbol_name)) {
112
+ symbol_name = shorthandMap.get(symbol_name)!;
113
+ }
114
+ }
115
+ if (this.inftyToOo && symbol_name === 'infinity') {
116
+ symbol_name = 'oo';
117
+ }
118
+ this.queue.push(new TypstToken(TypstTokenType.SYMBOL, symbol_name));
119
+ break;
120
+ } else if (node.head.type === TypstTokenType.SPACE || node.head.type === TypstTokenType.NEWLINE) {
121
+ for (const c of node.head.value) {
122
+ if (c === ' ') {
123
+ if (this.keepSpaces) {
124
+ this.queue.push(new TypstToken(TypstTokenType.SPACE, c));
125
+ }
126
+ } else if (c === '\n') {
127
+ this.queue.push(new TypstToken(TypstTokenType.SYMBOL, c));
128
+ } else {
129
+ throw new TypstWriterError(`Unexpected whitespace character: ${c}`, node);
130
+ }
131
+ }
132
+ break;
102
133
  } else {
103
- this.queue.push(new TypstToken(TypstTokenType.ELEMENT, node.content));
134
+ this.queue.push(node.head);
135
+ break;
104
136
  }
105
- break;
106
137
  }
107
- case 'symbol': {
108
- let content = node.content;
109
- if(this.preferShorthands) {
110
- if (shorthandMap.has(content)) {
111
- content = shorthandMap.get(content)!;
112
- }
113
- }
114
- if (this.inftyToOo && content === 'infinity') {
115
- content = 'oo';
138
+ case 'group': {
139
+ const node = abstractNode as TypstGroup;
140
+ for (const item of node.args!) {
141
+ this.serialize(item);
116
142
  }
117
- this.queue.push(new TypstToken(TypstTokenType.SYMBOL, content));
118
143
  break;
119
144
  }
120
- case 'literal':
121
- this.queue.push(new TypstToken(TypstTokenType.LITERAL, node.content));
122
- break;
123
- case 'text':
124
- this.queue.push(new TypstToken(TypstTokenType.TEXT, node.content));
125
- break;
126
- case 'comment':
127
- this.queue.push(new TypstToken(TypstTokenType.COMMENT, node.content));
128
- break;
129
- case 'whitespace':
130
- for (const c of node.content) {
131
- if (c === ' ') {
132
- if (this.keepSpaces) {
133
- this.queue.push(new TypstToken(TypstTokenType.SPACE, c));
134
- }
135
- } else if (c === '\n') {
136
- this.queue.push(new TypstToken(TypstTokenType.SYMBOL, c));
137
- } else {
138
- throw new TypstWriterError(`Unexpected whitespace character: ${c}`, node);
139
- }
145
+ case 'leftright': {
146
+ const node = abstractNode as TypstLeftright;
147
+ const LR = new TypstToken(TypstTokenType.SYMBOL, 'lr');
148
+ const {left, right} = node;
149
+ if (node.head.eq(LR)) {
150
+ this.queue.push(LR);
151
+ this.queue.push(TYPST_LEFT_PARENTHESIS);
152
+ }
153
+ if (left) {
154
+ this.queue.push(left);
140
155
  }
141
- break;
142
- case 'group':
143
156
  for (const item of node.args!) {
144
157
  this.serialize(item);
145
158
  }
159
+ if (right) {
160
+ this.queue.push(right);
161
+ }
162
+ if (node.head.eq(LR)) {
163
+ this.queue.push(TYPST_RIGHT_PARENTHESIS);
164
+ }
146
165
  break;
166
+ }
147
167
  case 'supsub': {
148
- let { base, sup, sub } = node.data as TypstSupsubData;
168
+ const node = abstractNode as TypstSupsub;
169
+ let { base, sup, sub } = node;
149
170
  this.appendWithBracketsIfNeeded(base);
150
171
 
151
172
  let trailing_space_needed = false;
152
- const has_prime = (sup && sup.type === 'atom' && sup.content === '\'');
173
+ const has_prime = (sup && sup.head.eq(new TypstToken(TypstTokenType.ELEMENT, "'")));
153
174
  if (has_prime) {
154
175
  // Put prime symbol before '_'. Because $y_1'$ is not displayed properly in Typst (so far)
155
176
  // e.g.
@@ -172,11 +193,10 @@ export class TypstWriter {
172
193
  break;
173
194
  }
174
195
  case 'funcCall': {
175
- const func_symbol: TypstToken = new TypstToken(TypstTokenType.SYMBOL, node.content);
196
+ const node = abstractNode as TypstFuncCall;
197
+ const func_symbol: TypstToken = node.head;
176
198
  this.queue.push(func_symbol);
177
- if (node.content !== 'lr') {
178
- this.insideFunctionDepth++;
179
- }
199
+ this.insideFunctionDepth++;
180
200
  this.queue.push(TYPST_LEFT_PARENTHESIS);
181
201
  for (let i = 0; i < node.args!.length; i++) {
182
202
  this.serialize(node.args![i]);
@@ -190,12 +210,11 @@ export class TypstWriter {
190
210
  }
191
211
  }
192
212
  this.queue.push(TYPST_RIGHT_PARENTHESIS);
193
- if (node.content !== 'lr') {
194
- this.insideFunctionDepth--;
195
- }
213
+ this.insideFunctionDepth--;
196
214
  break;
197
215
  }
198
216
  case 'fraction': {
217
+ const node = abstractNode as TypstFraction;
199
218
  const [numerator, denominator] = node.args!;
200
219
  const pos = this.queue.length;
201
220
  const no_wrap = this.appendWithBracketsIfNeeded(numerator);
@@ -212,7 +231,8 @@ export class TypstWriter {
212
231
  break;
213
232
  }
214
233
  case 'align': {
215
- const matrix = node.data as TypstNode[][];
234
+ const node = abstractNode as TypstAlign;
235
+ const matrix = node.matrix;
216
236
  matrix.forEach((row, i) => {
217
237
  row.forEach((cell, j) => {
218
238
  if (j > 0) {
@@ -227,7 +247,8 @@ export class TypstWriter {
227
247
  break;
228
248
  }
229
249
  case 'matrix': {
230
- const matrix = node.data as TypstNode[][];
250
+ const node = abstractNode as TypstMatrix;
251
+ const matrix = node.matrix;
231
252
  this.queue.push(new TypstToken(TypstTokenType.SYMBOL, 'mat'));
232
253
  this.insideFunctionDepth++;
233
254
  this.queue.push(TYPST_LEFT_PARENTHESIS);
@@ -262,7 +283,8 @@ export class TypstWriter {
262
283
  break;
263
284
  }
264
285
  case 'cases': {
265
- const cases = node.data as TypstNode[][];
286
+ const node = abstractNode as TypstCases;
287
+ const cases = node.matrix;
266
288
  this.queue.push(new TypstToken(TypstTokenType.SYMBOL, 'cases'));
267
289
  this.insideFunctionDepth++;
268
290
  this.queue.push(TYPST_LEFT_PARENTHESIS);
@@ -287,16 +309,8 @@ export class TypstWriter {
287
309
  this.insideFunctionDepth--;
288
310
  break;
289
311
  }
290
- case 'unknown': {
291
- if (this.nonStrict) {
292
- this.queue.push(new TypstToken(TypstTokenType.SYMBOL, node.content));
293
- } else {
294
- throw new TypstWriterError(`Unknown macro: ${node.content}`, node);
295
- }
296
- break;
297
- }
298
312
  default:
299
- throw new TypstWriterError(`Unimplemented node type to append: ${node.type}`, node);
313
+ throw new TypstWriterError(`Unimplemented node type to append: ${abstractNode.type}`, abstractNode);
300
314
  }
301
315
  }
302
316
 
package/src/util.ts CHANGED
@@ -7,7 +7,7 @@ export function isdigit(char: string): boolean {
7
7
  return '0123456789'.includes(char);
8
8
  }
9
9
 
10
- export function assert(condition: boolean, message: string = ''): void {
10
+ export function assert(condition: boolean, message: string = 'Assertion failed.'): void {
11
11
  if (!condition) {
12
12
  throw new Error(message);
13
13
  }
package/dist/types.d.ts DELETED
@@ -1,109 +0,0 @@
1
- export declare enum TexTokenType {
2
- ELEMENT = 0,
3
- COMMAND = 1,
4
- LITERAL = 2,
5
- COMMENT = 3,
6
- SPACE = 4,
7
- NEWLINE = 5,
8
- CONTROL = 6,
9
- UNKNOWN = 7
10
- }
11
- export declare class TexToken {
12
- type: TexTokenType;
13
- value: string;
14
- constructor(type: TexTokenType, value: string);
15
- eq(token: TexToken): boolean;
16
- toString(): string;
17
- }
18
- export interface TexSupsubData {
19
- base: TexNode;
20
- sup?: TexNode;
21
- sub?: TexNode;
22
- }
23
- export type TexSqrtData = TexNode;
24
- export type TexArrayData = TexNode[][];
25
- /**
26
- * element: 0-9, a-z, A-Z, punctuations such as +-/*,:; etc.
27
- * symbol: LaTeX macro with no parameter. e.g. \sin \cos \int \sum
28
- * unaryFunc: LaTeX macro with 1 parameter. e.g. \sqrt{3} \log{x} \exp{x}
29
- * binaryFunc: LaTeX macro with 2 parameters. e.g. \frac{1}{2}
30
- * text: text enclosed by braces. e.g. \text{hello world}
31
- * empty: special type when something is empty. e.g. the base of _{a} or ^{a}
32
- * whitespace: space, tab, newline
33
- */
34
- type TexNodeType = 'element' | 'text' | 'literal' | 'comment' | 'whitespace' | 'control' | 'ordgroup' | 'supsub' | 'unaryFunc' | 'binaryFunc' | 'leftright' | 'beginend' | 'symbol' | 'empty' | 'unknownMacro';
35
- export declare class TexNode {
36
- type: TexNodeType;
37
- content: string;
38
- args?: TexNode[];
39
- data?: TexSqrtData | TexSupsubData | TexArrayData;
40
- constructor(type: TexNodeType, content: string, args?: TexNode[], data?: TexSqrtData | TexSupsubData | TexArrayData);
41
- eq(other: TexNode): boolean;
42
- serialize(): TexToken[];
43
- }
44
- export declare enum TypstTokenType {
45
- NONE = 0,
46
- SYMBOL = 1,
47
- ELEMENT = 2,
48
- LITERAL = 3,
49
- TEXT = 4,
50
- COMMENT = 5,
51
- SPACE = 6,
52
- CONTROL = 7,
53
- NEWLINE = 8
54
- }
55
- export declare class TypstToken {
56
- type: TypstTokenType;
57
- value: string;
58
- constructor(type: TypstTokenType, content: string);
59
- eq(other: TypstToken): boolean;
60
- isOneOf(tokens: TypstToken[]): boolean;
61
- toNode(): TypstNode;
62
- toString(): string;
63
- }
64
- export interface TypstSupsubData {
65
- base: TypstNode;
66
- sup?: TypstNode;
67
- sub?: TypstNode;
68
- }
69
- export type TypstArrayData = TypstNode[][];
70
- export interface TypstLrData {
71
- leftDelim: string | null;
72
- rightDelim: string | null;
73
- }
74
- type TypstNodeType = 'atom' | 'symbol' | 'text' | 'literal' | 'control' | 'comment' | 'whitespace' | 'none' | 'group' | 'supsub' | 'funcCall' | 'fraction' | 'align' | 'matrix' | 'cases' | 'unknown';
75
- export type TypstNamedParams = {
76
- [key: string]: TypstNode;
77
- };
78
- export declare class TypstNode {
79
- type: TypstNodeType;
80
- content: string;
81
- args?: TypstNode[];
82
- data?: TypstSupsubData | TypstArrayData | TypstLrData;
83
- options?: TypstNamedParams;
84
- constructor(type: TypstNodeType, content: string, args?: TypstNode[], data?: TypstSupsubData | TypstArrayData | TypstLrData);
85
- setOptions(options: TypstNamedParams): void;
86
- eq(other: TypstNode): boolean;
87
- isOverHigh(): boolean;
88
- toString(): string;
89
- }
90
- export declare const TYPST_NONE: TypstNode;
91
- /**
92
- * ATTENTION:
93
- * Don't use any options except those explicitly documented in
94
- * https://github.com/qwinsi/tex2typst/blob/main/docs/api-reference.md
95
- * Any undocumented options may be not working at present or break in the future!
96
- */
97
- export interface Tex2TypstOptions {
98
- nonStrict?: boolean; /** default is true */
99
- preferShorthands?: boolean; /** default is true */
100
- keepSpaces?: boolean; /** default is false */
101
- fracToSlash?: boolean; /** default is true */
102
- inftyToOo?: boolean; /** default is false */
103
- optimize?: boolean; /** default is true */
104
- nonAsciiWrapper?: string; /** default is "" */
105
- customTexMacros?: {
106
- [key: string]: string;
107
- };
108
- }
109
- export {};