tex2typst 0.0.19 → 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 +3 -3
- package/dist/index.js +77 -3
- package/dist/tex2typst.min.js +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/parser.ts +79 -1
- package/src/types.ts +1 -1
- package/src/writer.ts +23 -2
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
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/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
|
|
@@ -210,7 +212,12 @@ export class TypstWriter {
|
|
|
210
212
|
this.queue.push({ type: 'symbol', content: text});
|
|
211
213
|
} else if (text.startsWith('SyMb01-')) {
|
|
212
214
|
// special hacks made in parseTex()
|
|
213
|
-
|
|
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});
|
|
214
221
|
} else {
|
|
215
222
|
this.queue.push({ type: 'symbol', content: 'op' });
|
|
216
223
|
this.queue.push({ type: 'atom', content: '('});
|
|
@@ -249,8 +256,12 @@ export class TypstWriter {
|
|
|
249
256
|
row.forEach((cell, j) => {
|
|
250
257
|
// There is a leading & in row
|
|
251
258
|
if (cell.type === 'ordgroup' && cell.args!.length === 0) {
|
|
259
|
+
this.queue.push({ type: 'atom', content: ',' });
|
|
252
260
|
return;
|
|
253
261
|
}
|
|
262
|
+
// if (j == 0 && cell.type === 'newline' && cell.content === '\n') {
|
|
263
|
+
// return;
|
|
264
|
+
// }
|
|
254
265
|
this.append(cell);
|
|
255
266
|
// cell.args!.forEach((n) => this.append(n));
|
|
256
267
|
if (j < row.length - 1) {
|
|
@@ -270,6 +281,8 @@ export class TypstWriter {
|
|
|
270
281
|
} else {
|
|
271
282
|
throw new TypstWriterError(`Unknown macro: ${node.content}`, node);
|
|
272
283
|
}
|
|
284
|
+
} else if (node.type === 'comment') {
|
|
285
|
+
this.queue.push({ type: 'comment', content: node.content });
|
|
273
286
|
} else {
|
|
274
287
|
throw new TypstWriterError(`Unimplemented node type to append: ${node.type}`, node);
|
|
275
288
|
}
|
|
@@ -292,6 +305,12 @@ export class TypstWriter {
|
|
|
292
305
|
this.needSpaceAfterSingleItemScript = true;
|
|
293
306
|
str = '';
|
|
294
307
|
break;
|
|
308
|
+
case 'comment':
|
|
309
|
+
str = `//${node.content}`;
|
|
310
|
+
break;
|
|
311
|
+
case 'newline':
|
|
312
|
+
str = '\n';
|
|
313
|
+
break;
|
|
295
314
|
default:
|
|
296
315
|
throw new TypstWriterError(`Unexpected node type to stringify: ${node.type}`, node)
|
|
297
316
|
}
|
|
@@ -350,7 +369,9 @@ function convertToken(token: string): string {
|
|
|
350
369
|
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
351
370
|
return token;
|
|
352
371
|
} else if (token === '\\\\') {
|
|
353
|
-
return '
|
|
372
|
+
return '\\';
|
|
373
|
+
} else if (token == '/') {
|
|
374
|
+
return '\\/';
|
|
354
375
|
} else if (['\\$', '\\#', '\\&', '\\_'].includes(token)) {
|
|
355
376
|
return token;
|
|
356
377
|
} else if (token.startsWith('\\')) {
|