tex2typst 0.0.18 → 0.1.20
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 +18 -3
- package/dist/index.js +95 -9
- package/dist/tex2typst.min.js +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +2 -2
- package/src/index.ts +0 -5
- package/src/map.ts +5 -3
- package/src/parser.ts +79 -1
- package/src/tex2typst.ts +9 -0
- package/src/types.ts +1 -1
- package/src/writer.ts +36 -2
- package/tool/dist/dist/ka.js +13654 -0
- package/tool/dist/ka.js +13634 -0
- package/tsconfig.json +1 -0
package/dist/types.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface TexNode {
|
|
|
19
19
|
irregularData?: TexSqrtData | TexSupsubData | TexArrayData;
|
|
20
20
|
}
|
|
21
21
|
export interface TypstNode {
|
|
22
|
-
type: 'atom' | 'symbol' | 'text' | 'softSpace';
|
|
22
|
+
type: 'atom' | 'symbol' | 'text' | 'softSpace' | 'comment' | 'newline';
|
|
23
23
|
content: string;
|
|
24
24
|
args?: TypstNode[];
|
|
25
25
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tex2typst",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "JavaScript library for converting TeX code to Typst",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"prebuild": "rimraf dist/",
|
|
18
18
|
"build:node": "bun build --entrypoints src/index.ts --outdir ./dist --target node --external katex",
|
|
19
|
-
"build:browser": "bun build --entrypoints src/
|
|
19
|
+
"build:browser": "bun build --entrypoints src/tex2typst.ts --outdir ./dist --target browser --entry-naming [dir]/[name].min.[ext] --minify",
|
|
20
20
|
"build:types": "tsc --project ./tsconfig.json",
|
|
21
21
|
"build": "npm run build:node && npm run build:browser && npm run build:types",
|
|
22
22
|
"test": "vitest run"
|
package/src/index.ts
CHANGED
package/src/map.ts
CHANGED
|
@@ -2,9 +2,6 @@ export const symbolMap = new Map<string, string>([
|
|
|
2
2
|
['gets', 'arrow.l'],
|
|
3
3
|
['nonumber', ''],
|
|
4
4
|
['vec', 'arrow'],
|
|
5
|
-
['mathbf', 'bold'],
|
|
6
|
-
['boldsymbol', 'bold'],
|
|
7
|
-
['mathfrak', 'frak'],
|
|
8
5
|
['neq', 'eq.not'],
|
|
9
6
|
['dot', 'dot'],
|
|
10
7
|
['ddot', 'dot.double'],
|
|
@@ -23,8 +20,13 @@ export const symbolMap = new Map<string, string>([
|
|
|
23
20
|
['underline', 'underline'], // same
|
|
24
21
|
['bar', 'macron'],
|
|
25
22
|
|
|
23
|
+
['boldsymbol', 'bold'],
|
|
24
|
+
['mathbf', 'bold'],
|
|
26
25
|
['mathbb', 'bb'],
|
|
27
26
|
['mathcal', 'cal'],
|
|
27
|
+
['mathfrak', 'frak'],
|
|
28
|
+
['mathsf', 'sans'],
|
|
29
|
+
['mathtt', 'mono'],
|
|
28
30
|
|
|
29
31
|
['mathrm', 'upright'],
|
|
30
32
|
['rm', 'upright'],
|
package/src/parser.ts
CHANGED
|
@@ -228,6 +228,10 @@ export function katexNodeToTexNode(node: KatexParseNode): TexNode {
|
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
throw new KatexNodeToTexNodeError(`Unknown error type in parsed result:`, node);
|
|
231
|
+
case 'comment':
|
|
232
|
+
res.type = 'comment';
|
|
233
|
+
res.content = node.text!;
|
|
234
|
+
break;
|
|
231
235
|
default:
|
|
232
236
|
throw new KatexNodeToTexNodeError(`Unknown node type: ${node.type}`, node);
|
|
233
237
|
break;
|
|
@@ -238,6 +242,61 @@ export function katexNodeToTexNode(node: KatexParseNode): TexNode {
|
|
|
238
242
|
}
|
|
239
243
|
}
|
|
240
244
|
|
|
245
|
+
// Split tex into a list of tex strings and comments.
|
|
246
|
+
// Each item in the returned list is either a tex snippet or a comment.
|
|
247
|
+
// Each comment item is a string starting with '%'.
|
|
248
|
+
function splitTex(tex: string): string[] {
|
|
249
|
+
const lines = tex.split("\n");
|
|
250
|
+
const out_tex_list: string[] = [];
|
|
251
|
+
let current_tex = "";
|
|
252
|
+
// let inside_begin_depth = 0;
|
|
253
|
+
for (let i = 0; i < lines.length; i++) {
|
|
254
|
+
const line = lines[i];
|
|
255
|
+
// if (line.includes('\\begin{')) {
|
|
256
|
+
// inside_begin_depth += line.split('\\begin{').length - 1;
|
|
257
|
+
// }
|
|
258
|
+
|
|
259
|
+
let index = -1;
|
|
260
|
+
while (index + 1 < line.length) {
|
|
261
|
+
index = line.indexOf('%', index + 1);
|
|
262
|
+
if (index === -1) {
|
|
263
|
+
// No comment in this line
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
if (index === 0 || line[index - 1] !== '\\') {
|
|
267
|
+
// Found a comment
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (index !== -1) {
|
|
272
|
+
current_tex += line.substring(0, index);
|
|
273
|
+
const comment = line.substring(index);
|
|
274
|
+
out_tex_list.push(current_tex);
|
|
275
|
+
current_tex = "";
|
|
276
|
+
out_tex_list.push(comment);
|
|
277
|
+
} else {
|
|
278
|
+
current_tex += line;
|
|
279
|
+
}
|
|
280
|
+
if (i < lines.length - 1) {
|
|
281
|
+
const has_begin_command = line.includes('\\begin{');
|
|
282
|
+
const followed_by_end_command = lines[i + 1].includes('\\end{');
|
|
283
|
+
if(!has_begin_command && !followed_by_end_command) {
|
|
284
|
+
current_tex += "\\SyMbOlNeWlInE ";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// if (line.includes('\\end{')) {
|
|
289
|
+
// inside_begin_depth -= line.split('\\end{').length - 1;
|
|
290
|
+
// }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (current_tex.length > 0) {
|
|
294
|
+
out_tex_list.push(current_tex);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return out_tex_list;
|
|
298
|
+
}
|
|
299
|
+
|
|
241
300
|
export function parseTex(tex: string, customTexMacros: {[key: string]: string}): TexNode {
|
|
242
301
|
// displayMode=true. Otherwise, "KaTeX parse error: {align*} can be used only in display mode."
|
|
243
302
|
const macros = {
|
|
@@ -257,6 +316,7 @@ export function parseTex(tex: string, customTexMacros: {[key: string]: string}):
|
|
|
257
316
|
'\\slash': '\\operatorname{SyMb01-slash}',
|
|
258
317
|
'\\LaTeX': '\\operatorname{SyMb01-LaTeX}',
|
|
259
318
|
'\\TeX': '\\operatorname{SyMb01-TeX}',
|
|
319
|
+
'\\SyMbOlNeWlInE': '\\operatorname{SyMb01-newline}',
|
|
260
320
|
...customTexMacros
|
|
261
321
|
};
|
|
262
322
|
const options = {
|
|
@@ -265,7 +325,25 @@ export function parseTex(tex: string, customTexMacros: {[key: string]: string}):
|
|
|
265
325
|
strict: "ignore",
|
|
266
326
|
throwOnError: false
|
|
267
327
|
};
|
|
268
|
-
|
|
328
|
+
|
|
329
|
+
const tex_list = splitTex(tex);
|
|
330
|
+
|
|
331
|
+
let treeArray: KatexParseNode[] = [];
|
|
332
|
+
|
|
333
|
+
for (const tex_item of tex_list) {
|
|
334
|
+
if (tex_item.startsWith('%')) {
|
|
335
|
+
const tex_node: KatexParseNode = {
|
|
336
|
+
type: 'comment',
|
|
337
|
+
mode: 'math',
|
|
338
|
+
text: tex_item.substring(1),
|
|
339
|
+
};
|
|
340
|
+
treeArray.push(tex_node);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const trees = generateParseTree(tex_item, options);
|
|
344
|
+
treeArray = treeArray.concat(trees);
|
|
345
|
+
}
|
|
346
|
+
|
|
269
347
|
let t = {
|
|
270
348
|
type: 'ordgroup',
|
|
271
349
|
mode: 'math',
|
package/src/tex2typst.ts
ADDED
package/src/types.ts
CHANGED
package/src/writer.ts
CHANGED
|
@@ -55,6 +55,8 @@ export class TypstWriter {
|
|
|
55
55
|
no_need_space ||= /[0-9]$/.test(this.buffer) && /^[0-9]/.test(str);
|
|
56
56
|
// leading sign
|
|
57
57
|
no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
|
|
58
|
+
// new line
|
|
59
|
+
no_need_space ||= str.startsWith('\n');
|
|
58
60
|
// buffer is empty
|
|
59
61
|
no_need_space ||= this.buffer === "";
|
|
60
62
|
// other cases
|
|
@@ -172,6 +174,19 @@ export class TypstWriter {
|
|
|
172
174
|
this.queue.push({ type: 'atom', content: ')'});
|
|
173
175
|
this.insideFunctionDepth --;
|
|
174
176
|
return;
|
|
177
|
+
} else if (node.content === '\\mathbf') {
|
|
178
|
+
this.append({ type: 'symbol', content: 'upright' });
|
|
179
|
+
this.insideFunctionDepth ++;
|
|
180
|
+
this.queue.push({ type: 'atom', content: '('});
|
|
181
|
+
this.queue.push(func_symbol);
|
|
182
|
+
this.insideFunctionDepth ++;
|
|
183
|
+
this.queue.push({ type: 'atom', content: '('});
|
|
184
|
+
this.append(arg0);
|
|
185
|
+
this.queue.push({ type: 'atom', content: ')'});
|
|
186
|
+
this.insideFunctionDepth --;
|
|
187
|
+
this.queue.push({ type: 'atom', content: ')'});
|
|
188
|
+
this.insideFunctionDepth --;
|
|
189
|
+
return;
|
|
175
190
|
} else if (node.content === '\\mathbb') {
|
|
176
191
|
const body = node.args![0];
|
|
177
192
|
if (body.type === 'symbol' && /^[A-Z]$/.test(body.content)) {
|
|
@@ -197,7 +212,12 @@ export class TypstWriter {
|
|
|
197
212
|
this.queue.push({ type: 'symbol', content: text});
|
|
198
213
|
} else if (text.startsWith('SyMb01-')) {
|
|
199
214
|
// special hacks made in parseTex()
|
|
200
|
-
|
|
215
|
+
const special_symbol = text.substring(7);
|
|
216
|
+
if (special_symbol === 'newline') {
|
|
217
|
+
this.queue.push({ type: 'newline', content: '\n'});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.queue.push({ type: 'symbol', content: '\\' + special_symbol});
|
|
201
221
|
} else {
|
|
202
222
|
this.queue.push({ type: 'symbol', content: 'op' });
|
|
203
223
|
this.queue.push({ type: 'atom', content: '('});
|
|
@@ -236,8 +256,12 @@ export class TypstWriter {
|
|
|
236
256
|
row.forEach((cell, j) => {
|
|
237
257
|
// There is a leading & in row
|
|
238
258
|
if (cell.type === 'ordgroup' && cell.args!.length === 0) {
|
|
259
|
+
this.queue.push({ type: 'atom', content: ',' });
|
|
239
260
|
return;
|
|
240
261
|
}
|
|
262
|
+
// if (j == 0 && cell.type === 'newline' && cell.content === '\n') {
|
|
263
|
+
// return;
|
|
264
|
+
// }
|
|
241
265
|
this.append(cell);
|
|
242
266
|
// cell.args!.forEach((n) => this.append(n));
|
|
243
267
|
if (j < row.length - 1) {
|
|
@@ -257,6 +281,8 @@ export class TypstWriter {
|
|
|
257
281
|
} else {
|
|
258
282
|
throw new TypstWriterError(`Unknown macro: ${node.content}`, node);
|
|
259
283
|
}
|
|
284
|
+
} else if (node.type === 'comment') {
|
|
285
|
+
this.queue.push({ type: 'comment', content: node.content });
|
|
260
286
|
} else {
|
|
261
287
|
throw new TypstWriterError(`Unimplemented node type to append: ${node.type}`, node);
|
|
262
288
|
}
|
|
@@ -279,6 +305,12 @@ export class TypstWriter {
|
|
|
279
305
|
this.needSpaceAfterSingleItemScript = true;
|
|
280
306
|
str = '';
|
|
281
307
|
break;
|
|
308
|
+
case 'comment':
|
|
309
|
+
str = `//${node.content}`;
|
|
310
|
+
break;
|
|
311
|
+
case 'newline':
|
|
312
|
+
str = '\n';
|
|
313
|
+
break;
|
|
282
314
|
default:
|
|
283
315
|
throw new TypstWriterError(`Unexpected node type to stringify: ${node.type}`, node)
|
|
284
316
|
}
|
|
@@ -337,7 +369,9 @@ function convertToken(token: string): string {
|
|
|
337
369
|
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
338
370
|
return token;
|
|
339
371
|
} else if (token === '\\\\') {
|
|
340
|
-
return '
|
|
372
|
+
return '\\';
|
|
373
|
+
} else if (token == '/') {
|
|
374
|
+
return '\\/';
|
|
341
375
|
} else if (['\\$', '\\#', '\\&', '\\_'].includes(token)) {
|
|
342
376
|
return token;
|
|
343
377
|
} else if (token.startsWith('\\')) {
|