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/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.0.19",
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",
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
- let treeArray = generateParseTree(tex, options);
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
@@ -28,7 +28,7 @@ export interface TexNode {
28
28
  }
29
29
 
30
30
  export interface TypstNode {
31
- type: 'atom' | 'symbol' | 'text' | 'softSpace';
31
+ type: 'atom' | 'symbol' | 'text' | 'softSpace' | 'comment' | 'newline',
32
32
  content: string;
33
33
  args?: TypstNode[];
34
34
  }
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
- this.queue.push({ type: 'symbol', content: '\\' + text.substring(7)});
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 '\\\n';
372
+ return '\\';
373
+ } else if (token == '/') {
374
+ return '\\/';
354
375
  } else if (['\\$', '\\#', '\\&', '\\_'].includes(token)) {
355
376
  return token;
356
377
  } else if (token.startsWith('\\')) {