@simonklee/opentui-tex 0.2.0 → 0.3.1

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,1797 @@
1
+ // src/tex-renderable.ts
2
+ import {
3
+ BoxRenderable,
4
+ ImageRenderable,
5
+ TextRenderable,
6
+ StyledText,
7
+ createTextAttributes,
8
+ parseColor,
9
+ Yoga
10
+ } from "@opentui/core";
11
+ import stringWidth3 from "string-width";
12
+
13
+ // src/math-graphemes.ts
14
+ var graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
15
+
16
+ // src/math-layout.ts
17
+ import stringWidth from "string-width";
18
+ var CELL_LIMIT = 16384;
19
+ var superscript = {
20
+ "0": "⁰",
21
+ "1": "¹",
22
+ "2": "²",
23
+ "3": "³",
24
+ "4": "⁴",
25
+ "5": "⁵",
26
+ "6": "⁶",
27
+ "7": "⁷",
28
+ "8": "⁸",
29
+ "9": "⁹",
30
+ "+": "⁺",
31
+ "-": "⁻",
32
+ "=": "⁼",
33
+ "(": "⁽",
34
+ ")": "⁾",
35
+ n: "ⁿ",
36
+ i: "ⁱ"
37
+ };
38
+ var subscript = {
39
+ "0": "₀",
40
+ "1": "₁",
41
+ "2": "₂",
42
+ "3": "₃",
43
+ "4": "₄",
44
+ "5": "₅",
45
+ "6": "₆",
46
+ "7": "₇",
47
+ "8": "₈",
48
+ "9": "₉",
49
+ "+": "₊",
50
+ "-": "₋",
51
+ "=": "₌",
52
+ "(": "₍",
53
+ ")": "₎",
54
+ a: "ₐ",
55
+ e: "ₑ",
56
+ h: "ₕ",
57
+ i: "ᵢ",
58
+ j: "ⱼ",
59
+ k: "ₖ",
60
+ l: "ₗ",
61
+ m: "ₘ",
62
+ n: "ₙ",
63
+ o: "ₒ",
64
+ p: "ₚ",
65
+ r: "ᵣ",
66
+ s: "ₛ",
67
+ t: "ₜ",
68
+ u: "ᵤ",
69
+ v: "ᵥ",
70
+ x: "ₓ"
71
+ };
72
+ function layoutMath(node, displayMode) {
73
+ return layout(node, { display: displayMode });
74
+ }
75
+ function boxToOutput(box, widthMax, heightMax) {
76
+ const spans = [];
77
+ for (let y = 0;y < Math.min(box.height, heightMax); y++) {
78
+ const line = [];
79
+ let width = 0;
80
+ for (let x = 0;x < box.width && width < widthMax; x++) {
81
+ const value = box.cells[y][x];
82
+ const cellWidth = value ? stringWidth(value.char) : 0;
83
+ if (cellWidth === 0) {
84
+ appendSpan(line, " ");
85
+ width++;
86
+ continue;
87
+ }
88
+ if (cellWidth > widthMax)
89
+ throw new Error(`Unicode glyph exceeds the ${widthMax}-column TeX width`);
90
+ if (width + cellWidth > widthMax)
91
+ break;
92
+ appendSpan(line, value.char, value.style);
93
+ width += cellWidth;
94
+ x += cellWidth - 1;
95
+ }
96
+ trimSpans(line);
97
+ if (y)
98
+ appendSpan(spans, `
99
+ `);
100
+ for (const span of line)
101
+ appendSpan(spans, span.text, span);
102
+ }
103
+ trimSpans(spans);
104
+ const text = spans.map((span) => span.text).join("");
105
+ return spans.some((span) => span.color !== undefined || span.bold !== undefined || span.italic !== undefined) ? { text, spans } : { text };
106
+ }
107
+ function appendSpan(spans, text, style) {
108
+ const last = spans.at(-1);
109
+ if (last && last.color === style?.color && last.bold === style?.bold && last.italic === style?.italic) {
110
+ last.text += text;
111
+ return;
112
+ }
113
+ spans.push({ ...style, text });
114
+ }
115
+ function trimSpans(spans) {
116
+ while (spans.length) {
117
+ const last = spans.at(-1);
118
+ last.text = last.text.trimEnd();
119
+ if (last.text)
120
+ return;
121
+ spans.pop();
122
+ }
123
+ }
124
+ function layout(node, context) {
125
+ switch (node.type) {
126
+ case "row":
127
+ return layoutRow(node.body, context);
128
+ case "symbol":
129
+ case "text":
130
+ case "operator":
131
+ return textBox(applyVariant(node.value, context.variant), context.style);
132
+ case "space":
133
+ return blank(node.width, 1, 0);
134
+ case "fraction":
135
+ return fraction(layout(node.numerator, context), layout(node.denominator, context), node.bar, node.numeratorAlign, context.style);
136
+ case "root":
137
+ return root(layout(node.body, context), node.index ? layout(node.index, context) : undefined, context.style);
138
+ case "scripts":
139
+ return scripts(node, context);
140
+ case "delimited":
141
+ return delimited(node.left, layout(node.body, context), node.right, context.style);
142
+ case "matrix":
143
+ return matrix(node, context);
144
+ case "accent":
145
+ return accent(node.accent, layout(node.body, context), context.style);
146
+ case "brace":
147
+ return brace(layout(node.body, context), node.position, context.style);
148
+ case "variant":
149
+ return layout(node.body, { ...context, variant: node.variant, style: node.variant === "bold" ? { ...context.style, bold: true } : node.variant === "italic" ? { ...context.style, italic: true } : context.style });
150
+ case "color":
151
+ return layout(node.body, { ...context, style: { ...context.style, color: node.color } });
152
+ case "overunder":
153
+ return overUnder(layout(node.base, context), node.over ? layout(node.over, context) : undefined, node.under ? layout(node.under, context) : undefined);
154
+ }
155
+ }
156
+ function layoutRow(nodes, context) {
157
+ const boxes = [];
158
+ let previous;
159
+ for (let index = 0;index < nodes.length; index++) {
160
+ const role = normalizedRole(roleOf(nodes[index]), previous, nextRole(nodes, index + 1));
161
+ if (needsSpace(previous, role, boxes.length))
162
+ boxes.push(blank(1, 1, 0));
163
+ boxes.push(layout(nodes[index], context));
164
+ if (nodes[index].type !== "space")
165
+ previous = role ?? "ordinary";
166
+ }
167
+ return hpack(boxes);
168
+ }
169
+ function fraction(top, bottom, bar, align, style) {
170
+ const width = Math.max(top.width, bottom.width) + 2;
171
+ const result = blank(width, top.height + bottom.height + 1, top.height);
172
+ overlay(result, top, align === "left" ? 1 : align === "right" ? width - top.width - 1 : Math.floor((width - top.width) / 2), 0);
173
+ if (bar)
174
+ horizontal(result, top.height, width, "─", style);
175
+ overlay(result, bottom, Math.floor((width - bottom.width) / 2), top.height + 1);
176
+ return result;
177
+ }
178
+ function root(body, index, style) {
179
+ const indexWidth = index ? Math.max(0, index.width - 1) : 0;
180
+ const bodyX = indexWidth + 2;
181
+ const bodyY = index?.height ?? 1;
182
+ const result = blank(bodyX + body.width, body.height + bodyY, body.baseline + bodyY);
183
+ set(result, bodyX - 1, bodyY - 1, "╭", style);
184
+ for (let x = bodyX;x < result.width; x++)
185
+ set(result, x, bodyY - 1, "─", style);
186
+ set(result, bodyX - 2, result.baseline, "√", style);
187
+ overlay(result, body, bodyX, bodyY);
188
+ if (index)
189
+ overlay(result, index, 0, 0);
190
+ return result;
191
+ }
192
+ function scripts(node, context) {
193
+ const base = layout(node.base, context);
194
+ const sup = node.superscript ? layout(node.superscript, context) : undefined;
195
+ const sub = node.subscript ? layout(node.subscript, context) : undefined;
196
+ const supText = node.superscript ? simpleText(node.superscript) : undefined;
197
+ const subText = node.subscript ? simpleText(node.subscript) : undefined;
198
+ let target = node.base;
199
+ while (target.type === "variant" || target.type === "color" || target.type === "scripts") {
200
+ target = target.type === "scripts" ? target.base : target.body;
201
+ }
202
+ const limits = target.type === "operator" && (target.limits === true || target.limits === "display" && context.display);
203
+ if (limits || target.type === "brace")
204
+ return overUnder(base, sup, sub);
205
+ if (base.height === 1) {
206
+ const mappedSup = supText === undefined ? undefined : mapScript(supText, superscript);
207
+ const mappedSub = subText === undefined ? undefined : mapScript(subText, subscript);
208
+ if ((!sup?.width || mappedSup !== undefined) && (!sub?.width || mappedSub !== undefined)) {
209
+ return hpack([base, textBox((mappedSup ?? "") + (mappedSub ?? ""), context.style)]);
210
+ }
211
+ }
212
+ const sideWidth = Math.max(sup?.width ?? 0, sub?.width ?? 0);
213
+ const topHeight = sup?.width ? sup.height : 0;
214
+ const bottomHeight = sub?.width ? sub.height : 0;
215
+ const result = blank(base.width + sideWidth, topHeight + base.height + bottomHeight, topHeight + base.baseline);
216
+ overlay(result, base, 0, topHeight);
217
+ if (sup)
218
+ overlay(result, sup, base.width, 0);
219
+ if (sub)
220
+ overlay(result, sub, base.width, topHeight + base.height);
221
+ return result;
222
+ }
223
+ function overUnder(base, over, under) {
224
+ const width = Math.max(base.width, over?.width ?? 0, under?.width ?? 0);
225
+ const overHeight = over?.width ? over.height : 0;
226
+ const underHeight = under?.width ? under.height : 0;
227
+ const result = blank(width, overHeight + base.height + underHeight, overHeight + base.baseline);
228
+ if (over)
229
+ overlay(result, over, Math.floor((width - over.width) / 2), 0);
230
+ overlay(result, base, Math.floor((width - base.width) / 2), overHeight);
231
+ if (under)
232
+ overlay(result, under, Math.floor((width - under.width) / 2), overHeight + base.height);
233
+ return result;
234
+ }
235
+ function delimited(left, body, right, style) {
236
+ return hpack([delimiter(left, body.height, body.baseline, true, style), body, delimiter(right, body.height, body.baseline, false, style)]);
237
+ }
238
+ function matrix(node, context) {
239
+ const cells = node.rows.map((row) => row.map((cell) => layout(cell, context)));
240
+ const alignments = node.columns?.match(/[lcr]/g);
241
+ const rules = node.columns?.split(/[lcr]/).map((rule) => rule.length) ?? [];
242
+ const columns = Math.max(alignments?.length ?? 0, ...cells.map((row) => row.length));
243
+ const widths = Array.from({ length: columns }, (_, x) => Math.max(0, ...cells.map((row) => row[x]?.width ?? 0)));
244
+ const ascents = cells.map((row) => Math.max(0, ...row.map((cell) => cell.baseline)));
245
+ const descents = cells.map((row) => Math.max(0, ...row.map((cell) => cell.height - cell.baseline - 1)));
246
+ const heights = ascents.map((value, y2) => value + descents[y2] + 1);
247
+ const aligned = node.environment === "aligned" || node.environment === "align";
248
+ const gap = node.environment === "cases" || aligned ? 2 : 1;
249
+ const gaps = Array.from({ length: columns + 1 }, (_, boundary) => {
250
+ const edge = boundary === 0 || boundary === columns;
251
+ return rules[boundary] ? rules[boundary] + (edge ? 1 : 2) : edge ? 0 : gap;
252
+ });
253
+ const width = widths.reduce((sum, value) => sum + value, 0) + gaps.reduce((sum, value) => sum + value, 0);
254
+ const height = Math.max(1, heights.reduce((sum, value) => sum + value, 0));
255
+ const result = blank(width, height, Math.floor(height / 2));
256
+ let y = 0;
257
+ for (let rowIndex = 0;rowIndex < cells.length; rowIndex++) {
258
+ let x = gaps[0];
259
+ for (let column = 0;column < columns; column++) {
260
+ const cell = cells[rowIndex][column];
261
+ if (cell) {
262
+ const alignment = alignments?.[column] ?? (node.environment === "cases" ? "l" : aligned ? column % 2 === 0 ? "r" : "l" : "c");
263
+ const offset = alignment === "l" ? 0 : alignment === "r" ? widths[column] - cell.width : Math.floor((widths[column] - cell.width) / 2);
264
+ overlay(result, cell, x + offset, y + ascents[rowIndex] - cell.baseline);
265
+ }
266
+ x += widths[column] + gaps[column + 1];
267
+ }
268
+ y += heights[rowIndex];
269
+ }
270
+ let boundaryX = 0;
271
+ for (let boundary = 0;boundary <= columns; boundary++) {
272
+ for (let rule = 0;rule < (rules[boundary] ?? 0); rule++) {
273
+ for (let row = 0;row < height; row++)
274
+ set(result, boundaryX + (boundary === 0 ? 0 : 1) + rule, row, "│", context.style);
275
+ }
276
+ boundaryX += gaps[boundary] + (widths[boundary] ?? 0);
277
+ }
278
+ const pair = matrixDelimiters(node.environment);
279
+ return pair ? delimited(pair[0], result, pair[1], context.style) : result;
280
+ }
281
+ function brace(body, position, style) {
282
+ const over = position === "over";
283
+ const width = Math.max(3, body.width);
284
+ const result = blank(width, body.height + 1, body.baseline + (over ? 1 : 0));
285
+ const y = over ? 0 : body.height;
286
+ overlay(result, body, Math.floor((width - body.width) / 2), over ? 1 : 0);
287
+ horizontal(result, y, width, "─", style);
288
+ set(result, 0, y, over ? "╭" : "╰", style);
289
+ set(result, width - 1, y, over ? "╮" : "╯", style);
290
+ set(result, Math.floor((width - 1) / 2), y, over ? "┴" : "┬", style);
291
+ return result;
292
+ }
293
+ function accent(kind, body, style) {
294
+ if (kind === "underline") {
295
+ const result2 = blank(body.width, body.height + 1, body.baseline);
296
+ overlay(result2, body, 0, 0);
297
+ horizontal(result2, body.height, body.width, "─", style);
298
+ return result2;
299
+ }
300
+ const result = blank(body.width, body.height + 1, body.baseline + 1);
301
+ overlay(result, body, 0, 1);
302
+ const mark = kind === "hat" || kind === "widehat" ? body.width === 1 ? "^" : "⌢" : kind === "bar" || kind === "overline" ? "─" : kind === "vec" ? "→" : kind === "tilde" ? "~" : kind === "dot" ? "·" : "¨";
303
+ if (kind === "bar" || kind === "overline")
304
+ horizontal(result, 0, body.width, mark, style);
305
+ else
306
+ set(result, Math.max(0, Math.floor((body.width - stringWidth(mark)) / 2)), 0, mark, style);
307
+ return result;
308
+ }
309
+ function delimiter(value, height, baseline, left, style) {
310
+ const base = value.replace(/\p{Mark}+$/u, "");
311
+ if (!base)
312
+ return blank(0, height, baseline);
313
+ if (height === 1)
314
+ return textBox(value, style);
315
+ const glyphs = delimiterGlyphs(base, left);
316
+ const suffix = value.slice(base.length);
317
+ const result = blank(Math.max(...glyphs.map((glyph) => stringWidth(glyph + suffix))), height, baseline);
318
+ for (let y = 0;y < height; y++)
319
+ set(result, 0, y, y === 0 ? glyphs[0] : y === height - 1 ? glyphs[2] : glyphs[1], style);
320
+ if ((base === "{" || base === "}") && height >= 3)
321
+ set(result, 0, Math.floor(height / 2), left ? "⎨" : "⎬", style);
322
+ if (suffix)
323
+ set(result, 0, baseline, result.cells[baseline][0].char + suffix, style);
324
+ return result;
325
+ }
326
+ function delimiterGlyphs(value, left) {
327
+ if (value === "(")
328
+ return ["⎛", "⎜", "⎝"];
329
+ if (value === ")")
330
+ return ["⎞", "⎟", "⎠"];
331
+ if (value === "[")
332
+ return ["⎡", "⎢", "⎣"];
333
+ if (value === "]")
334
+ return ["⎤", "⎥", "⎦"];
335
+ if (value === "{")
336
+ return ["⎧", "⎪", "⎩"];
337
+ if (value === "}")
338
+ return ["⎫", "⎪", "⎭"];
339
+ if (value === "⟨")
340
+ return ["/", "│", "\\"];
341
+ if (value === "⟩")
342
+ return ["\\", "│", "/"];
343
+ if (value === "⌊" || value === "⌋")
344
+ return ["│", "│", value];
345
+ if (value === "⌈" || value === "⌉")
346
+ return [value, "│", "│"];
347
+ return [value, value, value];
348
+ }
349
+ function matrixDelimiters(value) {
350
+ return value === "pmatrix" ? ["(", ")"] : value === "bmatrix" ? ["[", "]"] : value === "Bmatrix" ? ["{", "}"] : value === "vmatrix" ? ["│", "│"] : value === "Vmatrix" ? ["║", "║"] : value === "cases" ? ["{", ""] : undefined;
351
+ }
352
+ function textBox(text, style) {
353
+ const parts = Array.from(graphemeSegmenter.segment(text), (part) => part.segment);
354
+ const result = blank(parts.reduce((sum, part) => sum + stringWidth(part), 0), 1, 0);
355
+ let x = 0;
356
+ for (const part of parts) {
357
+ set(result, x, 0, part, style);
358
+ x += stringWidth(part);
359
+ }
360
+ return result;
361
+ }
362
+ function hpack(boxes) {
363
+ if (!boxes.length)
364
+ return blank(0, 1, 0);
365
+ const ascent = Math.max(...boxes.map((box) => box.baseline));
366
+ const descent = Math.max(...boxes.map((box) => box.height - box.baseline - 1));
367
+ const result = blank(boxes.reduce((sum, box) => sum + box.width, 0), ascent + descent + 1, ascent);
368
+ let x = 0;
369
+ for (const box of boxes) {
370
+ overlay(result, box, x, ascent - box.baseline);
371
+ x += box.width;
372
+ }
373
+ return result;
374
+ }
375
+ function blank(width, height, baseline) {
376
+ width = Math.max(0, width);
377
+ height = Math.max(1, height);
378
+ if (width * height > CELL_LIMIT)
379
+ throw new Error(`Unicode TeX output exceeds ${CELL_LIMIT} characters`);
380
+ return { width, height, baseline: Math.max(0, baseline), cells: Array.from({ length: height }, () => Array(width)) };
381
+ }
382
+ function overlay(target, source, x, y) {
383
+ for (let sy = 0;sy < source.height; sy++)
384
+ for (let sx = 0;sx < source.width; sx++)
385
+ if (source.cells[sy][sx])
386
+ target.cells[y + sy][x + sx] = source.cells[sy][sx];
387
+ }
388
+ function set(box, x, y, char, style) {
389
+ if (x >= 0 && y >= 0 && x < box.width && y < box.height)
390
+ box.cells[y][x] = style ? { char, style } : { char };
391
+ }
392
+ function horizontal(box, y, width, value, style) {
393
+ for (let x = 0;x < width; x++)
394
+ set(box, x, y, value, style);
395
+ }
396
+ function simpleText(node) {
397
+ if (node.type === "symbol" || node.type === "text" || node.type === "operator")
398
+ return node.value;
399
+ if (node.type === "row") {
400
+ const values = node.body.map(simpleText);
401
+ if (values.every((value) => value !== undefined))
402
+ return values.join("");
403
+ }
404
+ return;
405
+ }
406
+ function mapScript(value, table) {
407
+ let result = "";
408
+ for (const char of value) {
409
+ if (!table[char])
410
+ return;
411
+ result += table[char];
412
+ }
413
+ return result;
414
+ }
415
+ function roleOf(node) {
416
+ if (node.type === "symbol")
417
+ return node.role;
418
+ if (node.type === "operator" || node.type === "fraction" || node.type === "root" || node.type === "matrix")
419
+ return "operator";
420
+ if (node.type === "scripts")
421
+ return roleOf(node.base);
422
+ if (node.type === "variant" || node.type === "color")
423
+ return roleOf(node.body);
424
+ return;
425
+ }
426
+ function nextRole(nodes, start) {
427
+ for (let i = start;i < nodes.length; i++)
428
+ if (nodes[i].type !== "space")
429
+ return roleOf(nodes[i]) ?? "ordinary";
430
+ return;
431
+ }
432
+ function needsSpace(previous, current, count) {
433
+ return count > 0 && previous !== "opening" && previous !== "punctuation" && current !== "closing" && current !== "punctuation" && (previous === "binary" || previous === "relation" || previous === "operator" || current === "binary" || current === "relation" || current === "operator");
434
+ }
435
+ function normalizedRole(role, previous, next) {
436
+ return role === "binary" && (!previous || ["binary", "relation", "operator", "punctuation", "opening"].includes(previous) || !next || ["binary", "relation", "punctuation", "closing"].includes(next)) ? "ordinary" : role;
437
+ }
438
+ function applyVariant(value, variant) {
439
+ if (!variant || variant === "normal" || variant === "bold" || variant === "italic")
440
+ return value;
441
+ const exceptions = {
442
+ "double-struck": { C: "ℂ", H: "ℍ", N: "ℕ", P: "ℙ", Q: "ℚ", R: "ℝ", Z: "ℤ" },
443
+ script: { B: "ℬ", E: "ℰ", F: "ℱ", H: "ℋ", I: "ℐ", L: "ℒ", M: "ℳ", R: "ℛ", e: "ℯ", g: "ℊ", o: "ℴ" },
444
+ fraktur: { C: "ℭ", H: "ℌ", I: "ℑ", R: "ℜ", Z: "ℨ" }
445
+ };
446
+ const ranges = {
447
+ "double-struck": [120120, 120146, 120792],
448
+ script: [119964, 119990],
449
+ fraktur: [120068, 120094],
450
+ sans: [120224, 120250, 120802],
451
+ monospace: [120432, 120458, 120822]
452
+ };
453
+ const range = ranges[variant];
454
+ return Array.from(value).map((char) => {
455
+ const exception = exceptions[variant]?.[char];
456
+ if (exception)
457
+ return exception;
458
+ const code = char.codePointAt(0);
459
+ if (code >= 65 && code <= 90)
460
+ return String.fromCodePoint(range[0] + code - 65);
461
+ if (code >= 97 && code <= 122)
462
+ return String.fromCodePoint(range[1] + code - 97);
463
+ if (range[2] !== undefined && code >= 48 && code <= 57)
464
+ return String.fromCodePoint(range[2] + code - 48);
465
+ return char;
466
+ }).join("");
467
+ }
468
+
469
+ // src/math-symbols.ts
470
+ var ordinary = {
471
+ alpha: "α",
472
+ beta: "β",
473
+ gamma: "γ",
474
+ delta: "δ",
475
+ epsilon: "ϵ",
476
+ varepsilon: "ε",
477
+ zeta: "ζ",
478
+ eta: "η",
479
+ theta: "θ",
480
+ vartheta: "ϑ",
481
+ iota: "ι",
482
+ kappa: "κ",
483
+ lambda: "λ",
484
+ mu: "μ",
485
+ nu: "ν",
486
+ xi: "ξ",
487
+ omicron: "ο",
488
+ pi: "π",
489
+ varpi: "ϖ",
490
+ rho: "ρ",
491
+ varrho: "ϱ",
492
+ sigma: "σ",
493
+ varsigma: "ς",
494
+ tau: "τ",
495
+ upsilon: "υ",
496
+ phi: "ϕ",
497
+ varphi: "φ",
498
+ chi: "χ",
499
+ psi: "ψ",
500
+ omega: "ω",
501
+ Gamma: "Γ",
502
+ Delta: "Δ",
503
+ Theta: "Θ",
504
+ Lambda: "Λ",
505
+ Xi: "Ξ",
506
+ Pi: "Π",
507
+ Sigma: "Σ",
508
+ Upsilon: "Υ",
509
+ Phi: "Φ",
510
+ Psi: "Ψ",
511
+ Omega: "Ω",
512
+ infty: "∞",
513
+ partial: "∂",
514
+ nabla: "∇",
515
+ emptyset: "∅",
516
+ varnothing: "∅",
517
+ forall: "∀",
518
+ exists: "∃",
519
+ neg: "¬",
520
+ angle: "∠",
521
+ degree: "°",
522
+ prime: "′",
523
+ hbar: "ℏ",
524
+ ell: "ℓ",
525
+ Re: "ℜ",
526
+ Im: "ℑ",
527
+ aleph: "ℵ",
528
+ top: "⊤",
529
+ bot: "⊥",
530
+ checkmark: "✓",
531
+ imath: "ı",
532
+ jmath: "ȷ",
533
+ beth: "ℶ",
534
+ gimel: "ℷ",
535
+ daleth: "ℸ",
536
+ measuredangle: "∡",
537
+ triangle: "△",
538
+ square: "□",
539
+ lozenge: "◊",
540
+ nexists: "∄",
541
+ lnot: "¬",
542
+ backprime: "‵",
543
+ clubsuit: "♣",
544
+ diamondsuit: "♢",
545
+ heartsuit: "♡",
546
+ spadesuit: "♠"
547
+ };
548
+ var binary = {
549
+ pm: "±",
550
+ mp: "∓",
551
+ times: "×",
552
+ div: "÷",
553
+ cdot: "·",
554
+ ast: "∗",
555
+ star: "⋆",
556
+ circ: "∘",
557
+ bullet: "•",
558
+ oplus: "⊕",
559
+ ominus: "⊖",
560
+ otimes: "⊗",
561
+ oslash: "⊘",
562
+ odot: "⊙",
563
+ cap: "∩",
564
+ cup: "∪",
565
+ land: "∧",
566
+ wedge: "∧",
567
+ lor: "∨",
568
+ vee: "∨",
569
+ setminus: "∖",
570
+ uplus: "⊎",
571
+ sqcap: "⊓",
572
+ sqcup: "⊔",
573
+ wr: "≀",
574
+ diamond: "⋄",
575
+ bigtriangleup: "△",
576
+ bigtriangledown: "▽",
577
+ triangleleft: "◁",
578
+ triangleright: "▷"
579
+ };
580
+ var relation = {
581
+ ne: "≠",
582
+ neq: "≠",
583
+ equiv: "≡",
584
+ approx: "≈",
585
+ sim: "∼",
586
+ simeq: "≃",
587
+ cong: "≅",
588
+ propto: "∝",
589
+ le: "≤",
590
+ leq: "≤",
591
+ ge: "≥",
592
+ geq: "≥",
593
+ ll: "≪",
594
+ gg: "≫",
595
+ in: "∈",
596
+ notin: "∉",
597
+ ni: "∋",
598
+ subset: "⊂",
599
+ supset: "⊃",
600
+ subseteq: "⊆",
601
+ supseteq: "⊇",
602
+ parallel: "∥",
603
+ perp: "⊥",
604
+ vdash: "⊢",
605
+ models: "⊨",
606
+ leftarrow: "←",
607
+ gets: "←",
608
+ rightarrow: "→",
609
+ to: "→",
610
+ leftrightarrow: "↔",
611
+ Leftarrow: "⇐",
612
+ Rightarrow: "⇒",
613
+ Leftrightarrow: "⇔",
614
+ mapsto: "↦",
615
+ longleftarrow: "⟵",
616
+ longrightarrow: "⟶",
617
+ longleftrightarrow: "⟷",
618
+ uparrow: "↑",
619
+ downarrow: "↓",
620
+ updownarrow: "↕",
621
+ equals: "=",
622
+ asymp: "≍",
623
+ lt: "<",
624
+ gt: ">",
625
+ prec: "≺",
626
+ succ: "≻",
627
+ preceq: "⪯",
628
+ succeq: "⪰",
629
+ sqsubset: "⊏",
630
+ sqsupset: "⊐",
631
+ sqsubseteq: "⊑",
632
+ sqsupseteq: "⊒",
633
+ owns: "∋",
634
+ dashv: "⊣",
635
+ mid: "∣",
636
+ smile: "⌣",
637
+ frown: "⌢",
638
+ hookleftarrow: "↩",
639
+ hookrightarrow: "↪",
640
+ leftharpoonup: "↼",
641
+ leftharpoondown: "↽",
642
+ rightharpoonup: "⇀",
643
+ rightharpoondown: "⇁",
644
+ rightleftharpoons: "⇌",
645
+ Longleftarrow: "⟸",
646
+ Longrightarrow: "⟹",
647
+ Longleftrightarrow: "⟺",
648
+ longmapsto: "⟼",
649
+ Uparrow: "⇑",
650
+ Downarrow: "⇓",
651
+ Updownarrow: "⇕",
652
+ nearrow: "↗",
653
+ searrow: "↘",
654
+ swarrow: "↙",
655
+ nwarrow: "↖"
656
+ };
657
+ var punctuation = { ldots: "…", dots: "…", cdots: "⋯", vdots: "⋮", ddots: "⋱", colon: ":" };
658
+ function definitions(values, role) {
659
+ return Object.fromEntries(Object.entries(values).map(([name, value]) => [name, { value, role }]));
660
+ }
661
+ var symbols = {
662
+ ...definitions(ordinary, "ordinary"),
663
+ ...definitions(binary, "binary"),
664
+ ...definitions(relation, "relation"),
665
+ ...definitions(punctuation, "punctuation")
666
+ };
667
+ var operators = {
668
+ sum: "∑",
669
+ prod: "∏",
670
+ coprod: "∐",
671
+ int: "∫",
672
+ iint: "∬",
673
+ iiint: "∭",
674
+ oint: "∮",
675
+ bigcap: "⋂",
676
+ bigcup: "⋃",
677
+ bigvee: "⋁",
678
+ bigwedge: "⋀",
679
+ bigoplus: "⨁",
680
+ bigotimes: "⨂",
681
+ bigodot: "⨀"
682
+ };
683
+ var namedOperators = new Set([
684
+ "arccos",
685
+ "arcsin",
686
+ "arctan",
687
+ "arg",
688
+ "cos",
689
+ "cosh",
690
+ "cot",
691
+ "coth",
692
+ "csc",
693
+ "deg",
694
+ "det",
695
+ "dim",
696
+ "exp",
697
+ "gcd",
698
+ "hom",
699
+ "inf",
700
+ "ker",
701
+ "lg",
702
+ "lim",
703
+ "liminf",
704
+ "limsup",
705
+ "ln",
706
+ "log",
707
+ "max",
708
+ "min",
709
+ "mod",
710
+ "Pr",
711
+ "sec",
712
+ "sin",
713
+ "sinh",
714
+ "sup",
715
+ "tan",
716
+ "tanh"
717
+ ]);
718
+ var delimiters = {
719
+ "(": "(",
720
+ ")": ")",
721
+ "[": "[",
722
+ "]": "]",
723
+ "{": "{",
724
+ "}": "}",
725
+ "|": "│",
726
+ "\\|": "║",
727
+ lbrace: "{",
728
+ rbrace: "}",
729
+ vert: "│",
730
+ Vert: "║",
731
+ langle: "⟨",
732
+ rangle: "⟩",
733
+ lfloor: "⌊",
734
+ rfloor: "⌋",
735
+ lceil: "⌈",
736
+ rceil: "⌉",
737
+ lvert: "│",
738
+ rvert: "│",
739
+ lVert: "║",
740
+ rVert: "║",
741
+ "\\{": "{",
742
+ "\\}": "}",
743
+ "/": "/",
744
+ "<": "<",
745
+ ">": ">",
746
+ backslash: "\\",
747
+ ".": ""
748
+ };
749
+ var accents = {
750
+ hat: "hat",
751
+ widehat: "widehat",
752
+ bar: "bar",
753
+ overline: "overline",
754
+ underline: "underline",
755
+ vec: "vec",
756
+ tilde: "tilde",
757
+ widetilde: "tilde",
758
+ dot: "dot",
759
+ ddot: "ddot"
760
+ };
761
+ var spacing = { ",": 0, ":": 1, ";": 1, "!": 0, quad: 2, qquad: 4, enspace: 1, thinspace: 0 };
762
+
763
+ // src/math-parser.ts
764
+ var MAX_NESTING_DEPTH = 256;
765
+ var environments = new Set([
766
+ "matrix",
767
+ "pmatrix",
768
+ "bmatrix",
769
+ "Bmatrix",
770
+ "vmatrix",
771
+ "Vmatrix",
772
+ "cases",
773
+ "aligned",
774
+ "align",
775
+ "gathered",
776
+ "gather",
777
+ "smallmatrix",
778
+ "array"
779
+ ]);
780
+ var variants = {
781
+ mathrm: "normal",
782
+ textrm: "normal",
783
+ mathnormal: "normal",
784
+ mathbf: "bold",
785
+ boldsymbol: "bold",
786
+ bm: "bold",
787
+ mathit: "italic",
788
+ mathsf: "sans",
789
+ mathtt: "monospace",
790
+ mathbb: "double-struck",
791
+ mathcal: "script",
792
+ mathscr: "script",
793
+ mathfrak: "fraktur"
794
+ };
795
+ function parseMath(source, options = {}) {
796
+ return new Parser(source, false, options.strict ?? false).parse();
797
+ }
798
+ function parseMathIncomplete(source, options = {}) {
799
+ return new Parser(source, true, options.strict ?? false).parse();
800
+ }
801
+
802
+ class Parser {
803
+ source;
804
+ incomplete;
805
+ strict;
806
+ offset = 0;
807
+ depth = 0;
808
+ graphemes;
809
+ constructor(source, incomplete, strict) {
810
+ this.source = source;
811
+ this.incomplete = incomplete;
812
+ this.strict = strict;
813
+ this.graphemes = graphemeSegmenter.segment(source);
814
+ }
815
+ parse() {
816
+ const result = row(this.parseRow());
817
+ this.skipWhitespace();
818
+ if (!this.done())
819
+ this.fail(this.peek() === "}" ? "Unexpected closing TeX group" : `Unexpected "${this.peek()}"`);
820
+ return result;
821
+ }
822
+ parseRow(stop) {
823
+ const body = [];
824
+ while (!this.done()) {
825
+ this.skipWhitespace();
826
+ if (this.done() || stop?.() || this.peek() === "}")
827
+ break;
828
+ const char = this.peek();
829
+ if (char === "^" || char === "_") {
830
+ this.offset++;
831
+ const argument = this.parseArgument();
832
+ const previous = body.pop() ?? row([]);
833
+ const scripts2 = previous.type === "scripts" ? previous : { type: "scripts", base: previous };
834
+ if (char === "^")
835
+ scripts2.superscript = argument;
836
+ else
837
+ scripts2.subscript = argument;
838
+ body.push(scripts2);
839
+ } else {
840
+ const atom = this.parseAtom(body.at(-1), stop);
841
+ if (atom)
842
+ body.push(atom);
843
+ }
844
+ }
845
+ return body;
846
+ }
847
+ parseAtom(previous, stop) {
848
+ this.depth++;
849
+ if (this.depth > MAX_NESTING_DEPTH)
850
+ this.fail(`TeX nesting exceeds the ${MAX_NESTING_DEPTH}-level limit`);
851
+ try {
852
+ if (this.peek() === "{")
853
+ return this.parseGroup();
854
+ if (this.peek() === "\\") {
855
+ const atom = this.parseCommand(previous, stop);
856
+ const value2 = atom && unwrapStyle(atom);
857
+ if (value2 && "value" in value2)
858
+ value2.value += this.readCombiningSuffix();
859
+ return atom;
860
+ }
861
+ if (this.peek() === "~") {
862
+ this.offset++;
863
+ return { type: "space", width: 1 };
864
+ }
865
+ const value = this.readLiteral();
866
+ return { type: "symbol", value, role: inferRole(value[0]) };
867
+ } finally {
868
+ this.depth--;
869
+ }
870
+ }
871
+ parseCommand(previous, stop) {
872
+ const start = this.offset;
873
+ const command = this.readCommand();
874
+ if (command === "\\")
875
+ return row([]);
876
+ if (command === "begin")
877
+ return this.parseEnvironment();
878
+ if (command === "end")
879
+ this.fail("Unexpected \\end", start);
880
+ if (["frac", "dfrac", "tfrac", "cfrac"].includes(command)) {
881
+ this.skipWhitespace();
882
+ const alignment = command === "cfrac" && this.peek() === "[" ? /^\[([lr]?)(\]|$)/.exec(this.source.slice(this.offset)) : undefined;
883
+ if (alignment === null || alignment && !alignment[2] && (!this.incomplete || this.offset + alignment[0].length !== this.source.length)) {
884
+ this.fail("Unsupported \\cfrac alignment; expected [l], [r], or []");
885
+ }
886
+ if (alignment)
887
+ this.offset += alignment[0].length;
888
+ return {
889
+ type: "fraction",
890
+ numerator: this.parseArgument(),
891
+ denominator: this.parseArgument(),
892
+ bar: true,
893
+ ...alignment?.[1] ? { numeratorAlign: alignment[1] === "l" ? "left" : "right" } : {}
894
+ };
895
+ }
896
+ if (["binom", "dbinom", "tbinom"].includes(command)) {
897
+ return { type: "delimited", left: "(", body: { type: "fraction", numerator: this.parseArgument(), denominator: this.parseArgument(), bar: false }, right: ")" };
898
+ }
899
+ if (command === "sqrt") {
900
+ const index = this.optionalArgument();
901
+ return { type: "root", body: this.parseArgument(), ...index ? { index } : {} };
902
+ }
903
+ if (command === "left")
904
+ return this.parseLeftRight();
905
+ if (command === "right")
906
+ this.fail("Unexpected \\right", start);
907
+ if (command === "middle")
908
+ return { type: "symbol", value: this.readDelimiter() };
909
+ if (Object.hasOwn(accents, command))
910
+ return { type: "accent", accent: accents[command], body: this.parseArgument() };
911
+ if (Object.hasOwn(variants, command)) {
912
+ return { type: "variant", variant: variants[command], body: command === "textrm" ? { type: "text", value: this.readTextGroup() } : this.parseArgument() };
913
+ }
914
+ if (command === "text" || command === "mbox")
915
+ return { type: "text", value: this.readTextGroup() };
916
+ if (command === "operatorname") {
917
+ const limits = this.peek() === "*";
918
+ if (limits)
919
+ this.offset++;
920
+ return { type: "operator", value: this.readTextGroup(), limits };
921
+ }
922
+ if (command === "overset" || command === "stackrel") {
923
+ const over = this.parseArgument();
924
+ return { type: "overunder", over, base: this.parseArgument() };
925
+ }
926
+ if (command === "underset") {
927
+ const under = this.parseArgument();
928
+ return { type: "overunder", under, base: this.parseArgument() };
929
+ }
930
+ if (command === "overbrace" || command === "underbrace") {
931
+ return { type: "brace", body: this.parseArgument(), position: command === "overbrace" ? "over" : "under" };
932
+ }
933
+ if (command === "textcolor" || command === "color") {
934
+ const color = this.readRawGroup().value;
935
+ return { type: "color", color, body: command === "textcolor" ? this.parseArgument() : row(this.parseRow(stop)) };
936
+ }
937
+ if (command === "not") {
938
+ const target = this.parseArgument();
939
+ const symbol = unwrapStyle(target);
940
+ if (symbol.type === "symbol") {
941
+ symbol.value = negate(symbol.value);
942
+ return target;
943
+ }
944
+ return { type: "row", body: [{ type: "symbol", value: "¬" }, target] };
945
+ }
946
+ if (command === "pmod") {
947
+ return { type: "row", body: [{ type: "space", width: 1 }, { type: "text", value: "(mod " }, this.parseArgument(), { type: "text", value: ")" }] };
948
+ }
949
+ if (command === "mod" || command === "bmod")
950
+ return { type: "operator", value: "mod", limits: false };
951
+ if (command === "displaylines") {
952
+ this.skipWhitespace();
953
+ if (this.incomplete && this.done())
954
+ return { type: "matrix", environment: "gathered", rows: [[placeholder()]] };
955
+ this.expect("{");
956
+ return this.parseMatrix("gathered", "}");
957
+ }
958
+ if (command === "limits" || command === "nolimits") {
959
+ let base = previous;
960
+ while (base?.type === "variant" || base?.type === "color" || base?.type === "scripts") {
961
+ base = base.type === "scripts" ? base.base : base.body;
962
+ }
963
+ if (base?.type === "operator")
964
+ base.limits = command === "limits";
965
+ return;
966
+ }
967
+ if (["displaystyle", "textstyle", "scriptstyle", "scriptscriptstyle"].includes(command))
968
+ return;
969
+ if (/^(?:big|Big|bigg|Bigg)[lrm]?$/.test(command))
970
+ return { type: "symbol", value: this.readDelimiter() };
971
+ if (Object.hasOwn(spacing, command))
972
+ return { type: "space", width: spacing[command] };
973
+ if (Object.hasOwn(symbols, command))
974
+ return { type: "symbol", ...symbols[command] };
975
+ if (Object.hasOwn(operators, command))
976
+ return { type: "operator", value: operators[command], limits: command.includes("int") ? false : "display" };
977
+ if (namedOperators.has(command))
978
+ return { type: "operator", value: command, limits: command.startsWith("lim") || ["min", "max"].includes(command) ? "display" : false };
979
+ if (Object.hasOwn(delimiters, command))
980
+ return { type: "symbol", value: delimiters[`\\${command}`] ?? delimiters[command] };
981
+ if (["{", "}", "%", "#", "$", "&", "_", "backslash"].includes(command))
982
+ return { type: "symbol", value: command === "backslash" ? "\\" : command };
983
+ if (command === " ")
984
+ return { type: "space", width: 1 };
985
+ if (this.strict)
986
+ this.fail(`Unsupported command \\${command}`, start);
987
+ return { type: "text", value: `\\${command}` };
988
+ }
989
+ parseEnvironment() {
990
+ const rawName = this.readRawGroup().value;
991
+ const name = rawName.replace(/\*$/, "");
992
+ if (!environments.has(name))
993
+ this.fail(`Unsupported TeX environment: ${rawName}`);
994
+ return this.parseMatrix(name, `\\end{${rawName}}`, name === "array" ? this.readArrayColumns() : undefined);
995
+ }
996
+ parseMatrix(environment, end, columns) {
997
+ const rows = [];
998
+ let cells = [];
999
+ while (!this.done()) {
1000
+ this.skipWhitespace();
1001
+ if (this.done())
1002
+ break;
1003
+ if (this.source.startsWith(end, this.offset) && !cells.length)
1004
+ break;
1005
+ const start = this.offset;
1006
+ cells.push(row(this.parseRow(() => this.peek() === "&" || this.source.startsWith("\\\\", this.offset) || this.source.startsWith(end, this.offset) || this.isCommand("end"))));
1007
+ this.skipWhitespace();
1008
+ if (this.peek() === "&") {
1009
+ this.offset++;
1010
+ this.skipWhitespace();
1011
+ if (this.incomplete && this.done())
1012
+ cells.push(placeholder());
1013
+ continue;
1014
+ }
1015
+ if (this.source.startsWith("\\\\", this.offset)) {
1016
+ this.offset += 2;
1017
+ this.skipOptionalRowSpacing();
1018
+ rows.push(cells);
1019
+ cells = [];
1020
+ continue;
1021
+ }
1022
+ if (this.source.startsWith(end, this.offset))
1023
+ break;
1024
+ if (this.offset === start)
1025
+ this.fail(`Unexpected token in ${environment}`);
1026
+ }
1027
+ const closed = this.source.startsWith(end, this.offset);
1028
+ if (!closed && !(this.incomplete && this.done()))
1029
+ this.fail(`Unclosed TeX environment: ${environment}`);
1030
+ if (closed)
1031
+ this.offset += end.length;
1032
+ if (!closed && !rows.length && !cells.length)
1033
+ cells.push(placeholder());
1034
+ if (cells.length || !rows.length)
1035
+ rows.push(cells);
1036
+ return { type: "matrix", rows, environment, ...columns !== undefined ? { columns } : {} };
1037
+ }
1038
+ readArrayColumns() {
1039
+ const group = this.readRawGroup();
1040
+ const columns = group.value.replace(/\s/g, "");
1041
+ if (/[^lcr|]/.test(columns) || !/[lcr]/.test(columns) && group.closed) {
1042
+ this.fail("Unsupported array columns; expected l, c, r, and |");
1043
+ }
1044
+ return columns;
1045
+ }
1046
+ parseLeftRight() {
1047
+ const left = this.readDelimiter();
1048
+ const nodes = this.parseRow(() => this.isCommand("right"));
1049
+ const body = row(nodes);
1050
+ if (!this.isCommand("right")) {
1051
+ if (this.incomplete && this.done())
1052
+ return { type: "delimited", left, body: nodes.length ? body : placeholder(), right: "" };
1053
+ this.fail("Missing \\right");
1054
+ }
1055
+ this.readCommand();
1056
+ return { type: "delimited", left, body, right: this.readDelimiter() };
1057
+ }
1058
+ parseArgument() {
1059
+ while (true) {
1060
+ this.skipWhitespace();
1061
+ if (this.done()) {
1062
+ if (this.incomplete)
1063
+ return placeholder();
1064
+ this.fail("Expected a TeX argument");
1065
+ }
1066
+ if (this.peek() === "}")
1067
+ this.fail("Unexpected closing TeX group");
1068
+ const argument = this.parseAtom();
1069
+ if (argument)
1070
+ return argument;
1071
+ }
1072
+ }
1073
+ parseGroup() {
1074
+ this.expect("{");
1075
+ const nodes = this.parseRow();
1076
+ const result = row(nodes);
1077
+ if (this.peek() !== "}") {
1078
+ if (this.incomplete && this.done())
1079
+ return nodes.length ? result : placeholder();
1080
+ this.fail("Unclosed TeX group");
1081
+ }
1082
+ this.offset++;
1083
+ return result;
1084
+ }
1085
+ optionalArgument() {
1086
+ this.skipWhitespace();
1087
+ if (this.peek() !== "[")
1088
+ return;
1089
+ this.offset++;
1090
+ const nodes = this.parseRow(() => this.peek() === "]");
1091
+ const result = row(nodes);
1092
+ if (this.peek() !== "]") {
1093
+ if (this.incomplete && this.done())
1094
+ return nodes.length ? result : placeholder();
1095
+ this.fail('Expected "]"');
1096
+ }
1097
+ this.offset++;
1098
+ return result;
1099
+ }
1100
+ readTextGroup() {
1101
+ const group = this.readRawGroup(true);
1102
+ return (group.value || (group.closed ? "" : "□")).replace(/\\([A-Za-z@]+|.)/g, (match, command) => {
1103
+ if ("{}%#$&_ ".includes(command))
1104
+ return command;
1105
+ if (command === "textbackslash")
1106
+ return "\\";
1107
+ if (command === "!")
1108
+ return "";
1109
+ if (Object.hasOwn(spacing, command))
1110
+ return " ".repeat(Math.max(1, spacing[command]));
1111
+ return match;
1112
+ }).replaceAll("~", " ");
1113
+ }
1114
+ readRawGroup(preserveComments = false) {
1115
+ this.skipWhitespace();
1116
+ if (this.incomplete && this.done())
1117
+ return { value: "", closed: false };
1118
+ this.expect("{");
1119
+ let start = this.offset;
1120
+ const parts = [];
1121
+ let depth = 1;
1122
+ while (!this.done()) {
1123
+ const char = this.source[this.offset++];
1124
+ if (char === "%" && !preserveComments && !this.escaped(this.offset - 1)) {
1125
+ parts.push(this.source.slice(start, this.offset - 1));
1126
+ while (!this.done() && !/[\r\n]/.test(this.peek()))
1127
+ this.offset++;
1128
+ if (this.peek() === "\r")
1129
+ this.offset++;
1130
+ if (this.peek() === `
1131
+ `)
1132
+ this.offset++;
1133
+ start = this.offset;
1134
+ continue;
1135
+ }
1136
+ if (char === "{" && !this.escaped(this.offset - 1))
1137
+ depth++;
1138
+ else if (char === "}" && !this.escaped(this.offset - 1) && --depth === 0)
1139
+ return { value: parts.join("") + this.source.slice(start, this.offset - 1), closed: true };
1140
+ if (depth > MAX_NESTING_DEPTH)
1141
+ this.fail(`TeX nesting exceeds the ${MAX_NESTING_DEPTH}-level limit`);
1142
+ }
1143
+ if (this.incomplete)
1144
+ return { value: parts.join("") + this.source.slice(start), closed: false };
1145
+ this.fail("Unclosed TeX group", start);
1146
+ }
1147
+ readCommand() {
1148
+ this.expect("\\");
1149
+ if (this.done())
1150
+ return "\\";
1151
+ if (!/[A-Za-z@]/.test(this.peek()))
1152
+ return this.source[this.offset++];
1153
+ const start = this.offset;
1154
+ while (/[A-Za-z@]/.test(this.peek()))
1155
+ this.offset++;
1156
+ const result = this.source.slice(start, this.offset);
1157
+ if (this.peek() === " ")
1158
+ this.offset++;
1159
+ return result;
1160
+ }
1161
+ readDelimiter() {
1162
+ this.skipWhitespace();
1163
+ if (this.done()) {
1164
+ if (this.incomplete)
1165
+ return "";
1166
+ this.fail("Expected a TeX delimiter");
1167
+ }
1168
+ if (this.peek() === "}")
1169
+ this.fail("Unexpected closing TeX group");
1170
+ if (this.peek() === "\\") {
1171
+ const start = this.offset;
1172
+ const command = this.readCommand();
1173
+ if (Object.hasOwn(delimiters, command))
1174
+ return (delimiters[`\\${command}`] ?? delimiters[command]) + this.readCombiningSuffix();
1175
+ if (this.incomplete && this.done() && this.offset === start + 1)
1176
+ return "";
1177
+ if (this.strict)
1178
+ this.fail(`Unsupported delimiter \\${command}`, start);
1179
+ return command + this.readCombiningSuffix();
1180
+ }
1181
+ const token = this.peek();
1182
+ if (Object.hasOwn(delimiters, token)) {
1183
+ this.offset++;
1184
+ return delimiters[token] + this.readCombiningSuffix();
1185
+ }
1186
+ if (this.strict)
1187
+ this.fail(`Unsupported delimiter ${token}`);
1188
+ return this.readLiteral();
1189
+ }
1190
+ readLiteral() {
1191
+ const part = this.graphemes.containing(this.offset);
1192
+ const literal = part.segment.slice(this.offset - part.index);
1193
+ const boundary = literal.search(/[\\{}[\]^_~&%\s]/u);
1194
+ const value = boundary > 0 ? literal.slice(0, boundary) : literal;
1195
+ this.offset += value.length;
1196
+ return value;
1197
+ }
1198
+ readCombiningSuffix() {
1199
+ if (this.done())
1200
+ return "";
1201
+ const part = this.graphemes.containing(this.offset);
1202
+ const suffix = part.segment.slice(this.offset - part.index).match(/^\p{Mark}+/u)?.[0] ?? "";
1203
+ this.offset += suffix.length;
1204
+ return suffix;
1205
+ }
1206
+ skipOptionalRowSpacing() {
1207
+ this.skipWhitespace();
1208
+ if (this.peek() !== "[")
1209
+ return;
1210
+ while (!this.done() && this.source[this.offset++] !== "]") {}
1211
+ }
1212
+ isCommand(name) {
1213
+ if (!this.source.startsWith(`\\${name}`, this.offset))
1214
+ return false;
1215
+ return !/[A-Za-z@]/.test(this.source[this.offset + name.length + 1] ?? "");
1216
+ }
1217
+ skipWhitespace() {
1218
+ while (!this.done()) {
1219
+ if (/\s/.test(this.peek())) {
1220
+ this.offset++;
1221
+ continue;
1222
+ }
1223
+ if (this.peek() !== "%")
1224
+ return;
1225
+ while (!this.done() && !/[\r\n]/.test(this.peek()))
1226
+ this.offset++;
1227
+ }
1228
+ }
1229
+ done() {
1230
+ return this.offset >= this.source.length;
1231
+ }
1232
+ peek() {
1233
+ return this.source[this.offset] ?? "";
1234
+ }
1235
+ expect(value) {
1236
+ if (!this.source.startsWith(value, this.offset))
1237
+ this.fail(`Expected "${value}"`);
1238
+ this.offset += value.length;
1239
+ }
1240
+ escaped(index) {
1241
+ let count = 0;
1242
+ while (index > count && this.source[index - count - 1] === "\\")
1243
+ count++;
1244
+ return count % 2 === 1;
1245
+ }
1246
+ fail(message, offset = this.offset) {
1247
+ throw new Error(`${message} at offset ${offset}`);
1248
+ }
1249
+ }
1250
+ function row(body) {
1251
+ return body.length === 1 ? body[0] : { type: "row", body };
1252
+ }
1253
+ function placeholder() {
1254
+ return { type: "symbol", value: "□" };
1255
+ }
1256
+ function unwrapStyle(node) {
1257
+ while (node.type === "variant" || node.type === "color")
1258
+ node = node.body;
1259
+ return node;
1260
+ }
1261
+ function inferRole(value) {
1262
+ if ("+-*/×÷±∓".includes(value))
1263
+ return "binary";
1264
+ if ("=<>≤≥≠≈∈∉⊂⊃".includes(value))
1265
+ return "relation";
1266
+ if (",;:".includes(value))
1267
+ return "punctuation";
1268
+ if ("([{".includes(value))
1269
+ return "opening";
1270
+ if (")]}".includes(value))
1271
+ return "closing";
1272
+ return "ordinary";
1273
+ }
1274
+ function negate(value) {
1275
+ return {
1276
+ "=": "≠",
1277
+ "<": "≮",
1278
+ ">": "≯",
1279
+ "≤": "≰",
1280
+ "≥": "≱",
1281
+ "∈": "∉",
1282
+ "∋": "∌",
1283
+ "⊂": "⊄",
1284
+ "⊃": "⊅",
1285
+ "≡": "≢",
1286
+ "≈": "≉",
1287
+ "∼": "≁",
1288
+ "⊆": "⊈",
1289
+ "⊇": "⊉",
1290
+ "∣": "∤",
1291
+ "∥": "∦"
1292
+ }[value] ?? `${value}̸`;
1293
+ }
1294
+
1295
+ // src/unicode-tex-backend.ts
1296
+ import stringWidth2 from "string-width";
1297
+ var UNICODE_TEX_SOURCE_LENGTH_MAX = 4096;
1298
+ var OUTPUT_LENGTH_MAX = 16384;
1299
+
1300
+ class UnicodeTexBackend {
1301
+ renderSync(request) {
1302
+ return renderUnicode(request, false);
1303
+ }
1304
+ async render(request) {
1305
+ return this.renderSync(request);
1306
+ }
1307
+ }
1308
+ function renderIncompleteUnicode(request) {
1309
+ return renderUnicode(request, true);
1310
+ }
1311
+ function renderUnicode(request, incomplete) {
1312
+ assertNotAborted(request.signal);
1313
+ const sourceBytes = Buffer.byteLength(request.formula, "utf8");
1314
+ if (sourceBytes === 0 || sourceBytes > UNICODE_TEX_SOURCE_LENGTH_MAX) {
1315
+ throw new Error(`TeX formula must be between 1 and ${UNICODE_TEX_SOURCE_LENGTH_MAX} UTF-8 bytes`);
1316
+ }
1317
+ if (!Number.isFinite(request.widthMax) || !Number.isFinite(request.heightMax) || request.widthMax < 1 || request.heightMax < 1) {
1318
+ throw new Error("Unicode TeX dimensions must be finite positive numbers");
1319
+ }
1320
+ const widthMax = Math.floor(request.widthMax);
1321
+ const heightMax = Math.floor(request.heightMax);
1322
+ const options = { strict: request.strict };
1323
+ const node = incomplete ? parseMathIncomplete(request.formula, options) : parseMath(request.formula, options);
1324
+ const output = boxToOutput(layoutMath(node, request.display), widthMax, heightMax);
1325
+ assertNotAborted(request.signal);
1326
+ if (!output.text)
1327
+ throw new Error("TeX formula produced no Unicode output");
1328
+ if (output.text.length > OUTPUT_LENGTH_MAX)
1329
+ throw new Error(`Unicode TeX output exceeds ${OUTPUT_LENGTH_MAX} characters`);
1330
+ const lines = output.text.split(`
1331
+ `);
1332
+ return {
1333
+ kind: "unicode",
1334
+ ...output,
1335
+ columns: Math.max(1, ...lines.map((line) => stringWidth2(line))),
1336
+ rows: lines.length
1337
+ };
1338
+ }
1339
+ function assertNotAborted(signal) {
1340
+ if (signal.aborted)
1341
+ throw signal.reason ?? new Error("Unicode render cancelled");
1342
+ }
1343
+
1344
+ // src/tex-renderable.ts
1345
+ var NATIVE_SUPERSAMPLE = 4;
1346
+ var RESIZE_AREA_THRESHOLD = 1.3;
1347
+ var DEFAULT_PREVIEW_BACKEND = new UnicodeTexBackend;
1348
+ var UNICODE_RENDER = UnicodeTexBackend.prototype.render;
1349
+ var UNICODE_RENDER_SYNC = UnicodeTexBackend.prototype.renderSync;
1350
+ function dimensionMax(value) {
1351
+ if (!Number.isFinite(value))
1352
+ throw new Error("TeX dimensions must be finite");
1353
+ return Math.max(1, Math.floor(value));
1354
+ }
1355
+ function measureTex(width, height, display, widthMax, heightMax) {
1356
+ let rows = Math.max(1, Math.ceil(height / NATIVE_SUPERSAMPLE / (display ? 10 : 12)));
1357
+ let columns = Math.max(1, Math.round(width / height * rows * 2));
1358
+ if (columns > widthMax) {
1359
+ rows = Math.max(1, Math.round(rows * widthMax / columns));
1360
+ columns = widthMax;
1361
+ }
1362
+ if (rows > heightMax) {
1363
+ columns = Math.max(1, Math.round(columns * heightMax / rows));
1364
+ rows = heightMax;
1365
+ }
1366
+ return { columns, rows };
1367
+ }
1368
+ function fitImageToPlacement(imageWidth, imageHeight, columns, rows, cellPxWidth, cellPxHeight) {
1369
+ const boxWidth = Math.max(1, Math.floor(columns * cellPxWidth));
1370
+ const boxHeight = Math.max(1, Math.floor(rows * cellPxHeight));
1371
+ const scale = Math.min(boxWidth / imageWidth, boxHeight / imageHeight);
1372
+ if (scale >= 1)
1373
+ return null;
1374
+ const width = Math.max(1, Math.round(imageWidth * scale));
1375
+ const height = Math.max(1, Math.round(imageHeight * scale));
1376
+ return imageWidth * imageHeight > width * height * RESIZE_AREA_THRESHOLD ? { width, height } : null;
1377
+ }
1378
+
1379
+ class TexRenderable extends BoxRenderable {
1380
+ ready;
1381
+ backend;
1382
+ fallback;
1383
+ strict;
1384
+ widthMax;
1385
+ heightMax;
1386
+ autoWidth;
1387
+ autoHeight;
1388
+ requestedAlignSelf;
1389
+ currentDimensions = { columns: 1, rows: 1 };
1390
+ imageOptions;
1391
+ onError;
1392
+ _formula;
1393
+ _foreground;
1394
+ _background;
1395
+ _display;
1396
+ _streaming;
1397
+ controller = null;
1398
+ committedOutput = null;
1399
+ constructor(context, options) {
1400
+ const {
1401
+ formula,
1402
+ display = false,
1403
+ foreground,
1404
+ background,
1405
+ widthMax = 80,
1406
+ heightMax = 24,
1407
+ backend,
1408
+ fallback = "message",
1409
+ imageOptions,
1410
+ onError,
1411
+ streaming = false,
1412
+ strict = false,
1413
+ ...boxOptions
1414
+ } = options;
1415
+ super(context, {
1416
+ shouldFill: false,
1417
+ ...boxOptions,
1418
+ flexShrink: options.flexShrink ?? (typeof options.width === "string" && typeof options.height === "string" ? 1 : 0),
1419
+ width: options.width ?? "auto",
1420
+ height: options.height ?? "auto"
1421
+ });
1422
+ this._formula = formula;
1423
+ this._foreground = foreground;
1424
+ this._background = background;
1425
+ this._streaming = streaming;
1426
+ this._display = display;
1427
+ this.backend = backend;
1428
+ this.fallback = fallback;
1429
+ this.strict = strict;
1430
+ this.widthMax = dimensionMax(widthMax);
1431
+ this.heightMax = dimensionMax(heightMax);
1432
+ this.autoWidth = options.width == null || options.width === "auto";
1433
+ this.autoHeight = options.height == null || options.height === "auto";
1434
+ this.requestedAlignSelf = this.yogaNode.getAlignSelf();
1435
+ this.imageOptions = imageOptions;
1436
+ this.onError = onError;
1437
+ this.ready = this.update(formula, foreground, background, display);
1438
+ }
1439
+ get formula() {
1440
+ return this._formula;
1441
+ }
1442
+ set formula(value) {
1443
+ this.setSnapshot(value, this._foreground, this._background);
1444
+ }
1445
+ get display() {
1446
+ return this._display;
1447
+ }
1448
+ set display(value) {
1449
+ this.setSnapshot(this._formula, this._foreground, this._background, value === true);
1450
+ }
1451
+ get width() {
1452
+ return super.width;
1453
+ }
1454
+ set width(value) {
1455
+ super.width = value ?? "auto";
1456
+ this.autoWidth = value === "auto" || value == null;
1457
+ for (const child of this.getChildren())
1458
+ child.width = this.autoWidth ? this.currentDimensions.columns : "100%";
1459
+ }
1460
+ get height() {
1461
+ return super.height;
1462
+ }
1463
+ set height(value) {
1464
+ super.height = value ?? "auto";
1465
+ this.autoHeight = value === "auto" || value == null;
1466
+ for (const child of this.getChildren())
1467
+ child.height = this.autoHeight ? this.currentDimensions.rows : "100%";
1468
+ }
1469
+ set alignSelf(value) {
1470
+ super.alignSelf = value;
1471
+ this.requestedAlignSelf = this.yogaNode.getAlignSelf();
1472
+ }
1473
+ onLifecyclePass = () => {
1474
+ if (!this.parent)
1475
+ return;
1476
+ const crossAuto = this.parent.primaryAxis === "column" ? this.autoWidth : this.autoHeight;
1477
+ const alignment = this.requestedAlignSelf === Yoga.Align.Auto ? this.parent.getLayoutNode().getAlignItems() : this.requestedAlignSelf;
1478
+ const resolved = crossAuto && alignment === Yoga.Align.Stretch ? Yoga.Align.FlexStart : this.requestedAlignSelf;
1479
+ if (this.yogaNode.getAlignSelf() !== resolved)
1480
+ this.yogaNode.setAlignSelf(resolved);
1481
+ for (const child of this.getChildren()) {
1482
+ const options = child instanceof ImageRenderable ? this.imageOptions : undefined;
1483
+ const node = child.getLayoutNode();
1484
+ const shrink = options?.flexShrink ?? 1;
1485
+ const flexible = shrink > 0 && node.getPositionType() !== Yoga.PositionType.Absolute;
1486
+ node.setFlexShrink(shrink);
1487
+ if (options?.maxWidth === undefined)
1488
+ node.setMaxWidth(this.primaryAxis === "row" && flexible ? undefined : "100%");
1489
+ if (options?.maxHeight === undefined)
1490
+ node.setMaxHeight(this.primaryAxis === "column" && flexible ? undefined : "100%");
1491
+ }
1492
+ };
1493
+ get streaming() {
1494
+ return this._streaming;
1495
+ }
1496
+ set streaming(value) {
1497
+ if (value === this._streaming)
1498
+ return;
1499
+ this._streaming = value;
1500
+ if (value) {
1501
+ this.controller?.abort();
1502
+ this.controller = null;
1503
+ this.ready = Promise.resolve();
1504
+ return;
1505
+ }
1506
+ this.ready = this.update(this._formula, this._foreground, this._background, this._display);
1507
+ }
1508
+ setColors(foreground, background) {
1509
+ this.setSnapshot(this._formula, foreground, background);
1510
+ }
1511
+ setSnapshot(formula, foreground, background, display = this._display) {
1512
+ if (formula === this._formula && foreground === this._foreground && background === this._background && display === this._display)
1513
+ return;
1514
+ this.ready = this.update(formula, foreground, background, display);
1515
+ }
1516
+ async whenReady() {
1517
+ while (!this.isDestroyed) {
1518
+ const pending = this.ready;
1519
+ const controller = this.controller;
1520
+ let onAbort;
1521
+ const changed = new Promise((resolve) => {
1522
+ if (controller) {
1523
+ onAbort = () => resolve();
1524
+ controller.signal.addEventListener("abort", onAbort, { once: true });
1525
+ }
1526
+ });
1527
+ try {
1528
+ await Promise.race([pending, changed]);
1529
+ } catch (error) {
1530
+ if (pending === this.ready)
1531
+ throw error;
1532
+ continue;
1533
+ } finally {
1534
+ if (onAbort)
1535
+ controller?.signal.removeEventListener("abort", onAbort);
1536
+ }
1537
+ if (pending === this.ready)
1538
+ return;
1539
+ }
1540
+ }
1541
+ async update(formula, foreground, background, display) {
1542
+ this.controller?.abort();
1543
+ this._formula = formula;
1544
+ this._foreground = foreground;
1545
+ this._background = background;
1546
+ this._display = display;
1547
+ if (!formula) {
1548
+ this.clearOutput();
1549
+ this.currentDimensions = { columns: 1, rows: 1 };
1550
+ this.yogaNode.setMeasureFunc(() => ({ width: 1, height: 1 }));
1551
+ if (!this._streaming) {
1552
+ disposeOutput(this.committedOutput);
1553
+ this.committedOutput = null;
1554
+ }
1555
+ this.controller = null;
1556
+ return;
1557
+ }
1558
+ const controller = new AbortController;
1559
+ this.controller = controller;
1560
+ const request = {
1561
+ formula,
1562
+ display,
1563
+ foreground,
1564
+ background,
1565
+ widthMax: this.widthMax,
1566
+ heightMax: this.heightMax,
1567
+ signal: controller.signal,
1568
+ strict: this.strict
1569
+ };
1570
+ if (this._streaming) {
1571
+ try {
1572
+ this.applyOutput(this.previewOutput(request));
1573
+ } catch {
1574
+ this.applyOutput(rawSourceOutput(formula, this.widthMax, this.heightMax));
1575
+ }
1576
+ return;
1577
+ }
1578
+ let unicodeOutput = null;
1579
+ const synchronousBackend = this.backend instanceof UnicodeTexBackend && this.backend.render === UNICODE_RENDER && this.backend.renderSync === UNICODE_RENDER_SYNC ? this.backend : null;
1580
+ if (!synchronousBackend) {
1581
+ try {
1582
+ const preview = DEFAULT_PREVIEW_BACKEND.renderSync(request);
1583
+ this.applyOutput(preview);
1584
+ unicodeOutput = preview;
1585
+ } catch {}
1586
+ }
1587
+ try {
1588
+ const output = synchronousBackend ? synchronousBackend.renderSync(request) : await this.backend.render(request);
1589
+ if (controller.signal.aborted || this.isDestroyed) {
1590
+ disposeOutput(output);
1591
+ return;
1592
+ }
1593
+ if (this.fallback === "retain") {
1594
+ const previous = this.committedOutput;
1595
+ try {
1596
+ this.applyOutput(output);
1597
+ } catch (error) {
1598
+ disposeOutput(output);
1599
+ throw error;
1600
+ }
1601
+ this.committedOutput = output;
1602
+ disposeOutput(previous);
1603
+ } else {
1604
+ try {
1605
+ this.applyOutput(output);
1606
+ } finally {
1607
+ disposeOutput(output);
1608
+ }
1609
+ }
1610
+ } catch (error) {
1611
+ if (synchronousBackend)
1612
+ await Promise.resolve();
1613
+ if (!controller.signal.aborted && !this.isDestroyed) {
1614
+ if (this.fallback === "retain" && this.committedOutput)
1615
+ this.applyOutput(this.committedOutput);
1616
+ else if (this.fallback === "message" || this.fallback === "unicode" && !unicodeOutput) {
1617
+ const message = error instanceof Error ? error.message : String(error);
1618
+ const text = fitLine(`[TeX error: ${message}]`, this.widthMax);
1619
+ this.applyOutput({ kind: "unicode", text, columns: Math.max(1, stringWidth3(text)), rows: 1 });
1620
+ }
1621
+ this.onError?.(error);
1622
+ if (this.fallback === "throw")
1623
+ throw error;
1624
+ }
1625
+ }
1626
+ }
1627
+ previewOutput(request) {
1628
+ if (request.formula.length <= UNICODE_TEX_SOURCE_LENGTH_MAX && Buffer.byteLength(request.formula, "utf8") <= UNICODE_TEX_SOURCE_LENGTH_MAX) {
1629
+ try {
1630
+ return DEFAULT_PREVIEW_BACKEND.renderSync(request);
1631
+ } catch {
1632
+ try {
1633
+ return renderIncompleteUnicode(request);
1634
+ } catch {}
1635
+ }
1636
+ }
1637
+ return rawSourceOutput(request.formula, this.widthMax, this.heightMax);
1638
+ }
1639
+ clearOutput() {
1640
+ for (const existing of this.getChildren())
1641
+ existing.destroyRecursively();
1642
+ }
1643
+ applyOutput(output) {
1644
+ const dimensions = output.kind === "image" ? measureTex(output.image.width, output.image.height, this.display, this.widthMax, this.heightMax) : { columns: Math.min(this.widthMax, output.columns), rows: Math.min(this.heightMax, output.rows) };
1645
+ const child = output.kind === "image" ? this.createImageChild(output.image, dimensions) : new TextRenderable(this._ctx, {
1646
+ content: output.spans ? new StyledText(output.spans.map((span) => ({
1647
+ __isChunk: true,
1648
+ text: span.text,
1649
+ fg: span.color === undefined ? undefined : parseColor(span.color),
1650
+ attributes: createTextAttributes({ bold: span.bold, italic: span.italic })
1651
+ }))) : output.text,
1652
+ fg: this._foreground,
1653
+ bg: this._background,
1654
+ wrapMode: "none",
1655
+ width: this.autoWidth ? dimensions.columns : "100%",
1656
+ height: this.autoHeight ? dimensions.rows : "100%",
1657
+ maxWidth: "100%",
1658
+ maxHeight: "100%"
1659
+ });
1660
+ let added = false;
1661
+ try {
1662
+ this.clearOutput();
1663
+ this.currentDimensions = dimensions;
1664
+ this.yogaNode.unsetMeasureFunc();
1665
+ this.add(child);
1666
+ added = true;
1667
+ } finally {
1668
+ if (!added)
1669
+ child.destroyRecursively();
1670
+ }
1671
+ }
1672
+ createImageChild(image, dimensions) {
1673
+ const resized = this.resizeToPlacement(image, dimensions);
1674
+ const source = resized ?? image;
1675
+ if (this._ctx.capabilities?.kitty_graphics) {
1676
+ try {
1677
+ source.ensureEncodedPng();
1678
+ } catch {}
1679
+ }
1680
+ try {
1681
+ return new ImageRenderable(this._ctx, {
1682
+ protocol: "auto",
1683
+ fit: "fit",
1684
+ maxWidth: "100%",
1685
+ maxHeight: "100%",
1686
+ ...this.imageOptions,
1687
+ source,
1688
+ width: this.autoWidth ? dimensions.columns : "100%",
1689
+ height: this.autoHeight ? dimensions.rows : "100%"
1690
+ });
1691
+ } finally {
1692
+ resized?.dispose();
1693
+ }
1694
+ }
1695
+ resizeToPlacement(image, dimensions) {
1696
+ const { terminalWidth, terminalHeight, resolution } = this._ctx;
1697
+ if (!terminalWidth || !terminalHeight || !resolution?.width || !resolution.height)
1698
+ return null;
1699
+ const target = fitImageToPlacement(image.width, image.height, dimensions.columns, dimensions.rows, resolution.width / terminalWidth, resolution.height / terminalHeight);
1700
+ if (!target)
1701
+ return null;
1702
+ try {
1703
+ return image.resize({ ...target, kernel: "area" });
1704
+ } catch {
1705
+ return null;
1706
+ }
1707
+ }
1708
+ destroySelf() {
1709
+ this.controller?.abort();
1710
+ this.controller = null;
1711
+ disposeOutput(this.committedOutput);
1712
+ this.committedOutput = null;
1713
+ super.destroySelf();
1714
+ }
1715
+ }
1716
+ function disposeOutput(output) {
1717
+ if (output?.kind === "image")
1718
+ output.image.dispose();
1719
+ }
1720
+ function fitLine(value, widthMax) {
1721
+ let output = "";
1722
+ for (const character of value) {
1723
+ if (stringWidth3(output + character) > widthMax)
1724
+ break;
1725
+ output += character;
1726
+ }
1727
+ return output || "?";
1728
+ }
1729
+ function rawSourceOutput(source, widthMax, heightMax) {
1730
+ const cellLimit = Math.min(16384, widthMax * heightMax);
1731
+ const tailStart = Math.max(0, source.length - cellLimit * 2);
1732
+ let tail = source.slice(tailStart);
1733
+ if (tailStart > 0) {
1734
+ const restart = safeRawRestart(tail);
1735
+ tail = restart < 0 ? "" : tail.slice(restart);
1736
+ }
1737
+ const tailGraphemes = Array.from(graphemeSegmenter.segment(tail), (part) => part.segment);
1738
+ const lines = [];
1739
+ let line = "";
1740
+ let width = 0;
1741
+ for (const sourceGrapheme of tailGraphemes) {
1742
+ const visible2 = [...sourceGrapheme].map(visibleSourceCharacter).join("");
1743
+ const segments = visible2 === sourceGrapheme ? [visible2] : [...visible2];
1744
+ for (const character of segments) {
1745
+ const characterWidth = stringWidth3(character);
1746
+ if (characterWidth > widthMax) {
1747
+ if (line)
1748
+ lines.push(line);
1749
+ lines.push("?");
1750
+ line = "";
1751
+ width = 0;
1752
+ } else if (width + characterWidth > widthMax) {
1753
+ lines.push(line);
1754
+ line = character;
1755
+ width = characterWidth;
1756
+ } else {
1757
+ line += character;
1758
+ width += characterWidth;
1759
+ }
1760
+ }
1761
+ }
1762
+ if (line)
1763
+ lines.push(line);
1764
+ const visibleLines = lines.slice(-heightMax);
1765
+ const widths = visibleLines.map((value) => stringWidth3(value));
1766
+ const visible = visibleLines.some((value, index) => /\S/u.test(value) && widths[index] > 0);
1767
+ const text = visible ? visibleLines.join(`
1768
+ `) : "?";
1769
+ return {
1770
+ kind: "unicode",
1771
+ text,
1772
+ columns: visible ? Math.max(1, ...widths) : 1,
1773
+ rows: visible ? Math.max(1, visibleLines.length) : 1
1774
+ };
1775
+ }
1776
+ function safeRawRestart(value) {
1777
+ for (let index = 1;index < value.length; index++) {
1778
+ if (isPrintableAscii(value.charCodeAt(index - 1)) && isPrintableAscii(value.charCodeAt(index)))
1779
+ return index;
1780
+ }
1781
+ return -1;
1782
+ }
1783
+ function isPrintableAscii(value) {
1784
+ return value >= 32 && value <= 126;
1785
+ }
1786
+ function visibleSourceCharacter(character) {
1787
+ if (character === `
1788
+ `)
1789
+ return "\\n";
1790
+ if (character === "\r")
1791
+ return "\\r";
1792
+ if (character === "\t")
1793
+ return "\\t";
1794
+ const code = character.codePointAt(0);
1795
+ return code < 32 || code >= 127 && code <= 159 ? `\\x${code.toString(16).padStart(2, "0")}` : character;
1796
+ }
1797
+ export { UnicodeTexBackend, measureTex, TexRenderable };