@simonklee/opentui-tex 0.2.0 → 0.3.0

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,1434 @@
1
+ // src/tex-renderable.ts
2
+ import {
3
+ BoxRenderable,
4
+ ImageRenderable,
5
+ TextRenderable,
6
+ Yoga
7
+ } from "@opentui/core";
8
+ import stringWidth3 from "string-width";
9
+
10
+ // src/math-graphemes.ts
11
+ var graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
12
+
13
+ // src/math-layout.ts
14
+ import stringWidth from "string-width";
15
+ var CELL_LIMIT = 16384;
16
+ var superscript = {
17
+ "0": "⁰",
18
+ "1": "¹",
19
+ "2": "²",
20
+ "3": "³",
21
+ "4": "⁴",
22
+ "5": "⁵",
23
+ "6": "⁶",
24
+ "7": "⁷",
25
+ "8": "⁸",
26
+ "9": "⁹",
27
+ "+": "⁺",
28
+ "-": "⁻",
29
+ "=": "⁼",
30
+ "(": "⁽",
31
+ ")": "⁾",
32
+ n: "ⁿ",
33
+ i: "ⁱ"
34
+ };
35
+ var subscript = {
36
+ "0": "₀",
37
+ "1": "₁",
38
+ "2": "₂",
39
+ "3": "₃",
40
+ "4": "₄",
41
+ "5": "₅",
42
+ "6": "₆",
43
+ "7": "₇",
44
+ "8": "₈",
45
+ "9": "₉",
46
+ "+": "₊",
47
+ "-": "₋",
48
+ "=": "₌",
49
+ "(": "₍",
50
+ ")": "₎",
51
+ a: "ₐ",
52
+ e: "ₑ",
53
+ h: "ₕ",
54
+ i: "ᵢ",
55
+ j: "ⱼ",
56
+ k: "ₖ",
57
+ l: "ₗ",
58
+ m: "ₘ",
59
+ n: "ₙ",
60
+ o: "ₒ",
61
+ p: "ₚ",
62
+ r: "ᵣ",
63
+ s: "ₛ",
64
+ t: "ₜ",
65
+ u: "ᵤ",
66
+ v: "ᵥ",
67
+ x: "ₓ"
68
+ };
69
+ function layoutMath(node, displayMode) {
70
+ return layout(node, displayMode);
71
+ }
72
+ function boxToString(box, widthMax, heightMax) {
73
+ const lines = [];
74
+ for (let y = 0;y < Math.min(box.height, heightMax); y++) {
75
+ let line = "";
76
+ let width = 0;
77
+ for (let x = 0;x < box.width && width < widthMax; x++) {
78
+ const value = box.cells[y][x];
79
+ const cellWidth = value ? stringWidth(value) : 0;
80
+ if (cellWidth === 0) {
81
+ line += " ";
82
+ width++;
83
+ continue;
84
+ }
85
+ if (cellWidth > widthMax)
86
+ throw new Error(`Unicode glyph exceeds the ${widthMax}-column TeX width`);
87
+ if (width + cellWidth > widthMax)
88
+ break;
89
+ line += value;
90
+ width += cellWidth;
91
+ x += cellWidth - 1;
92
+ }
93
+ lines.push(line.trimEnd());
94
+ }
95
+ return lines.join(`
96
+ `).trimEnd();
97
+ }
98
+ function layout(node, display) {
99
+ switch (node.type) {
100
+ case "row":
101
+ return layoutRow(node.body, display);
102
+ case "symbol":
103
+ case "text":
104
+ case "operator":
105
+ return textBox(node.value);
106
+ case "space":
107
+ return blank(node.width, 1, 0);
108
+ case "fraction":
109
+ return fraction(layout(node.numerator, display), layout(node.denominator, display), node.bar);
110
+ case "root":
111
+ return root(layout(node.body, display), node.index ? layout(node.index, display) : undefined);
112
+ case "scripts":
113
+ return scripts(node, display);
114
+ case "delimited":
115
+ return delimited(node.left, layout(node.body, display), node.right);
116
+ case "matrix":
117
+ return matrix(node.rows, node.environment, display);
118
+ case "accent":
119
+ return accent(node.accent, layout(node.body, display));
120
+ case "overunder":
121
+ return overUnder(layout(node.base, display), node.over ? layout(node.over, display) : undefined, node.under ? layout(node.under, display) : undefined);
122
+ }
123
+ }
124
+ function layoutRow(nodes, display) {
125
+ const boxes = [];
126
+ let previous;
127
+ for (let index = 0;index < nodes.length; index++) {
128
+ const role = normalizedRole(roleOf(nodes[index]), previous, nextRole(nodes, index + 1));
129
+ if (needsSpace(previous, role, boxes.length))
130
+ boxes.push(blank(1, 1, 0));
131
+ boxes.push(layout(nodes[index], display));
132
+ if (nodes[index].type !== "space")
133
+ previous = role ?? "ordinary";
134
+ }
135
+ return hpack(boxes);
136
+ }
137
+ function fraction(top, bottom, bar) {
138
+ const width = Math.max(top.width, bottom.width) + 2;
139
+ const result = blank(width, top.height + bottom.height + 1, top.height);
140
+ overlay(result, top, Math.floor((width - top.width) / 2), 0);
141
+ if (bar)
142
+ horizontal(result, top.height, width, "─");
143
+ overlay(result, bottom, Math.floor((width - bottom.width) / 2), top.height + 1);
144
+ return result;
145
+ }
146
+ function root(body, index) {
147
+ const indexWidth = index ? Math.max(0, index.width - 1) : 0;
148
+ const bodyX = indexWidth + 2;
149
+ const bodyY = index?.height ?? 1;
150
+ const result = blank(bodyX + body.width, body.height + bodyY, body.baseline + bodyY);
151
+ set(result, bodyX - 1, bodyY - 1, "╭");
152
+ for (let x = bodyX;x < result.width; x++)
153
+ set(result, x, bodyY - 1, "─");
154
+ set(result, bodyX - 2, result.baseline, "√");
155
+ overlay(result, body, bodyX, bodyY);
156
+ if (index)
157
+ overlay(result, index, 0, 0);
158
+ return result;
159
+ }
160
+ function scripts(node, display) {
161
+ const base = layout(node.base, display);
162
+ const supText = node.superscript ? simpleText(node.superscript) : undefined;
163
+ const subText = node.subscript ? simpleText(node.subscript) : undefined;
164
+ const limits = node.base.type === "operator" && (node.base.limits === true || node.base.limits === "display" && display);
165
+ if (!limits) {
166
+ const mappedSup = supText === undefined ? undefined : mapScript(supText, superscript);
167
+ const mappedSub = subText === undefined ? undefined : mapScript(subText, subscript);
168
+ if ((!node.superscript || mappedSup !== undefined) && (!node.subscript || mappedSub !== undefined)) {
169
+ return hpack([base, textBox((mappedSup ?? "") + (mappedSub ?? ""))]);
170
+ }
171
+ }
172
+ if (limits)
173
+ return overUnder(base, node.superscript ? layout(node.superscript, display) : undefined, node.subscript ? layout(node.subscript, display) : undefined);
174
+ const sup = node.superscript ? layout(node.superscript, display) : undefined;
175
+ const sub = node.subscript ? layout(node.subscript, display) : undefined;
176
+ const sideWidth = Math.max(sup?.width ?? 0, sub?.width ?? 0);
177
+ const result = blank(base.width + sideWidth, (sup?.height ?? 0) + base.height + (sub?.height ?? 0), (sup?.height ?? 0) + base.baseline);
178
+ overlay(result, base, 0, sup?.height ?? 0);
179
+ if (sup)
180
+ overlay(result, sup, base.width, 0);
181
+ if (sub)
182
+ overlay(result, sub, base.width, (sup?.height ?? 0) + base.height);
183
+ return result;
184
+ }
185
+ function overUnder(base, over, under) {
186
+ const width = Math.max(base.width, over?.width ?? 0, under?.width ?? 0);
187
+ const result = blank(width, (over?.height ?? 0) + base.height + (under?.height ?? 0), (over?.height ?? 0) + base.baseline);
188
+ if (over)
189
+ overlay(result, over, Math.floor((width - over.width) / 2), 0);
190
+ overlay(result, base, Math.floor((width - base.width) / 2), over?.height ?? 0);
191
+ if (under)
192
+ overlay(result, under, Math.floor((width - under.width) / 2), (over?.height ?? 0) + base.height);
193
+ return result;
194
+ }
195
+ function delimited(left, body, right) {
196
+ return hpack([delimiter(left, body.height, body.baseline, true), body, delimiter(right, body.height, body.baseline, false)]);
197
+ }
198
+ function matrix(rows, environment, display) {
199
+ const cells = rows.map((row) => row.map((node) => layout(node, display)));
200
+ const columns = Math.max(0, ...cells.map((row) => row.length));
201
+ const widths = Array.from({ length: columns }, (_, x) => Math.max(0, ...cells.map((row) => row[x]?.width ?? 0)));
202
+ const ascents = cells.map((row) => Math.max(0, ...row.map((cell) => cell.baseline)));
203
+ const descents = cells.map((row) => Math.max(0, ...row.map((cell) => cell.height - cell.baseline - 1)));
204
+ const heights = ascents.map((value, y2) => value + descents[y2] + 1);
205
+ const gap = environment === "cases" || environment === "aligned" || environment === "align" ? 2 : 1;
206
+ const width = widths.reduce((sum, value) => sum + value, 0) + Math.max(0, columns - 1) * gap;
207
+ const height = Math.max(1, heights.reduce((sum, value) => sum + value, 0));
208
+ const result = blank(width, height, Math.floor(height / 2));
209
+ let y = 0;
210
+ for (let rowIndex = 0;rowIndex < cells.length; rowIndex++) {
211
+ let x = 0;
212
+ for (let column = 0;column < columns; column++) {
213
+ const cell = cells[rowIndex][column];
214
+ if (cell) {
215
+ const aligned = environment === "aligned" || environment === "align" || environment === "cases";
216
+ const offset = aligned ? column % 2 === 0 ? widths[column] - cell.width : 0 : Math.floor((widths[column] - cell.width) / 2);
217
+ overlay(result, cell, x + offset, y + ascents[rowIndex] - cell.baseline);
218
+ }
219
+ x += widths[column] + gap;
220
+ }
221
+ y += heights[rowIndex];
222
+ }
223
+ const pair = matrixDelimiters(environment);
224
+ return pair ? delimited(pair[0], result, pair[1]) : result;
225
+ }
226
+ function accent(kind, body) {
227
+ if (kind === "underline") {
228
+ const result2 = blank(body.width, body.height + 1, body.baseline);
229
+ overlay(result2, body, 0, 0);
230
+ horizontal(result2, body.height, body.width, "─");
231
+ return result2;
232
+ }
233
+ const result = blank(body.width, body.height + 1, body.baseline + 1);
234
+ overlay(result, body, 0, 1);
235
+ const mark = kind === "hat" || kind === "widehat" ? body.width === 1 ? "^" : "⌢" : kind === "bar" || kind === "overline" ? "─" : kind === "vec" ? "→" : kind === "tilde" ? "~" : kind === "dot" ? "·" : "¨";
236
+ if (kind === "bar" || kind === "overline")
237
+ horizontal(result, 0, body.width, mark);
238
+ else
239
+ set(result, Math.max(0, Math.floor((body.width - stringWidth(mark)) / 2)), 0, mark);
240
+ return result;
241
+ }
242
+ function delimiter(value, height, baseline, left) {
243
+ const base = value.replace(/\p{Mark}+$/u, "");
244
+ if (!base)
245
+ return blank(0, height, baseline);
246
+ if (height === 1)
247
+ return textBox(value);
248
+ const glyphs = delimiterGlyphs(base, left);
249
+ const suffix = value.slice(base.length);
250
+ const result = blank(Math.max(...glyphs.map((glyph) => stringWidth(glyph + suffix))), height, baseline);
251
+ for (let y = 0;y < height; y++)
252
+ set(result, 0, y, y === 0 ? glyphs[0] : y === height - 1 ? glyphs[2] : glyphs[1]);
253
+ if ((base === "{" || base === "}") && height >= 3)
254
+ set(result, 0, Math.floor(height / 2), left ? "⎨" : "⎬");
255
+ if (suffix)
256
+ set(result, 0, baseline, result.cells[baseline][0] + suffix);
257
+ return result;
258
+ }
259
+ function delimiterGlyphs(value, left) {
260
+ if (value === "(")
261
+ return ["⎛", "⎜", "⎝"];
262
+ if (value === ")")
263
+ return ["⎞", "⎟", "⎠"];
264
+ if (value === "[")
265
+ return ["⎡", "⎢", "⎣"];
266
+ if (value === "]")
267
+ return ["⎤", "⎥", "⎦"];
268
+ if (value === "{")
269
+ return ["⎧", "⎪", "⎩"];
270
+ if (value === "}")
271
+ return ["⎫", "⎪", "⎭"];
272
+ if (value === "⟨")
273
+ return ["/", "│", "\\"];
274
+ if (value === "⟩")
275
+ return ["\\", "│", "/"];
276
+ if (value === "⌊" || value === "⌋")
277
+ return ["│", "│", value];
278
+ if (value === "⌈" || value === "⌉")
279
+ return [value, "│", "│"];
280
+ return [value, value, value];
281
+ }
282
+ function matrixDelimiters(value) {
283
+ return value === "pmatrix" ? ["(", ")"] : value === "bmatrix" ? ["[", "]"] : value === "Bmatrix" ? ["{", "}"] : value === "vmatrix" ? ["│", "│"] : value === "Vmatrix" ? ["║", "║"] : value === "cases" ? ["{", ""] : undefined;
284
+ }
285
+ function textBox(text) {
286
+ const parts = Array.from(graphemeSegmenter.segment(text), (part) => part.segment);
287
+ const result = blank(parts.reduce((sum, part) => sum + stringWidth(part), 0), 1, 0);
288
+ let x = 0;
289
+ for (const part of parts) {
290
+ set(result, x, 0, part);
291
+ x += stringWidth(part);
292
+ }
293
+ return result;
294
+ }
295
+ function hpack(boxes) {
296
+ if (!boxes.length)
297
+ return blank(0, 1, 0);
298
+ const ascent = Math.max(...boxes.map((box) => box.baseline));
299
+ const descent = Math.max(...boxes.map((box) => box.height - box.baseline - 1));
300
+ const result = blank(boxes.reduce((sum, box) => sum + box.width, 0), ascent + descent + 1, ascent);
301
+ let x = 0;
302
+ for (const box of boxes) {
303
+ overlay(result, box, x, ascent - box.baseline);
304
+ x += box.width;
305
+ }
306
+ return result;
307
+ }
308
+ function blank(width, height, baseline) {
309
+ width = Math.max(0, width);
310
+ height = Math.max(1, height);
311
+ if (width * height > CELL_LIMIT)
312
+ throw new Error(`Unicode TeX output exceeds ${CELL_LIMIT} characters`);
313
+ return { width, height, baseline: Math.max(0, baseline), cells: Array.from({ length: height }, () => Array(width)) };
314
+ }
315
+ function overlay(target, source, x, y) {
316
+ for (let sy = 0;sy < source.height; sy++)
317
+ for (let sx = 0;sx < source.width; sx++)
318
+ if (source.cells[sy][sx])
319
+ target.cells[y + sy][x + sx] = source.cells[sy][sx];
320
+ }
321
+ function set(box, x, y, value) {
322
+ if (x >= 0 && y >= 0 && x < box.width && y < box.height)
323
+ box.cells[y][x] = value;
324
+ }
325
+ function horizontal(box, y, width, value) {
326
+ for (let x = 0;x < width; x++)
327
+ set(box, x, y, value);
328
+ }
329
+ function simpleText(node) {
330
+ if (node.type === "symbol" || node.type === "text" || node.type === "operator")
331
+ return node.value;
332
+ if (node.type === "row") {
333
+ const values = node.body.map(simpleText);
334
+ if (values.every((value) => value !== undefined))
335
+ return values.join("");
336
+ }
337
+ return;
338
+ }
339
+ function mapScript(value, table) {
340
+ let result = "";
341
+ for (const char of value) {
342
+ if (!table[char])
343
+ return;
344
+ result += table[char];
345
+ }
346
+ return result;
347
+ }
348
+ function roleOf(node) {
349
+ if (node.type === "symbol")
350
+ return node.role;
351
+ if (node.type === "operator" || node.type === "fraction" || node.type === "root" || node.type === "matrix")
352
+ return "operator";
353
+ if (node.type === "scripts")
354
+ return roleOf(node.base);
355
+ return;
356
+ }
357
+ function nextRole(nodes, start) {
358
+ for (let i = start;i < nodes.length; i++)
359
+ if (nodes[i].type !== "space")
360
+ return roleOf(nodes[i]) ?? "ordinary";
361
+ return;
362
+ }
363
+ function needsSpace(previous, current, count) {
364
+ return count > 0 && previous !== "opening" && previous !== "punctuation" && current !== "closing" && current !== "punctuation" && (previous === "binary" || previous === "relation" || previous === "operator" || current === "binary" || current === "relation" || current === "operator");
365
+ }
366
+ function normalizedRole(role, previous, next) {
367
+ return role === "binary" && (!previous || ["binary", "relation", "operator", "punctuation", "opening"].includes(previous) || !next || ["binary", "relation", "punctuation", "closing"].includes(next)) ? "ordinary" : role;
368
+ }
369
+
370
+ // src/math-symbols.ts
371
+ var ordinary = {
372
+ alpha: "α",
373
+ beta: "β",
374
+ gamma: "γ",
375
+ delta: "δ",
376
+ epsilon: "ϵ",
377
+ varepsilon: "ε",
378
+ zeta: "ζ",
379
+ eta: "η",
380
+ theta: "θ",
381
+ vartheta: "ϑ",
382
+ iota: "ι",
383
+ kappa: "κ",
384
+ lambda: "λ",
385
+ mu: "μ",
386
+ nu: "ν",
387
+ xi: "ξ",
388
+ omicron: "ο",
389
+ pi: "π",
390
+ varpi: "ϖ",
391
+ rho: "ρ",
392
+ varrho: "ϱ",
393
+ sigma: "σ",
394
+ varsigma: "ς",
395
+ tau: "τ",
396
+ upsilon: "υ",
397
+ phi: "ϕ",
398
+ varphi: "φ",
399
+ chi: "χ",
400
+ psi: "ψ",
401
+ omega: "ω",
402
+ Gamma: "Γ",
403
+ Delta: "Δ",
404
+ Theta: "Θ",
405
+ Lambda: "Λ",
406
+ Xi: "Ξ",
407
+ Pi: "Π",
408
+ Sigma: "Σ",
409
+ Upsilon: "Υ",
410
+ Phi: "Φ",
411
+ Psi: "Ψ",
412
+ Omega: "Ω",
413
+ infty: "∞",
414
+ partial: "∂",
415
+ nabla: "∇",
416
+ emptyset: "∅",
417
+ varnothing: "∅",
418
+ forall: "∀",
419
+ exists: "∃",
420
+ neg: "¬",
421
+ angle: "∠",
422
+ degree: "°",
423
+ prime: "′",
424
+ hbar: "ℏ",
425
+ ell: "ℓ",
426
+ Re: "ℜ",
427
+ Im: "ℑ",
428
+ aleph: "ℵ",
429
+ top: "⊤",
430
+ bot: "⊥",
431
+ checkmark: "✓"
432
+ };
433
+ var binary = {
434
+ pm: "±",
435
+ mp: "∓",
436
+ times: "×",
437
+ div: "÷",
438
+ cdot: "·",
439
+ ast: "∗",
440
+ star: "⋆",
441
+ circ: "∘",
442
+ bullet: "•",
443
+ oplus: "⊕",
444
+ ominus: "⊖",
445
+ otimes: "⊗",
446
+ oslash: "⊘",
447
+ odot: "⊙",
448
+ cap: "∩",
449
+ cup: "∪",
450
+ land: "∧",
451
+ wedge: "∧",
452
+ lor: "∨",
453
+ vee: "∨",
454
+ setminus: "∖"
455
+ };
456
+ var relation = {
457
+ ne: "≠",
458
+ neq: "≠",
459
+ equiv: "≡",
460
+ approx: "≈",
461
+ sim: "∼",
462
+ simeq: "≃",
463
+ cong: "≅",
464
+ propto: "∝",
465
+ le: "≤",
466
+ leq: "≤",
467
+ ge: "≥",
468
+ geq: "≥",
469
+ ll: "≪",
470
+ gg: "≫",
471
+ in: "∈",
472
+ notin: "∉",
473
+ ni: "∋",
474
+ subset: "⊂",
475
+ supset: "⊃",
476
+ subseteq: "⊆",
477
+ supseteq: "⊇",
478
+ parallel: "∥",
479
+ perp: "⊥",
480
+ vdash: "⊢",
481
+ models: "⊨",
482
+ leftarrow: "←",
483
+ gets: "←",
484
+ rightarrow: "→",
485
+ to: "→",
486
+ leftrightarrow: "↔",
487
+ Leftarrow: "⇐",
488
+ Rightarrow: "⇒",
489
+ Leftrightarrow: "⇔",
490
+ mapsto: "↦",
491
+ longleftarrow: "⟵",
492
+ longrightarrow: "⟶",
493
+ longleftrightarrow: "⟷",
494
+ uparrow: "↑",
495
+ downarrow: "↓",
496
+ updownarrow: "↕"
497
+ };
498
+ var punctuation = { ldots: "…", dots: "…", cdots: "⋯", vdots: "⋮", ddots: "⋱", colon: ":" };
499
+ function definitions(values, role) {
500
+ return Object.fromEntries(Object.entries(values).map(([name, value]) => [name, { value, role }]));
501
+ }
502
+ var symbols = {
503
+ ...definitions(ordinary, "ordinary"),
504
+ ...definitions(binary, "binary"),
505
+ ...definitions(relation, "relation"),
506
+ ...definitions(punctuation, "punctuation")
507
+ };
508
+ var operators = {
509
+ sum: "∑",
510
+ prod: "∏",
511
+ coprod: "∐",
512
+ int: "∫",
513
+ iint: "∬",
514
+ iiint: "∭",
515
+ oint: "∮",
516
+ bigcap: "⋂",
517
+ bigcup: "⋃",
518
+ bigvee: "⋁",
519
+ bigwedge: "⋀"
520
+ };
521
+ var namedOperators = new Set(["sin", "cos", "tan", "log", "ln", "exp", "lim", "min", "max", "sup", "inf", "det", "gcd", "ker"]);
522
+ var delimiters = {
523
+ "(": "(",
524
+ ")": ")",
525
+ "[": "[",
526
+ "]": "]",
527
+ "{": "{",
528
+ "}": "}",
529
+ "|": "│",
530
+ "\\|": "║",
531
+ lbrace: "{",
532
+ rbrace: "}",
533
+ vert: "│",
534
+ Vert: "║",
535
+ langle: "⟨",
536
+ rangle: "⟩",
537
+ lfloor: "⌊",
538
+ rfloor: "⌋",
539
+ lceil: "⌈",
540
+ rceil: "⌉",
541
+ ".": ""
542
+ };
543
+ var accents = {
544
+ hat: "hat",
545
+ widehat: "widehat",
546
+ bar: "bar",
547
+ overline: "overline",
548
+ underline: "underline",
549
+ vec: "vec",
550
+ tilde: "tilde",
551
+ widetilde: "tilde",
552
+ dot: "dot",
553
+ ddot: "ddot"
554
+ };
555
+ var spacing = { ",": 0, ":": 1, ";": 1, "!": 0, quad: 2, qquad: 4, enspace: 1, thinspace: 0 };
556
+
557
+ // src/math-parser.ts
558
+ var MAX_NESTING_DEPTH = 256;
559
+ var environments = new Set([
560
+ "matrix",
561
+ "pmatrix",
562
+ "bmatrix",
563
+ "Bmatrix",
564
+ "vmatrix",
565
+ "Vmatrix",
566
+ "cases",
567
+ "aligned",
568
+ "align",
569
+ "gathered",
570
+ "gather",
571
+ "smallmatrix",
572
+ "array"
573
+ ]);
574
+ function parseMath(source) {
575
+ return new Parser(source).parse();
576
+ }
577
+ function parseMathIncomplete(source) {
578
+ return new Parser(source, true).parse();
579
+ }
580
+
581
+ class Parser {
582
+ source;
583
+ incomplete;
584
+ offset = 0;
585
+ depth = 0;
586
+ graphemes;
587
+ constructor(source, incomplete = false) {
588
+ this.source = source;
589
+ this.incomplete = incomplete;
590
+ this.graphemes = graphemeSegmenter.segment(source);
591
+ }
592
+ parse() {
593
+ const result = row(this.parseRow());
594
+ this.skipWhitespace();
595
+ if (!this.done())
596
+ this.fail(this.peek() === "}" ? "Unexpected closing TeX group" : `Unexpected "${this.peek()}"`);
597
+ return result;
598
+ }
599
+ parseRow(stop) {
600
+ const body = [];
601
+ while (!this.done()) {
602
+ this.skipWhitespace();
603
+ if (this.done() || stop?.() || this.peek() === "}")
604
+ break;
605
+ const char = this.peek();
606
+ if (char === "^" || char === "_") {
607
+ this.offset++;
608
+ const argument = this.parseArgument();
609
+ const previous = body.pop() ?? row([]);
610
+ const scripts2 = previous.type === "scripts" ? previous : { type: "scripts", base: previous };
611
+ if (char === "^")
612
+ scripts2.superscript = argument;
613
+ else
614
+ scripts2.subscript = argument;
615
+ body.push(scripts2);
616
+ } else {
617
+ const atom = this.parseAtom(body.at(-1));
618
+ if (atom)
619
+ body.push(atom);
620
+ }
621
+ }
622
+ return body;
623
+ }
624
+ parseAtom(previous) {
625
+ this.depth++;
626
+ if (this.depth > MAX_NESTING_DEPTH)
627
+ this.fail(`TeX nesting exceeds the ${MAX_NESTING_DEPTH}-level limit`);
628
+ try {
629
+ if (this.peek() === "{")
630
+ return this.parseGroup();
631
+ if (this.peek() === "\\") {
632
+ const atom = this.parseCommand(previous);
633
+ if (atom && "value" in atom)
634
+ atom.value += this.readCombiningSuffix();
635
+ return atom;
636
+ }
637
+ if (this.peek() === "~") {
638
+ this.offset++;
639
+ return { type: "space", width: 1 };
640
+ }
641
+ const part = this.graphemes.containing(this.offset);
642
+ const literal = part.segment.slice(this.offset - part.index);
643
+ const boundary = literal.search(/[\\{}[\]^_~&\s]/u);
644
+ const value = boundary > 0 ? literal.slice(0, boundary) : literal;
645
+ this.offset += value.length;
646
+ return { type: "symbol", value, role: inferRole(value[0]) };
647
+ } finally {
648
+ this.depth--;
649
+ }
650
+ }
651
+ parseCommand(previous) {
652
+ const start = this.offset;
653
+ const command = this.readCommand();
654
+ if (command === "\\")
655
+ return row([]);
656
+ if (command === "begin")
657
+ return this.parseEnvironment();
658
+ if (command === "end")
659
+ this.fail("Unexpected \\end", start);
660
+ if (["frac", "dfrac", "tfrac", "cfrac"].includes(command)) {
661
+ return { type: "fraction", numerator: this.parseArgument(), denominator: this.parseArgument(), bar: true };
662
+ }
663
+ if (["binom", "dbinom", "tbinom"].includes(command)) {
664
+ return { type: "delimited", left: "(", body: { type: "fraction", numerator: this.parseArgument(), denominator: this.parseArgument(), bar: false }, right: ")" };
665
+ }
666
+ if (command === "sqrt") {
667
+ const index = this.optionalArgument();
668
+ return { type: "root", body: this.parseArgument(), ...index ? { index } : {} };
669
+ }
670
+ if (command === "left")
671
+ return this.parseLeftRight();
672
+ if (command === "right")
673
+ this.fail("Unexpected \\right", start);
674
+ if (command === "middle")
675
+ return { type: "symbol", value: this.readDelimiter() };
676
+ if (command in accents)
677
+ return { type: "accent", accent: accents[command], body: this.parseArgument() };
678
+ if (["mathrm", "mathbf", "mathit", "mathsf", "mathtt", "mathbb", "mathcal", "mathfrak"].includes(command)) {
679
+ return this.parseArgument();
680
+ }
681
+ if (["text", "textrm", "mbox"].includes(command)) {
682
+ return { type: "text", value: this.readRawGroup().replace(/\\([{}%#$&_])/g, "$1").replaceAll("~", " ") };
683
+ }
684
+ if (command === "operatorname")
685
+ return { type: "operator", value: this.readRawGroup(), limits: false };
686
+ if (command === "overset" || command === "stackrel") {
687
+ const over = this.parseArgument();
688
+ return { type: "overunder", over, base: this.parseArgument() };
689
+ }
690
+ if (command === "underset") {
691
+ const under = this.parseArgument();
692
+ return { type: "overunder", under, base: this.parseArgument() };
693
+ }
694
+ if (command === "not") {
695
+ const target = this.parseArgument();
696
+ if (target.type === "symbol")
697
+ return { ...target, value: negate(target.value) };
698
+ return { type: "row", body: [{ type: "symbol", value: "¬" }, target] };
699
+ }
700
+ if (command === "limits" || command === "nolimits") {
701
+ const base = previous?.type === "scripts" ? previous.base : previous;
702
+ if (base?.type === "operator")
703
+ base.limits = command === "limits";
704
+ return;
705
+ }
706
+ if (["displaystyle", "textstyle", "scriptstyle", "scriptscriptstyle"].includes(command))
707
+ return;
708
+ if (/^(?:big|Big|bigg|Bigg)[lrm]?$/.test(command))
709
+ return { type: "symbol", value: this.readDelimiter() };
710
+ if (command in spacing)
711
+ return { type: "space", width: spacing[command] };
712
+ if (command in symbols)
713
+ return { type: "symbol", ...symbols[command] };
714
+ if (command in operators)
715
+ return { type: "operator", value: operators[command], limits: command.includes("int") ? false : "display" };
716
+ if (namedOperators.has(command))
717
+ return { type: "operator", value: command, limits: ["lim", "min", "max"].includes(command) ? "display" : false };
718
+ if (command in delimiters)
719
+ return { type: "symbol", value: delimiters[`\\${command}`] ?? delimiters[command] };
720
+ if (["{", "}", "%", "#", "$", "&", "_", "backslash"].includes(command))
721
+ return { type: "symbol", value: command === "backslash" ? "\\" : command };
722
+ return { type: "text", value: `\\${command}` };
723
+ }
724
+ parseEnvironment() {
725
+ const rawName = this.readRawGroup();
726
+ const name = rawName.replace(/\*$/, "");
727
+ if (!environments.has(name))
728
+ this.fail(`Unsupported TeX environment: ${rawName}`);
729
+ if (name === "array") {
730
+ this.skipWhitespace();
731
+ if (this.peek() === "{")
732
+ this.readRawGroup();
733
+ }
734
+ const rows = [];
735
+ let cells = [];
736
+ while (!this.done()) {
737
+ this.skipWhitespace();
738
+ if (this.done())
739
+ break;
740
+ if (this.isEnd(rawName)) {
741
+ this.offset += `\\end{${rawName}}`.length;
742
+ if (cells.length || !rows.length)
743
+ rows.push(cells);
744
+ return { type: "matrix", rows, environment: name };
745
+ }
746
+ const start = this.offset;
747
+ cells.push(row(this.parseRow(() => this.peek() === "&" || this.source.startsWith("\\\\", this.offset) || this.source.startsWith("\\end{", this.offset))));
748
+ if (this.offset === start)
749
+ this.fail(`Unexpected token in ${rawName}`);
750
+ this.skipWhitespace();
751
+ if (this.peek() === "&") {
752
+ this.offset++;
753
+ this.skipWhitespace();
754
+ if (this.incomplete && this.done())
755
+ cells.push(placeholder());
756
+ continue;
757
+ }
758
+ if (this.source.startsWith("\\\\", this.offset)) {
759
+ this.offset += 2;
760
+ this.skipOptionalRowSpacing();
761
+ rows.push(cells);
762
+ cells = [];
763
+ continue;
764
+ }
765
+ }
766
+ if (this.incomplete) {
767
+ if (cells.length)
768
+ rows.push(cells);
769
+ else if (!rows.length)
770
+ rows.push([placeholder()]);
771
+ return { type: "matrix", rows, environment: name };
772
+ }
773
+ this.fail(`Unclosed TeX environment: ${rawName}`);
774
+ }
775
+ parseLeftRight() {
776
+ const left = this.readDelimiter();
777
+ const nodes = this.parseRow(() => this.isCommand("right"));
778
+ const body = row(nodes);
779
+ if (!this.isCommand("right")) {
780
+ if (this.incomplete && this.done())
781
+ return { type: "delimited", left, body: nodes.length ? body : placeholder(), right: "" };
782
+ this.fail("Missing \\right");
783
+ }
784
+ this.readCommand();
785
+ return { type: "delimited", left, body, right: this.readDelimiter() };
786
+ }
787
+ parseArgument() {
788
+ while (true) {
789
+ this.skipWhitespace();
790
+ if (this.done()) {
791
+ if (this.incomplete)
792
+ return placeholder();
793
+ this.fail("Expected a TeX argument");
794
+ }
795
+ if (this.peek() === "}")
796
+ this.fail("Unexpected closing TeX group");
797
+ const argument = this.peek() === "{" ? this.parseGroup() : this.parseAtom();
798
+ if (argument)
799
+ return argument;
800
+ }
801
+ }
802
+ parseGroup() {
803
+ this.expect("{");
804
+ const nodes = this.parseRow();
805
+ const result = row(nodes);
806
+ if (this.peek() !== "}") {
807
+ if (this.incomplete && this.done())
808
+ return nodes.length ? result : placeholder();
809
+ this.fail("Unclosed TeX group");
810
+ }
811
+ this.offset++;
812
+ return result;
813
+ }
814
+ optionalArgument() {
815
+ this.skipWhitespace();
816
+ if (this.peek() !== "[")
817
+ return;
818
+ this.offset++;
819
+ const nodes = this.parseRow(() => this.peek() === "]");
820
+ const result = row(nodes);
821
+ if (this.peek() !== "]") {
822
+ if (this.incomplete && this.done())
823
+ return nodes.length ? result : placeholder();
824
+ this.fail('Expected "]"');
825
+ }
826
+ this.offset++;
827
+ return result;
828
+ }
829
+ readRawGroup() {
830
+ this.skipWhitespace();
831
+ this.expect("{");
832
+ const start = this.offset;
833
+ let depth = 1;
834
+ while (!this.done()) {
835
+ const char = this.source[this.offset++];
836
+ if (char === "{" && !this.escaped(this.offset - 1))
837
+ depth++;
838
+ else if (char === "}" && !this.escaped(this.offset - 1) && --depth === 0)
839
+ return this.source.slice(start, this.offset - 1);
840
+ if (depth > MAX_NESTING_DEPTH)
841
+ this.fail(`TeX nesting exceeds the ${MAX_NESTING_DEPTH}-level limit`);
842
+ }
843
+ if (this.incomplete)
844
+ return this.source.slice(start) || "□";
845
+ this.fail("Unclosed TeX group", start);
846
+ }
847
+ readCommand() {
848
+ this.expect("\\");
849
+ if (this.done())
850
+ return "\\";
851
+ if (!/[A-Za-z@]/.test(this.peek()))
852
+ return this.source[this.offset++];
853
+ const start = this.offset;
854
+ while (/[A-Za-z@]/.test(this.peek()))
855
+ this.offset++;
856
+ const result = this.source.slice(start, this.offset);
857
+ if (this.peek() === " ")
858
+ this.offset++;
859
+ return result;
860
+ }
861
+ readDelimiter() {
862
+ this.skipWhitespace();
863
+ if (this.done()) {
864
+ if (this.incomplete)
865
+ return "";
866
+ this.fail("Expected a TeX delimiter");
867
+ }
868
+ if (this.peek() === "}")
869
+ this.fail("Unexpected closing TeX group");
870
+ if (this.peek() === "\\") {
871
+ const command = this.readCommand();
872
+ return (delimiters[`\\${command}`] ?? delimiters[command] ?? command) + this.readCombiningSuffix();
873
+ }
874
+ const token = this.source[this.offset++] ?? "";
875
+ return (delimiters[token] ?? token) + this.readCombiningSuffix();
876
+ }
877
+ readCombiningSuffix() {
878
+ if (this.done())
879
+ return "";
880
+ const part = this.graphemes.containing(this.offset);
881
+ const suffix = part.segment.slice(this.offset - part.index).match(/^\p{Mark}+/u)?.[0] ?? "";
882
+ this.offset += suffix.length;
883
+ return suffix;
884
+ }
885
+ skipOptionalRowSpacing() {
886
+ this.skipWhitespace();
887
+ if (this.peek() !== "[")
888
+ return;
889
+ while (!this.done() && this.source[this.offset++] !== "]") {}
890
+ }
891
+ isEnd(name) {
892
+ return this.source.startsWith(`\\end{${name}}`, this.offset);
893
+ }
894
+ isCommand(name) {
895
+ if (!this.source.startsWith(`\\${name}`, this.offset))
896
+ return false;
897
+ return !/[A-Za-z@]/.test(this.source[this.offset + name.length + 1] ?? "");
898
+ }
899
+ skipWhitespace() {
900
+ while (/\s/.test(this.peek()))
901
+ this.offset++;
902
+ }
903
+ done() {
904
+ return this.offset >= this.source.length;
905
+ }
906
+ peek() {
907
+ return this.source[this.offset] ?? "";
908
+ }
909
+ expect(value) {
910
+ if (!this.source.startsWith(value, this.offset))
911
+ this.fail(`Expected "${value}"`);
912
+ this.offset += value.length;
913
+ }
914
+ escaped(index) {
915
+ let count = 0;
916
+ while (index > count && this.source[index - count - 1] === "\\")
917
+ count++;
918
+ return count % 2 === 1;
919
+ }
920
+ fail(message, offset = this.offset) {
921
+ throw new Error(`${message} at offset ${offset}`);
922
+ }
923
+ }
924
+ function row(body) {
925
+ return body.length === 1 ? body[0] : { type: "row", body };
926
+ }
927
+ function placeholder() {
928
+ return { type: "symbol", value: "□" };
929
+ }
930
+ function inferRole(value) {
931
+ if ("+-*/×÷±∓".includes(value))
932
+ return "binary";
933
+ if ("=<>≤≥≠≈∈∉⊂⊃".includes(value))
934
+ return "relation";
935
+ if (",;:".includes(value))
936
+ return "punctuation";
937
+ if ("([{".includes(value))
938
+ return "opening";
939
+ if (")]}".includes(value))
940
+ return "closing";
941
+ return "ordinary";
942
+ }
943
+ function negate(value) {
944
+ return { "=": "≠", "<": "≮", ">": "≯", "≤": "≰", "≥": "≱", "∈": "∉", "∋": "∌", "⊂": "⊄", "⊃": "⊅" }[value] ?? `${value}̸`;
945
+ }
946
+
947
+ // src/unicode-tex-backend.ts
948
+ import stringWidth2 from "string-width";
949
+ var UNICODE_TEX_SOURCE_LENGTH_MAX = 4096;
950
+ var OUTPUT_LENGTH_MAX = 16384;
951
+
952
+ class UnicodeTexBackend {
953
+ renderSync(request) {
954
+ return renderUnicode(request, false);
955
+ }
956
+ async render(request) {
957
+ return this.renderSync(request);
958
+ }
959
+ }
960
+ function renderIncompleteUnicode(request) {
961
+ return renderUnicode(request, true);
962
+ }
963
+ function renderUnicode(request, incomplete) {
964
+ assertNotAborted(request.signal);
965
+ const sourceBytes = Buffer.byteLength(request.formula, "utf8");
966
+ if (sourceBytes === 0 || sourceBytes > UNICODE_TEX_SOURCE_LENGTH_MAX) {
967
+ throw new Error(`TeX formula must be between 1 and ${UNICODE_TEX_SOURCE_LENGTH_MAX} UTF-8 bytes`);
968
+ }
969
+ if (!Number.isFinite(request.widthMax) || !Number.isFinite(request.heightMax) || request.widthMax < 1 || request.heightMax < 1) {
970
+ throw new Error("Unicode TeX dimensions must be finite positive numbers");
971
+ }
972
+ const widthMax = Math.floor(request.widthMax);
973
+ const heightMax = Math.floor(request.heightMax);
974
+ const node = incomplete ? parseMathIncomplete(request.formula) : parseMath(request.formula);
975
+ const text = boxToString(layoutMath(node, request.display), widthMax, heightMax);
976
+ assertNotAborted(request.signal);
977
+ if (!text)
978
+ throw new Error("TeX formula produced no Unicode output");
979
+ if (text.length > OUTPUT_LENGTH_MAX)
980
+ throw new Error(`Unicode TeX output exceeds ${OUTPUT_LENGTH_MAX} characters`);
981
+ const lines = text.split(`
982
+ `);
983
+ return {
984
+ kind: "unicode",
985
+ text,
986
+ columns: Math.max(1, ...lines.map((line) => stringWidth2(line))),
987
+ rows: lines.length
988
+ };
989
+ }
990
+ function assertNotAborted(signal) {
991
+ if (signal.aborted)
992
+ throw signal.reason ?? new Error("Unicode render cancelled");
993
+ }
994
+
995
+ // src/tex-renderable.ts
996
+ var NATIVE_SUPERSAMPLE = 4;
997
+ var RESIZE_AREA_THRESHOLD = 1.3;
998
+ var DEFAULT_PREVIEW_BACKEND = new UnicodeTexBackend;
999
+ var UNICODE_RENDER = UnicodeTexBackend.prototype.render;
1000
+ var UNICODE_RENDER_SYNC = UnicodeTexBackend.prototype.renderSync;
1001
+ function dimensionMax(value) {
1002
+ if (!Number.isFinite(value))
1003
+ throw new Error("TeX dimensions must be finite");
1004
+ return Math.max(1, Math.floor(value));
1005
+ }
1006
+ function measureTex(width, height, display, widthMax, heightMax) {
1007
+ let rows = Math.max(1, Math.ceil(height / NATIVE_SUPERSAMPLE / (display ? 10 : 12)));
1008
+ let columns = Math.max(1, Math.round(width / height * rows * 2));
1009
+ if (columns > widthMax) {
1010
+ rows = Math.max(1, Math.round(rows * widthMax / columns));
1011
+ columns = widthMax;
1012
+ }
1013
+ if (rows > heightMax) {
1014
+ columns = Math.max(1, Math.round(columns * heightMax / rows));
1015
+ rows = heightMax;
1016
+ }
1017
+ return { columns, rows };
1018
+ }
1019
+ function fitImageToPlacement(imageWidth, imageHeight, columns, rows, cellPxWidth, cellPxHeight) {
1020
+ const boxWidth = Math.max(1, Math.floor(columns * cellPxWidth));
1021
+ const boxHeight = Math.max(1, Math.floor(rows * cellPxHeight));
1022
+ const scale = Math.min(boxWidth / imageWidth, boxHeight / imageHeight);
1023
+ if (scale >= 1)
1024
+ return null;
1025
+ const width = Math.max(1, Math.round(imageWidth * scale));
1026
+ const height = Math.max(1, Math.round(imageHeight * scale));
1027
+ return imageWidth * imageHeight > width * height * RESIZE_AREA_THRESHOLD ? { width, height } : null;
1028
+ }
1029
+
1030
+ class TexRenderable extends BoxRenderable {
1031
+ ready;
1032
+ backend;
1033
+ fallback;
1034
+ widthMax;
1035
+ heightMax;
1036
+ autoWidth;
1037
+ autoHeight;
1038
+ requestedAlignSelf;
1039
+ currentDimensions = { columns: 1, rows: 1 };
1040
+ imageOptions;
1041
+ onError;
1042
+ _formula;
1043
+ _foreground;
1044
+ _background;
1045
+ _display;
1046
+ _streaming;
1047
+ controller = null;
1048
+ committedOutput = null;
1049
+ constructor(context, options) {
1050
+ const {
1051
+ formula,
1052
+ display = false,
1053
+ foreground,
1054
+ background,
1055
+ widthMax = 80,
1056
+ heightMax = 24,
1057
+ backend,
1058
+ fallback = "message",
1059
+ imageOptions,
1060
+ onError,
1061
+ streaming = false,
1062
+ ...boxOptions
1063
+ } = options;
1064
+ super(context, {
1065
+ shouldFill: false,
1066
+ ...boxOptions,
1067
+ flexShrink: options.flexShrink ?? (typeof options.width === "string" && typeof options.height === "string" ? 1 : 0),
1068
+ width: options.width ?? "auto",
1069
+ height: options.height ?? "auto"
1070
+ });
1071
+ this._formula = formula;
1072
+ this._foreground = foreground;
1073
+ this._background = background;
1074
+ this._streaming = streaming;
1075
+ this._display = display;
1076
+ this.backend = backend;
1077
+ this.fallback = fallback;
1078
+ this.widthMax = dimensionMax(widthMax);
1079
+ this.heightMax = dimensionMax(heightMax);
1080
+ this.autoWidth = options.width == null || options.width === "auto";
1081
+ this.autoHeight = options.height == null || options.height === "auto";
1082
+ this.requestedAlignSelf = this.yogaNode.getAlignSelf();
1083
+ this.imageOptions = imageOptions;
1084
+ this.onError = onError;
1085
+ this.ready = this.update(formula, foreground, background, display);
1086
+ }
1087
+ get formula() {
1088
+ return this._formula;
1089
+ }
1090
+ set formula(value) {
1091
+ this.setSnapshot(value, this._foreground, this._background);
1092
+ }
1093
+ get display() {
1094
+ return this._display;
1095
+ }
1096
+ set display(value) {
1097
+ this.setSnapshot(this._formula, this._foreground, this._background, value === true);
1098
+ }
1099
+ get width() {
1100
+ return super.width;
1101
+ }
1102
+ set width(value) {
1103
+ super.width = value ?? "auto";
1104
+ this.autoWidth = value === "auto" || value == null;
1105
+ for (const child of this.getChildren())
1106
+ child.width = this.autoWidth ? this.currentDimensions.columns : "100%";
1107
+ }
1108
+ get height() {
1109
+ return super.height;
1110
+ }
1111
+ set height(value) {
1112
+ super.height = value ?? "auto";
1113
+ this.autoHeight = value === "auto" || value == null;
1114
+ for (const child of this.getChildren())
1115
+ child.height = this.autoHeight ? this.currentDimensions.rows : "100%";
1116
+ }
1117
+ set alignSelf(value) {
1118
+ super.alignSelf = value;
1119
+ this.requestedAlignSelf = this.yogaNode.getAlignSelf();
1120
+ }
1121
+ onLifecyclePass = () => {
1122
+ if (!this.parent)
1123
+ return;
1124
+ const crossAuto = this.parent.primaryAxis === "column" ? this.autoWidth : this.autoHeight;
1125
+ const alignment = this.requestedAlignSelf === Yoga.Align.Auto ? this.parent.getLayoutNode().getAlignItems() : this.requestedAlignSelf;
1126
+ const resolved = crossAuto && alignment === Yoga.Align.Stretch ? Yoga.Align.FlexStart : this.requestedAlignSelf;
1127
+ if (this.yogaNode.getAlignSelf() !== resolved)
1128
+ this.yogaNode.setAlignSelf(resolved);
1129
+ for (const child of this.getChildren()) {
1130
+ const options = child instanceof ImageRenderable ? this.imageOptions : undefined;
1131
+ const node = child.getLayoutNode();
1132
+ const shrink = options?.flexShrink ?? 1;
1133
+ const flexible = shrink > 0 && node.getPositionType() !== Yoga.PositionType.Absolute;
1134
+ node.setFlexShrink(shrink);
1135
+ if (options?.maxWidth === undefined)
1136
+ node.setMaxWidth(this.primaryAxis === "row" && flexible ? undefined : "100%");
1137
+ if (options?.maxHeight === undefined)
1138
+ node.setMaxHeight(this.primaryAxis === "column" && flexible ? undefined : "100%");
1139
+ }
1140
+ };
1141
+ get streaming() {
1142
+ return this._streaming;
1143
+ }
1144
+ set streaming(value) {
1145
+ if (value === this._streaming)
1146
+ return;
1147
+ this._streaming = value;
1148
+ if (value) {
1149
+ this.controller?.abort();
1150
+ this.controller = null;
1151
+ this.ready = Promise.resolve();
1152
+ return;
1153
+ }
1154
+ this.ready = this.update(this._formula, this._foreground, this._background, this._display);
1155
+ }
1156
+ setColors(foreground, background) {
1157
+ this.setSnapshot(this._formula, foreground, background);
1158
+ }
1159
+ setSnapshot(formula, foreground, background, display = this._display) {
1160
+ if (formula === this._formula && foreground === this._foreground && background === this._background && display === this._display)
1161
+ return;
1162
+ this.ready = this.update(formula, foreground, background, display);
1163
+ }
1164
+ async whenReady() {
1165
+ while (!this.isDestroyed) {
1166
+ const pending = this.ready;
1167
+ const controller = this.controller;
1168
+ let onAbort;
1169
+ const changed = new Promise((resolve) => {
1170
+ if (controller) {
1171
+ onAbort = () => resolve();
1172
+ controller.signal.addEventListener("abort", onAbort, { once: true });
1173
+ }
1174
+ });
1175
+ try {
1176
+ await Promise.race([pending, changed]);
1177
+ } catch (error) {
1178
+ if (pending === this.ready)
1179
+ throw error;
1180
+ continue;
1181
+ } finally {
1182
+ if (onAbort)
1183
+ controller?.signal.removeEventListener("abort", onAbort);
1184
+ }
1185
+ if (pending === this.ready)
1186
+ return;
1187
+ }
1188
+ }
1189
+ async update(formula, foreground, background, display) {
1190
+ this.controller?.abort();
1191
+ this._formula = formula;
1192
+ this._foreground = foreground;
1193
+ this._background = background;
1194
+ this._display = display;
1195
+ if (!formula) {
1196
+ this.clearOutput();
1197
+ this.currentDimensions = { columns: 1, rows: 1 };
1198
+ this.yogaNode.setMeasureFunc(() => ({ width: 1, height: 1 }));
1199
+ if (!this._streaming) {
1200
+ disposeOutput(this.committedOutput);
1201
+ this.committedOutput = null;
1202
+ }
1203
+ this.controller = null;
1204
+ return;
1205
+ }
1206
+ const controller = new AbortController;
1207
+ this.controller = controller;
1208
+ const request = {
1209
+ formula,
1210
+ display,
1211
+ foreground,
1212
+ background,
1213
+ widthMax: this.widthMax,
1214
+ heightMax: this.heightMax,
1215
+ signal: controller.signal
1216
+ };
1217
+ if (this._streaming) {
1218
+ this.applyOutput(this.previewOutput(request));
1219
+ return;
1220
+ }
1221
+ let unicodeOutput = null;
1222
+ const synchronousBackend = this.backend instanceof UnicodeTexBackend && this.backend.render === UNICODE_RENDER && this.backend.renderSync === UNICODE_RENDER_SYNC ? this.backend : null;
1223
+ if (!synchronousBackend) {
1224
+ try {
1225
+ unicodeOutput = DEFAULT_PREVIEW_BACKEND.renderSync(request);
1226
+ this.applyOutput(unicodeOutput);
1227
+ } catch {}
1228
+ }
1229
+ try {
1230
+ const output = synchronousBackend ? synchronousBackend.renderSync(request) : await this.backend.render(request);
1231
+ if (controller.signal.aborted || this.isDestroyed) {
1232
+ disposeOutput(output);
1233
+ return;
1234
+ }
1235
+ if (this.fallback === "retain") {
1236
+ const previous = this.committedOutput;
1237
+ try {
1238
+ this.applyOutput(output);
1239
+ } catch (error) {
1240
+ disposeOutput(output);
1241
+ throw error;
1242
+ }
1243
+ this.committedOutput = output;
1244
+ disposeOutput(previous);
1245
+ } else {
1246
+ try {
1247
+ this.applyOutput(output);
1248
+ } finally {
1249
+ disposeOutput(output);
1250
+ }
1251
+ }
1252
+ } catch (error) {
1253
+ if (synchronousBackend)
1254
+ await Promise.resolve();
1255
+ if (!controller.signal.aborted && !this.isDestroyed) {
1256
+ if (this.fallback === "retain" && this.committedOutput)
1257
+ this.applyOutput(this.committedOutput);
1258
+ else if (this.fallback === "message" || this.fallback === "unicode" && !unicodeOutput) {
1259
+ const message = error instanceof Error ? error.message : String(error);
1260
+ const text = fitLine(`[TeX error: ${message}]`, this.widthMax);
1261
+ this.applyOutput({ kind: "unicode", text, columns: Math.max(1, stringWidth3(text)), rows: 1 });
1262
+ }
1263
+ this.onError?.(error);
1264
+ if (this.fallback === "throw")
1265
+ throw error;
1266
+ }
1267
+ }
1268
+ }
1269
+ previewOutput(request) {
1270
+ if (request.formula.length <= UNICODE_TEX_SOURCE_LENGTH_MAX && Buffer.byteLength(request.formula, "utf8") <= UNICODE_TEX_SOURCE_LENGTH_MAX) {
1271
+ try {
1272
+ return DEFAULT_PREVIEW_BACKEND.renderSync(request);
1273
+ } catch {
1274
+ try {
1275
+ return renderIncompleteUnicode(request);
1276
+ } catch {}
1277
+ }
1278
+ }
1279
+ return rawSourceOutput(request.formula, this.widthMax, this.heightMax);
1280
+ }
1281
+ clearOutput() {
1282
+ for (const existing of this.getChildren())
1283
+ existing.destroyRecursively();
1284
+ }
1285
+ applyOutput(output) {
1286
+ 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) };
1287
+ const child = output.kind === "image" ? this.createImageChild(output.image, dimensions) : new TextRenderable(this._ctx, {
1288
+ content: output.text,
1289
+ fg: this._foreground,
1290
+ bg: this._background,
1291
+ wrapMode: "none",
1292
+ width: this.autoWidth ? dimensions.columns : "100%",
1293
+ height: this.autoHeight ? dimensions.rows : "100%",
1294
+ maxWidth: "100%",
1295
+ maxHeight: "100%"
1296
+ });
1297
+ let added = false;
1298
+ try {
1299
+ this.clearOutput();
1300
+ this.currentDimensions = dimensions;
1301
+ this.yogaNode.unsetMeasureFunc();
1302
+ this.add(child);
1303
+ added = true;
1304
+ } finally {
1305
+ if (!added)
1306
+ child.destroyRecursively();
1307
+ }
1308
+ }
1309
+ createImageChild(image, dimensions) {
1310
+ const resized = this.resizeToPlacement(image, dimensions);
1311
+ const source = resized ?? image;
1312
+ if (this._ctx.capabilities?.kitty_graphics) {
1313
+ try {
1314
+ source.ensureEncodedPng();
1315
+ } catch {}
1316
+ }
1317
+ try {
1318
+ return new ImageRenderable(this._ctx, {
1319
+ protocol: "auto",
1320
+ fit: "fit",
1321
+ maxWidth: "100%",
1322
+ maxHeight: "100%",
1323
+ ...this.imageOptions,
1324
+ source,
1325
+ width: this.autoWidth ? dimensions.columns : "100%",
1326
+ height: this.autoHeight ? dimensions.rows : "100%"
1327
+ });
1328
+ } finally {
1329
+ resized?.dispose();
1330
+ }
1331
+ }
1332
+ resizeToPlacement(image, dimensions) {
1333
+ const { terminalWidth, terminalHeight, resolution } = this._ctx;
1334
+ if (!terminalWidth || !terminalHeight || !resolution?.width || !resolution.height)
1335
+ return null;
1336
+ const target = fitImageToPlacement(image.width, image.height, dimensions.columns, dimensions.rows, resolution.width / terminalWidth, resolution.height / terminalHeight);
1337
+ if (!target)
1338
+ return null;
1339
+ try {
1340
+ return image.resize({ ...target, kernel: "area" });
1341
+ } catch {
1342
+ return null;
1343
+ }
1344
+ }
1345
+ destroySelf() {
1346
+ this.controller?.abort();
1347
+ this.controller = null;
1348
+ disposeOutput(this.committedOutput);
1349
+ this.committedOutput = null;
1350
+ super.destroySelf();
1351
+ }
1352
+ }
1353
+ function disposeOutput(output) {
1354
+ if (output?.kind === "image")
1355
+ output.image.dispose();
1356
+ }
1357
+ function fitLine(value, widthMax) {
1358
+ let output = "";
1359
+ for (const character of value) {
1360
+ if (stringWidth3(output + character) > widthMax)
1361
+ break;
1362
+ output += character;
1363
+ }
1364
+ return output || "?";
1365
+ }
1366
+ function rawSourceOutput(source, widthMax, heightMax) {
1367
+ const cellLimit = Math.min(16384, widthMax * heightMax);
1368
+ const tailStart = Math.max(0, source.length - cellLimit * 2);
1369
+ let tail = source.slice(tailStart);
1370
+ if (tailStart > 0) {
1371
+ const restart = safeRawRestart(tail);
1372
+ tail = restart < 0 ? "" : tail.slice(restart);
1373
+ }
1374
+ const tailGraphemes = Array.from(graphemeSegmenter.segment(tail), (part) => part.segment);
1375
+ const lines = [];
1376
+ let line = "";
1377
+ let width = 0;
1378
+ for (const sourceGrapheme of tailGraphemes) {
1379
+ const visible2 = [...sourceGrapheme].map(visibleSourceCharacter).join("");
1380
+ const segments = visible2 === sourceGrapheme ? [visible2] : [...visible2];
1381
+ for (const character of segments) {
1382
+ const characterWidth = stringWidth3(character);
1383
+ if (characterWidth > widthMax) {
1384
+ if (line)
1385
+ lines.push(line);
1386
+ lines.push("?");
1387
+ line = "";
1388
+ width = 0;
1389
+ } else if (width + characterWidth > widthMax) {
1390
+ lines.push(line);
1391
+ line = character;
1392
+ width = characterWidth;
1393
+ } else {
1394
+ line += character;
1395
+ width += characterWidth;
1396
+ }
1397
+ }
1398
+ }
1399
+ if (line)
1400
+ lines.push(line);
1401
+ const visibleLines = lines.slice(-heightMax);
1402
+ const widths = visibleLines.map((value) => stringWidth3(value));
1403
+ const visible = visibleLines.some((value, index) => /\S/u.test(value) && widths[index] > 0);
1404
+ const text = visible ? visibleLines.join(`
1405
+ `) : "?";
1406
+ return {
1407
+ kind: "unicode",
1408
+ text,
1409
+ columns: visible ? Math.max(1, ...widths) : 1,
1410
+ rows: visible ? Math.max(1, visibleLines.length) : 1
1411
+ };
1412
+ }
1413
+ function safeRawRestart(value) {
1414
+ for (let index = 1;index < value.length; index++) {
1415
+ if (isPrintableAscii(value.charCodeAt(index - 1)) && isPrintableAscii(value.charCodeAt(index)))
1416
+ return index;
1417
+ }
1418
+ return -1;
1419
+ }
1420
+ function isPrintableAscii(value) {
1421
+ return value >= 32 && value <= 126;
1422
+ }
1423
+ function visibleSourceCharacter(character) {
1424
+ if (character === `
1425
+ `)
1426
+ return "\\n";
1427
+ if (character === "\r")
1428
+ return "\\r";
1429
+ if (character === "\t")
1430
+ return "\\t";
1431
+ const code = character.codePointAt(0);
1432
+ return code < 32 || code >= 127 && code <= 159 ? `\\x${code.toString(16).padStart(2, "0")}` : character;
1433
+ }
1434
+ export { UnicodeTexBackend, measureTex, TexRenderable };