tex2typst 0.3.23 → 0.3.25
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/convert.d.ts +9 -3
- package/dist/generic.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2278 -2145
- package/dist/tex-parser.d.ts +1 -1
- package/dist/tex-tokenizer.d.ts +1 -1
- package/dist/tex-types.d.ts +107 -0
- package/dist/tex-writer.d.ts +1 -2
- package/dist/tex2typst.min.js +13 -13
- package/dist/typst-parser.d.ts +6 -4
- package/dist/typst-tokenizer.d.ts +1 -1
- package/dist/typst-types.d.ts +97 -0
- package/dist/typst-writer.d.ts +4 -2
- package/package.json +1 -1
- package/src/convert.ts +538 -446
- package/src/generic.ts +28 -2
- package/src/index.ts +1 -1
- package/src/map.ts +5 -0
- package/src/tex-parser.ts +49 -63
- package/src/tex-tokenizer.ts +4 -3
- package/src/tex-types.ts +369 -0
- package/src/tex-writer.ts +3 -51
- package/src/typst-parser.ts +83 -65
- package/src/typst-tokenizer.ts +86 -85
- package/src/typst-types.ts +229 -0
- package/src/typst-writer.ts +143 -129
- package/src/util.ts +1 -1
- package/dist/types.d.ts +0 -109
- package/src/types.ts +0 -414
package/src/generic.ts
CHANGED
|
@@ -2,25 +2,48 @@ interface IEquatable {
|
|
|
2
2
|
eq(other: IEquatable): boolean;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
export function array_equal<T extends IEquatable>(a: T[], b: T[]): boolean {
|
|
6
|
+
/*
|
|
7
|
+
if (a.length !== b.length) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
for (let i = 0; i < a.length; i++) {
|
|
11
|
+
if (!a[i].eq(b[i])) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
*/
|
|
17
|
+
return a.length === b.length && a.every((x, i) => x.eq(b[i]));
|
|
18
|
+
}
|
|
5
19
|
|
|
6
20
|
export function array_find<T extends IEquatable>(array: T[], item: T, start: number = 0): number {
|
|
21
|
+
/*
|
|
7
22
|
for (let i = start; i < array.length; i++) {
|
|
8
23
|
if (array[i].eq(item)) {
|
|
9
24
|
return i;
|
|
10
25
|
}
|
|
11
26
|
}
|
|
12
27
|
return -1;
|
|
28
|
+
*/
|
|
29
|
+
const index = array.slice(start).findIndex((x) => x.eq(item));
|
|
30
|
+
return index === -1 ? -1 : index + start;
|
|
13
31
|
}
|
|
14
32
|
|
|
15
33
|
export function array_includes<T extends IEquatable>(array: T[], item: T): boolean {
|
|
16
|
-
|
|
17
|
-
|
|
34
|
+
/*
|
|
35
|
+
for (const x of array) {
|
|
36
|
+
if (x.eq(item)) {
|
|
18
37
|
return true;
|
|
19
38
|
}
|
|
20
39
|
}
|
|
21
40
|
return false;
|
|
41
|
+
*/
|
|
42
|
+
return array.some((x) => x.eq(item));
|
|
22
43
|
}
|
|
23
44
|
|
|
45
|
+
// e.g. input array=['a', 'b', '+', 'c', '+', 'd', 'e'], sep = '+'
|
|
46
|
+
// return [['a', 'b'], ['c'], ['d', 'e']]
|
|
24
47
|
export function array_split<T extends IEquatable>(array: T[], sep: T): T[][] {
|
|
25
48
|
const res: T[][] = [];
|
|
26
49
|
let current_slice: T[] = [];
|
|
@@ -39,6 +62,7 @@ export function array_split<T extends IEquatable>(array: T[], sep: T): T[][] {
|
|
|
39
62
|
// e.g. input array=['a', 'b', 'c'], sep = '+'
|
|
40
63
|
// return ['a','+', 'b', '+','c']
|
|
41
64
|
export function array_intersperse<T>(array: T[], sep: T): T[] {
|
|
65
|
+
/*
|
|
42
66
|
const res: T[] = [];
|
|
43
67
|
for (let i = 0; i < array.length; i++) {
|
|
44
68
|
res.push(array[i]);
|
|
@@ -47,4 +71,6 @@ export function array_intersperse<T>(array: T[], sep: T): T[] {
|
|
|
47
71
|
}
|
|
48
72
|
}
|
|
49
73
|
return res;
|
|
74
|
+
*/
|
|
75
|
+
return array.flatMap((x, i) => i !== array.length - 1? [x, sep]: [x]);
|
|
50
76
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseTex } from "./tex-parser";
|
|
2
|
-
import type { Tex2TypstOptions } from "./types";
|
|
2
|
+
import type { Tex2TypstOptions } from "./tex-types";
|
|
3
3
|
import { TypstWriter, type TypstWriterOptions } from "./typst-writer";
|
|
4
4
|
import { convert_tex_node_to_typst, convert_typst_node_to_tex } from "./convert";
|
|
5
5
|
import { symbolMap } from "./map";
|
package/src/map.ts
CHANGED
|
@@ -5,6 +5,7 @@ const symbolMap = new Map<string, string>([
|
|
|
5
5
|
['|', 'bar.v.double'],
|
|
6
6
|
[',', 'thin'],
|
|
7
7
|
[':', 'med'],
|
|
8
|
+
[' ', 'med'],
|
|
8
9
|
[';', 'thick'],
|
|
9
10
|
|
|
10
11
|
/* textual operators */
|
|
@@ -21,6 +22,10 @@ const symbolMap = new Map<string, string>([
|
|
|
21
22
|
['Xi', 'Xi'],
|
|
22
23
|
['Upsilon', 'Upsilon'],
|
|
23
24
|
['lim', 'lim'],
|
|
25
|
+
['binom', 'binom'],
|
|
26
|
+
['tilde', 'tilde'],
|
|
27
|
+
['hat', 'hat'],
|
|
28
|
+
['sqrt', 'sqrt'],
|
|
24
29
|
|
|
25
30
|
['nonumber', ''],
|
|
26
31
|
['vec', 'arrow'],
|
package/src/tex-parser.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { TexNode, TexSupsubData, TexToken, TexTokenType } from "./types";
|
|
1
|
+
import { TexBeginEnd, TexFuncCall, TexLeftRight, TexNode, TexGroup, TexSupSub, TexSupsubData, TexText, TexToken, TexTokenType } from "./tex-types";
|
|
3
2
|
import { assert } from "./util";
|
|
4
3
|
import { array_find } from "./generic";
|
|
5
4
|
import { TEX_BINARY_COMMANDS, TEX_UNARY_COMMANDS, tokenize_tex } from "./tex-tokenizer";
|
|
@@ -11,7 +10,7 @@ const IGNORED_COMMANDS = [
|
|
|
11
10
|
'Biggl', 'Biggr',
|
|
12
11
|
];
|
|
13
12
|
|
|
14
|
-
const EMPTY_NODE: TexNode =
|
|
13
|
+
const EMPTY_NODE: TexNode = TexToken.EMPTY.toNode();
|
|
15
14
|
|
|
16
15
|
function get_command_param_num(command: string): number {
|
|
17
16
|
if (TEX_UNARY_COMMANDS.includes(command)) {
|
|
@@ -120,7 +119,8 @@ export class LatexParser {
|
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
parse(tokens: TexToken[]): TexNode {
|
|
123
|
-
const
|
|
122
|
+
const token_displaystyle = new TexToken(TexTokenType.COMMAND, '\\displaystyle');
|
|
123
|
+
const idx = array_find(tokens, token_displaystyle);
|
|
124
124
|
if (idx === -1) {
|
|
125
125
|
// no \displaystyle, normal execution path
|
|
126
126
|
const [tree, _] = this.parseGroup(tokens, 0, tokens.length);
|
|
@@ -128,13 +128,13 @@ export class LatexParser {
|
|
|
128
128
|
} else if (idx === 0) {
|
|
129
129
|
// \displaystyle at the beginning. Wrap the whole thing in \displaystyle
|
|
130
130
|
const [tree, _] = this.parseGroup(tokens, 1, tokens.length);
|
|
131
|
-
return new
|
|
131
|
+
return new TexFuncCall(token_displaystyle, [tree]);
|
|
132
132
|
} else {
|
|
133
133
|
// \displaystyle somewhere in the middle. Split the expression to two parts
|
|
134
134
|
const [tree1, _1] = this.parseGroup(tokens, 0, idx);
|
|
135
135
|
const [tree2, _2] = this.parseGroup(tokens, idx + 1, tokens.length);
|
|
136
|
-
const display = new
|
|
137
|
-
return new
|
|
136
|
+
const display = new TexFuncCall(token_displaystyle, [tree2]);
|
|
137
|
+
return new TexGroup([tree1, display]);
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -144,15 +144,15 @@ export class LatexParser {
|
|
|
144
144
|
while (pos < end) {
|
|
145
145
|
const [res, newPos] = this.parseNextExpr(tokens, pos);
|
|
146
146
|
pos = newPos;
|
|
147
|
-
if(res.type ===
|
|
148
|
-
if (!this.space_sensitive && res.
|
|
147
|
+
if(res.head.type === TexTokenType.SPACE || res.head.type === TexTokenType.NEWLINE) {
|
|
148
|
+
if (!this.space_sensitive && res.head.value.replace(/ /g, '').length === 0) {
|
|
149
149
|
continue;
|
|
150
150
|
}
|
|
151
|
-
if (!this.newline_sensitive && res.
|
|
151
|
+
if (!this.newline_sensitive && res.head.value === '\n') {
|
|
152
152
|
continue;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
if (res.
|
|
155
|
+
if (res.head.eq(new TexToken(TexTokenType.CONTROL, '&'))) {
|
|
156
156
|
throw new LatexParserError('Unexpected & outside of an alignment');
|
|
157
157
|
}
|
|
158
158
|
results.push(res);
|
|
@@ -162,7 +162,7 @@ export class LatexParser {
|
|
|
162
162
|
if (results.length === 1) {
|
|
163
163
|
node = results[0];
|
|
164
164
|
} else {
|
|
165
|
-
node = new
|
|
165
|
+
node = new TexGroup(results);
|
|
166
166
|
}
|
|
167
167
|
return [node, end + 1];
|
|
168
168
|
}
|
|
@@ -199,25 +199,23 @@ export class LatexParser {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
if (sub !== null || sup !== null || num_prime > 0) {
|
|
202
|
-
const res: TexSupsubData = { base };
|
|
202
|
+
const res: TexSupsubData = { base, sup: null, sub: null };
|
|
203
203
|
if (sub) {
|
|
204
204
|
res.sub = sub;
|
|
205
205
|
}
|
|
206
206
|
if (num_prime > 0) {
|
|
207
|
-
|
|
207
|
+
const items: TexNode[] = [];
|
|
208
208
|
for (let i = 0; i < num_prime; i++) {
|
|
209
|
-
|
|
209
|
+
items.push(new TexToken(TexTokenType.ELEMENT, "'").toNode());
|
|
210
210
|
}
|
|
211
211
|
if (sup) {
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
if (res.sup.args!.length === 1) {
|
|
215
|
-
res.sup = res.sup.args![0];
|
|
212
|
+
items.push(sup);
|
|
216
213
|
}
|
|
214
|
+
res.sup = items.length === 1 ? items[0] : new TexGroup(items);
|
|
217
215
|
} else if (sup) {
|
|
218
216
|
res.sup = sup;
|
|
219
217
|
}
|
|
220
|
-
return [new
|
|
218
|
+
return [new TexSupSub(res), pos];
|
|
221
219
|
} else {
|
|
222
220
|
return [base, pos];
|
|
223
221
|
}
|
|
@@ -230,14 +228,11 @@ export class LatexParser {
|
|
|
230
228
|
const firstToken = tokens[start];
|
|
231
229
|
switch (firstToken.type) {
|
|
232
230
|
case TexTokenType.ELEMENT:
|
|
233
|
-
return [new TexNode('element', firstToken.value), start + 1];
|
|
234
231
|
case TexTokenType.LITERAL:
|
|
235
|
-
return [new TexNode('literal', firstToken.value), start + 1];
|
|
236
232
|
case TexTokenType.COMMENT:
|
|
237
|
-
return [new TexNode('comment', firstToken.value), start + 1];
|
|
238
233
|
case TexTokenType.SPACE:
|
|
239
234
|
case TexTokenType.NEWLINE:
|
|
240
|
-
return [
|
|
235
|
+
return [firstToken.toNode(), start + 1];
|
|
241
236
|
case TexTokenType.COMMAND:
|
|
242
237
|
const commandName = firstToken.value.slice(1);
|
|
243
238
|
if (IGNORED_COMMANDS.includes(commandName)) {
|
|
@@ -266,14 +261,14 @@ export class LatexParser {
|
|
|
266
261
|
case '\\,':
|
|
267
262
|
case '\\:':
|
|
268
263
|
case '\\;':
|
|
269
|
-
return [
|
|
264
|
+
return [firstToken.toNode(), start + 1];
|
|
270
265
|
case '\\ ':
|
|
271
|
-
return [
|
|
266
|
+
return [firstToken.toNode(), start + 1];
|
|
272
267
|
case '_':
|
|
273
268
|
case '^':
|
|
274
269
|
return [ EMPTY_NODE, start];
|
|
275
270
|
case '&':
|
|
276
|
-
return [
|
|
271
|
+
return [firstToken.toNode(), start + 1];
|
|
277
272
|
default:
|
|
278
273
|
throw new LatexParserError('Unknown control sequence');
|
|
279
274
|
}
|
|
@@ -285,7 +280,8 @@ export class LatexParser {
|
|
|
285
280
|
parseCommandExpr(tokens: TexToken[], start: number): ParseResult {
|
|
286
281
|
assert(tokens[start].type === TexTokenType.COMMAND);
|
|
287
282
|
|
|
288
|
-
const
|
|
283
|
+
const command_token = tokens[start];
|
|
284
|
+
const command = command_token.value; // command name starts with a \
|
|
289
285
|
|
|
290
286
|
let pos = start + 1;
|
|
291
287
|
|
|
@@ -297,10 +293,7 @@ export class LatexParser {
|
|
|
297
293
|
const paramNum = get_command_param_num(command.slice(1));
|
|
298
294
|
switch (paramNum) {
|
|
299
295
|
case 0:
|
|
300
|
-
|
|
301
|
-
return [new TexNode('unknownMacro', command), pos];
|
|
302
|
-
}
|
|
303
|
-
return [new TexNode('symbol', command), pos];
|
|
296
|
+
return [command_token.toNode(), pos];
|
|
304
297
|
case 1: {
|
|
305
298
|
// TODO: JavaScript gives undefined instead of throwing an error when accessing an index out of bounds,
|
|
306
299
|
// so index checking like this should be everywhere. This is rough.
|
|
@@ -315,7 +308,7 @@ export class LatexParser {
|
|
|
315
308
|
}
|
|
316
309
|
const [exponent, _] = this.parseGroup(tokens, posLeftSquareBracket + 1, posRightSquareBracket);
|
|
317
310
|
const [arg1, newPos] = this.parseNextArg(tokens, posRightSquareBracket + 1);
|
|
318
|
-
return [new
|
|
311
|
+
return [new TexFuncCall(command_token, [arg1], exponent), newPos];
|
|
319
312
|
} else if (command === '\\text') {
|
|
320
313
|
if (pos + 2 >= tokens.length) {
|
|
321
314
|
throw new LatexParserError('Expecting content for \\text command');
|
|
@@ -323,16 +316,16 @@ export class LatexParser {
|
|
|
323
316
|
assert(tokens[pos].eq(LEFT_CURLY_BRACKET));
|
|
324
317
|
assert(tokens[pos + 1].type === TexTokenType.LITERAL);
|
|
325
318
|
assert(tokens[pos + 2].eq(RIGHT_CURLY_BRACKET));
|
|
326
|
-
const
|
|
327
|
-
return [new
|
|
319
|
+
const literal = tokens[pos + 1];
|
|
320
|
+
return [new TexText(literal), pos + 3];
|
|
328
321
|
}
|
|
329
322
|
let [arg1, newPos] = this.parseNextArg(tokens, pos);
|
|
330
|
-
return [new
|
|
323
|
+
return [new TexFuncCall(command_token, [arg1]), newPos];
|
|
331
324
|
}
|
|
332
325
|
case 2: {
|
|
333
326
|
const [arg1, pos1] = this.parseNextArg(tokens, pos);
|
|
334
327
|
const [arg2, pos2] = this.parseNextArg(tokens, pos1);
|
|
335
|
-
return [new
|
|
328
|
+
return [new TexFuncCall(command_token, [arg1, arg2]), pos2];
|
|
336
329
|
}
|
|
337
330
|
default:
|
|
338
331
|
throw new Error( 'Invalid number of parameters');
|
|
@@ -342,9 +335,9 @@ export class LatexParser {
|
|
|
342
335
|
/*
|
|
343
336
|
Extract a non-space argument from the token stream.
|
|
344
337
|
So that `\frac{12} 3` is parsed as
|
|
345
|
-
|
|
338
|
+
TypstFuncCall{ head: '\frac', args: [ELEMENT_12, ELEMENT_3] }
|
|
346
339
|
rather than
|
|
347
|
-
|
|
340
|
+
TypstFuncCall{ head: '\frac', args: [ELEMENT_12, SPACE] }, ELEMENT_3
|
|
348
341
|
*/
|
|
349
342
|
parseNextArg(tokens: TexToken[], start: number): ParseResult {
|
|
350
343
|
let pos = start;
|
|
@@ -352,7 +345,7 @@ export class LatexParser {
|
|
|
352
345
|
while (pos < tokens.length) {
|
|
353
346
|
let node: TexNode;
|
|
354
347
|
[node, pos] = this.parseNextExprWithoutSupSub(tokens, pos);
|
|
355
|
-
if (node.type
|
|
348
|
+
if (!(node.head.type === TexTokenType.SPACE || node.head.type === TexTokenType.NEWLINE)) {
|
|
356
349
|
arg = node;
|
|
357
350
|
break;
|
|
358
351
|
}
|
|
@@ -398,12 +391,9 @@ export class LatexParser {
|
|
|
398
391
|
pos++;
|
|
399
392
|
|
|
400
393
|
const [body, _] = this.parseGroup(tokens, exprInsideStart, exprInsideEnd);
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
new TexNode('element', rightDelimiter.value)
|
|
405
|
-
]
|
|
406
|
-
const res = new TexNode('leftright', '', args);
|
|
394
|
+
const left = leftDelimiter.value === '.'? null: leftDelimiter;
|
|
395
|
+
const right = rightDelimiter.value === '.'? null: rightDelimiter;
|
|
396
|
+
const res = new TexLeftRight({body: body, left: left, right: right});
|
|
407
397
|
return [res, pos];
|
|
408
398
|
}
|
|
409
399
|
|
|
@@ -418,14 +408,10 @@ export class LatexParser {
|
|
|
418
408
|
pos += 3;
|
|
419
409
|
|
|
420
410
|
|
|
421
|
-
|
|
411
|
+
let data: TexNode | null = null;
|
|
422
412
|
if(['array', 'subarray'].includes(envName)) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
const [arg, newPos] = this.parseNextArg(tokens, pos);
|
|
427
|
-
args.push(arg);
|
|
428
|
-
pos = newPos;
|
|
413
|
+
pos += eat_whitespaces(tokens, pos).length;
|
|
414
|
+
[data, pos] = this.parseNextArg(tokens, pos);
|
|
429
415
|
}
|
|
430
416
|
|
|
431
417
|
pos += eat_whitespaces(tokens, pos).length; // ignore whitespaces and '\n' after \begin{envName}
|
|
@@ -454,7 +440,7 @@ export class LatexParser {
|
|
|
454
440
|
exprInside.pop();
|
|
455
441
|
}
|
|
456
442
|
const body = this.parseAligned(exprInside);
|
|
457
|
-
const res = new
|
|
443
|
+
const res = new TexBeginEnd(new TexToken(TexTokenType.LITERAL, envName), body, data);
|
|
458
444
|
return [res, pos];
|
|
459
445
|
}
|
|
460
446
|
|
|
@@ -463,32 +449,32 @@ export class LatexParser {
|
|
|
463
449
|
const allRows: TexNode[][] = [];
|
|
464
450
|
let row: TexNode[] = [];
|
|
465
451
|
allRows.push(row);
|
|
466
|
-
let group = new
|
|
452
|
+
let group = new TexGroup([]);
|
|
467
453
|
row.push(group);
|
|
468
454
|
|
|
469
455
|
while (pos < tokens.length) {
|
|
470
456
|
const [res, newPos] = this.parseNextExpr(tokens, pos);
|
|
471
457
|
pos = newPos;
|
|
472
458
|
|
|
473
|
-
if (res.type ===
|
|
474
|
-
if (!this.space_sensitive && res.
|
|
459
|
+
if (res.head.type === TexTokenType.SPACE || res.head.type === TexTokenType.NEWLINE) {
|
|
460
|
+
if (!this.space_sensitive && res.head.value.replace(/ /g, '').length === 0) {
|
|
475
461
|
continue;
|
|
476
462
|
}
|
|
477
|
-
if (!this.newline_sensitive && res.
|
|
463
|
+
if (!this.newline_sensitive && res.head.value === '\n') {
|
|
478
464
|
continue;
|
|
479
465
|
}
|
|
480
466
|
}
|
|
481
467
|
|
|
482
|
-
if (res.
|
|
468
|
+
if (res.head.eq(new TexToken(TexTokenType.CONTROL, '\\\\'))) {
|
|
483
469
|
row = [];
|
|
484
|
-
group = new
|
|
470
|
+
group = new TexGroup([]);
|
|
485
471
|
row.push(group);
|
|
486
472
|
allRows.push(row);
|
|
487
|
-
} else if (res.
|
|
488
|
-
group = new
|
|
473
|
+
} else if (res.head.eq(new TexToken(TexTokenType.CONTROL, '&'))) {
|
|
474
|
+
group = new TexGroup([]);
|
|
489
475
|
row.push(group);
|
|
490
476
|
} else {
|
|
491
|
-
group.
|
|
477
|
+
group.items.push(res);
|
|
492
478
|
}
|
|
493
479
|
}
|
|
494
480
|
return allRows;
|
package/src/tex-tokenizer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TexToken, TexTokenType } from "./types";
|
|
1
|
+
import { TexToken, TexTokenType } from "./tex-types";
|
|
2
2
|
import { JSLex, Scanner } from "./jslex";
|
|
3
3
|
|
|
4
4
|
export const TEX_UNARY_COMMANDS = [
|
|
@@ -46,6 +46,7 @@ export const TEX_BINARY_COMMANDS = [
|
|
|
46
46
|
'tbinom',
|
|
47
47
|
'overset',
|
|
48
48
|
'underset',
|
|
49
|
+
'textcolor',
|
|
49
50
|
]
|
|
50
51
|
|
|
51
52
|
|
|
@@ -58,7 +59,7 @@ function unescape(str: string): string {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
const rules_map = new Map<string, (a: Scanner<TexToken>) => TexToken | TexToken[]>([
|
|
61
|
-
//
|
|
62
|
+
// match `\begin{array}{cc}`
|
|
62
63
|
[
|
|
63
64
|
String.raw`\\begin{(array|subarry)}{(.+?)}`, (s) => {
|
|
64
65
|
const match = s.reMatchArray()!;
|
|
@@ -74,7 +75,7 @@ const rules_map = new Map<string, (a: Scanner<TexToken>) => TexToken | TexToken[
|
|
|
74
75
|
}
|
|
75
76
|
],
|
|
76
77
|
[
|
|
77
|
-
String.raw`\\(text|operatorname|begin|end|hspace|array){(.+?)}`, (s) => {
|
|
78
|
+
String.raw`\\(text|operatorname|textcolor|begin|end|hspace|array){(.+?)}`, (s) => {
|
|
78
79
|
const match = s.reMatchArray()!;
|
|
79
80
|
return [
|
|
80
81
|
new TexToken(TexTokenType.COMMAND, '\\' + match[1]),
|