@amritk/nish-x86_64-linux 0.20.0 → 0.21.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.
Files changed (70) hide show
  1. package/INSTALL.md +22 -10
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2759 -0
  4. package/package.json +2 -1
  5. package/runtime/browser.d.ts +14 -0
  6. package/runtime/browser.mjs +88 -0
  7. package/runtime/core/arrays.d.ts +18 -0
  8. package/runtime/core/arrays.mjs +125 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +70 -0
  11. package/runtime/core/host.mjs +311 -0
  12. package/runtime/core/numbers.d.ts +53 -0
  13. package/runtime/core/numbers.mjs +316 -0
  14. package/runtime/core/result.d.ts +41 -0
  15. package/runtime/core/result.mjs +127 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +44 -0
  22. package/runtime/nish.h +75 -2
  23. package/runtime/nish.mjs +23 -3
  24. package/runtime/node.mjs +383 -0
  25. package/runtime/runtime-bytes.c +401 -0
  26. package/runtime/runtime-cross.c +1 -1
  27. package/runtime/runtime-host.c +1 -1
  28. package/runtime/runtime-os.c +1 -1
  29. package/runtime/runtime-parallel.c +1 -1
  30. package/runtime/runtime-simd.c +231 -7
  31. package/runtime/runtime-term.c +504 -0
  32. package/runtime/runtime-wasm.c +0 -11
  33. package/runtime/runtime.c +190 -6
  34. package/runtime/shim.mjs +6 -985
  35. package/runtime/sqlite/LICENSE.md +85 -0
  36. package/runtime/sqlite/README.md +45 -0
  37. package/runtime/sqlite/nish-sqlite.c +359 -0
  38. package/runtime/sqlite/sqlite3.c +271131 -0
  39. package/runtime/sqlite/sqlite3.h +14447 -0
  40. package/scripts/build.sh +261 -53
  41. package/std/LICENSE-fast-float +23 -0
  42. package/std/LICENSE-fdlibm +8 -0
  43. package/std/README.md +31 -11
  44. package/std/base64.ts +116 -0
  45. package/std/bytes.ts +256 -0
  46. package/std/cli.ts +328 -0
  47. package/std/collections.ts +6 -5
  48. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  49. package/std/crypto/base64url.ts +125 -0
  50. package/std/crypto/bcrypt.ts +760 -0
  51. package/std/crypto/hmac.ts +138 -2
  52. package/std/crypto/x509.ts +11 -122
  53. package/std/json-tokenizer.ts +2998 -57
  54. package/std/json-value.ts +22 -3
  55. package/std/json.ts +83 -1
  56. package/std/math.ts +608 -0
  57. package/std/net/http1-server.ts +85 -8
  58. package/std/net/http3-server.ts +50 -12
  59. package/std/net/postgres.ts +1875 -0
  60. package/std/net/quic-listener.ts +20 -4
  61. package/std/regex.ts +4274 -0
  62. package/std/sqlite.ts +391 -0
  63. package/std/term-io.ts +978 -0
  64. package/std/term-pixels.ts +874 -0
  65. package/std/term-probe.ts +459 -0
  66. package/std/term-rate.ts +993 -0
  67. package/std/term.ts +2453 -0
  68. package/std/text.ts +90 -7
  69. package/std/yaml-lines.ts +141 -0
  70. package/std/yaml.ts +4675 -0
@@ -0,0 +1,874 @@
1
+ /**
2
+ * `nish/term-pixels` — a framebuffer of pixels, and the encoders that draw it
3
+ * to a terminal with the best backend the terminal has: kitty graphics, Sixel,
4
+ * half-block cells or ASCII.
5
+ *
6
+ * A program draws `0xRRGGBB` pixels into a `Framebuffer` and never thinks about
7
+ * the terminal; `std/term-io` picks the backend and calls one encoder per frame.
8
+ * Everything here is pure: no capability, no I/O, and the bytes are the same
9
+ * natively and under Node, because the kitty and Sixel output is ASCII and the
10
+ * cell drawers go through `Screen`, which already is.
11
+ *
12
+ * - `Framebuffer(width, height)` holds the pixels, with `set`, `get`, `fill`,
13
+ * `clear` and `resize`. `scaleFramebuffer(src, divisor, dst)` shrinks one by
14
+ * an integer divisor, nearest-neighbour, for the refresh controller's
15
+ * resolution downgrade.
16
+ * - `GRAPHICS_ASCII`, `GRAPHICS_HALFBLOCK`, `GRAPHICS_SIXEL` and
17
+ * `GRAPHICS_KITTY` name the backends, numbered from worst to best so that a
18
+ * capability and a cap combine with `Math.min`.
19
+ * - `fitFramebuffer(fbW, fbH, cols, rows, cellW, cellH, backend)` answers the
20
+ * `PixelRect` the image is drawn in: the cells it covers, centred in a
21
+ * `cols × rows` area (letterboxed), and its size in the backend's pixels,
22
+ * keeping the framebuffer's aspect ratio.
23
+ * - `kittyFrame(fb, rect, imageId, out)` writes the kitty graphics commands for
24
+ * one frame into a `PixelBytes`; `KITTY_TRANSFER_DIRECT` is its transfer.
25
+ * Every frame retransmits the same `imageId`, which the terminal swaps in
26
+ * atomically, so there is no per-frame delete; `kittyDelete(imageId)` is the
27
+ * command that frees the image when the program closes.
28
+ * - `sixelFrame(fb, rect, dither, out)` writes one Sixel image, in the fixed
29
+ * palette `SIXEL_REGISTERS` counts, optionally ordered-dithered.
30
+ * - `drawHalfBlocks(fb, canvas, rect)` and `drawAscii(fb, screen, rect, colors)`
31
+ * draw onto the cells of a `Canvas` or a `Screen` from `nish/term`, whose own
32
+ * diff makes an unchanged cell cost nothing at `flush`.
33
+ *
34
+ * Every encoder samples the framebuffer nearest-neighbour onto the rect's
35
+ * pixels; none of them clears the letterbox around the rect, which is the
36
+ * caller's.
37
+ *
38
+ * `PixelBytes` holds the kitty and Sixel encoders' working arrays as well as
39
+ * their output, grown once to the largest frame, so a program that keeps one
40
+ * `PixelBytes` encodes frame after frame without allocating.
41
+ *
42
+ * Private helpers share the importing program's flat symbol namespace
43
+ * (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
44
+ */
45
+ import { base64EncodeInto } from "nish/base64"
46
+ import { COLOR_DEFAULT, ATTR_NONE, Canvas, Screen } from "nish/term"
47
+
48
+ /** Text cells, one luminance character each: the Linux console's backend. */
49
+ export const GRAPHICS_ASCII: i32 = 0
50
+
51
+ /** Two pixels a cell, through `Canvas`'s half blocks. */
52
+ export const GRAPHICS_HALFBLOCK: i32 = 1
53
+
54
+ /** DEC Sixel, in the terminal's own pixels. */
55
+ export const GRAPHICS_SIXEL: i32 = 2
56
+
57
+ /** The kitty graphics protocol, in the terminal's own pixels. */
58
+ export const GRAPHICS_KITTY: i32 = 3
59
+
60
+ /**
61
+ * kitty's direct transfer, `t=d`: the pixels travel base64-encoded inside the
62
+ * escape codes themselves. It is the only transfer built. The protocol's
63
+ * shared-memory transfer (`t=s`, a POSIX shm object named in the payload) and
64
+ * zlib compression (`o=z`) are the seam for a local terminal and a slow link;
65
+ * a future `KITTY_TRANSFER_SHM` would take its own value here and its own
66
+ * branch in `kittyFrame`, which today has none to choose.
67
+ */
68
+ export const KITTY_TRANSFER_DIRECT: i32 = 0
69
+
70
+ /** The Sixel registers the fixed palette uses: 6 reds × 7 greens × 6 blues. */
71
+ export const SIXEL_REGISTERS: i32 = 252
72
+
73
+ /** The pixel size of a cell `fitFramebuffer` assumes when the terminal did not say: VGA's 8×16. */
74
+ const PIXELS_CELL_W: i32 = 8
75
+ const PIXELS_CELL_H: i32 = 16
76
+
77
+ /** The base64 characters in one kitty chunk, the most the protocol allows. */
78
+ const PIXELS_KITTY_CHUNK: i32 = 4096
79
+
80
+ /** The pixel bytes one chunk carries: 4096 characters are 1024 groups of three. */
81
+ const PIXELS_KITTY_RAW: i32 = 3072
82
+
83
+ /** ESC, which opens every escape this module writes. */
84
+ const PIXELS_ESC: i32 = 27
85
+
86
+ // The rest of the bytes this module writes, named because a bare literal is an
87
+ // f64 under `--number-mode f64` and cannot be passed where an i32 is wanted.
88
+
89
+ /** One, as the i32 a byte count and a bit start from. */
90
+ const PIXELS_ONE: i32 = 1
91
+
92
+ /** `\`, which follows ESC to end an APC or a DCS (ECMA-48 ST). */
93
+ const PIXELS_BACKSLASH: i32 = 92
94
+
95
+ /** `[`, which follows ESC to open a control sequence (ECMA-48 CSI). */
96
+ const PIXELS_BRACKET: i32 = 91
97
+
98
+ /** `H`, the final byte of cursor position (ECMA-48 CUP). */
99
+ const PIXELS_CUP: i32 = 72
100
+
101
+ /** `0`, the first decimal digit. */
102
+ const PIXELS_DIGIT_0: i32 = 48
103
+
104
+ /** `;`, between the numbers of raster attributes and colour introducers. */
105
+ const PIXELS_SEMICOLON: i32 = 59
106
+
107
+ /** `!`, the Sixel repeat introducer (DECGRI). */
108
+ const SIXEL_REPEAT: i32 = 33
109
+
110
+ /** `?`, the sixel with no pixels set; a sixel is this plus its six bits. */
111
+ const SIXEL_BLANK: i32 = 63
112
+
113
+ /** `#`, the Sixel colour introducer (DECGCI). */
114
+ const SIXEL_COLOUR: i32 = 35
115
+
116
+ /** `$`, the Sixel carriage return (DECGCR): back to the band's first column. */
117
+ const SIXEL_RETURN: i32 = 36
118
+
119
+ /** `-`, the Sixel next line (DECGNL): down to the next six-pixel band. */
120
+ const SIXEL_NEXT_LINE: i32 = 45
121
+
122
+ /** The 4×4 Bayer matrix, row by row, one hex digit a threshold: the order of ordered dithering. */
123
+ const PIXELS_BAYER: string = "082ac4e63b19f7d5"
124
+
125
+ /** The luminance ramp `drawAscii` draws with, darkest first. */
126
+ const PIXELS_RAMP: string = " .:-=+*#%@"
127
+
128
+ /** xterm's 16 standard colours in their SGR order, six hex digits each. */
129
+ const PIXELS_ANSI16: string =
130
+ "000000cd000000cd00cdcd000000eecd00cd00cdcde5e5e57f7f7fff000000ff00ffff005c5cffff00ff00ffffffffff"
131
+
132
+ /** The value of the hex digits `text[at..at+count)`. */
133
+ const pixelsHex = (text: string, at: i32, count: i32): i32 => {
134
+ let v: i32 = 0
135
+ const length: i32 = toI32(text.length)
136
+ for (let i: i32 = at; i < at + count && i >= 0 && i < length; i++) {
137
+ const c: i32 = toI32(text.charCodeAt(i))
138
+ v = (v << 4) | (c >= 97 ? c - 87 : c - 48)
139
+ }
140
+ return v
141
+ }
142
+
143
+ /** Panic, as `who`, unless `width × height` is a size an array of pixels can have. */
144
+ const pixelsCheckSize = (who: string, width: i32, height: i32): void => {
145
+ if (width < 0 || height < 0) {
146
+ panic(`${who}: a negative size`)
147
+ }
148
+ if (width > 0 && height > ((2147483647 / width) | 0)) {
149
+ panic(`${who}: more than 2^31 - 1 pixels`)
150
+ }
151
+ }
152
+
153
+ /** A `width × height` grid of `0xRRGGBB` pixels, row by row, all black when made. */
154
+ export class Framebuffer {
155
+ /** Pixels across. */
156
+ width: i32
157
+ /** Pixels down. */
158
+ height: i32
159
+ /** One colour per pixel, `y * width + x`. */
160
+ pixels: i32[]
161
+
162
+ /** A black framebuffer of `width × height`; a negative size, or more than 2^31 - 1 pixels, panics. */
163
+ constructor(width: i32, height: i32) {
164
+ pixelsCheckSize("Framebuffer", width, height)
165
+ this.width = width
166
+ this.height = height
167
+ this.pixels = new Array<i32>(width * height)
168
+ this.pixels.fill(0)
169
+ }
170
+
171
+ /** Make the framebuffer `width × height` and black; the array is replaced only when the size changes. */
172
+ resize(width: i32, height: i32): void {
173
+ pixelsCheckSize("Framebuffer.resize", width, height)
174
+ if (width !== this.width || height !== this.height) {
175
+ this.width = width
176
+ this.height = height
177
+ this.pixels = new Array<i32>(width * height)
178
+ }
179
+ this.pixels.fill(0)
180
+ }
181
+
182
+ /** Set the pixel at `x`, `y` to `colour`'s low 24 bits; a pixel off the framebuffer is not drawn. */
183
+ set(x: i32, y: i32, colour: i32): void {
184
+ if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
185
+ return
186
+ }
187
+ this.pixels[y * this.width + x] = colour & 0xffffff
188
+ }
189
+
190
+ /** The colour of the pixel at `x`, `y`, or black off the framebuffer. */
191
+ get(x: i32, y: i32): i32 {
192
+ if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
193
+ return 0
194
+ }
195
+ return this.pixels[y * this.width + x]
196
+ }
197
+
198
+ /** Set every pixel to `colour`'s low 24 bits. */
199
+ fill(colour: i32): void {
200
+ this.pixels.fill(colour & 0xffffff)
201
+ }
202
+
203
+ /** Set every pixel to black. */
204
+ clear(): void {
205
+ this.pixels.fill(0)
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Shrink `src` by `divisor` into `dst`, which is resized to
211
+ * `ceil(width / divisor) × ceil(height / divisor)`: each pixel of `dst` is the
212
+ * top-left pixel of its `divisor × divisor` block, nearest-neighbour, so a
213
+ * frame costs a quarter or a sixteenth of the bytes at divisor 2 or 4. A
214
+ * divisor below 1 panics; 1 copies. `dst` must be another framebuffer than
215
+ * `src`, which resizing it would blank.
216
+ */
217
+ export const scaleFramebuffer = (src: Framebuffer, divisor: i32, dst: Framebuffer): void => {
218
+ if (divisor < 1) {
219
+ panic("scaleFramebuffer: a divisor below 1")
220
+ }
221
+ const w: i32 = pixelsCeilDiv(src.width, divisor)
222
+ const h: i32 = pixelsCeilDiv(src.height, divisor)
223
+ dst.resize(w, h)
224
+ for (let y: i32 = 0; y < h; y++) {
225
+ const from: i32 = y * divisor * src.width
226
+ for (let x: i32 = 0; x < w; x++) {
227
+ dst.pixels[y * w + x] = src.pixels[from + x * divisor]
228
+ }
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Where a framebuffer is drawn: the cells it covers and its size in the
234
+ * backend's pixels. `width` and `height` are terminal pixels for kitty and
235
+ * Sixel, half-cell pixels for half-blocks (`cols` across, up to `rows * 2`
236
+ * down) and cells for ASCII (`cols × rows`).
237
+ */
238
+ export class PixelRect {
239
+ /** The first column covered, from 0. */
240
+ col: i32
241
+ /** The first row covered, from 0. */
242
+ row: i32
243
+ /** The columns covered. */
244
+ cols: i32
245
+ /** The rows covered. */
246
+ rows: i32
247
+ /** The image's width in the backend's pixels. */
248
+ width: i32
249
+ /** The image's height in the backend's pixels. */
250
+ height: i32
251
+ /** The backend's pixels per framebuffer pixel across; ASCII's rows are half that down. */
252
+ scale: f64
253
+
254
+ /** A rect of the given cells and size; `fitFramebuffer` is how one is usually made. */
255
+ constructor(col: i32, row: i32, cols: i32, rows: i32, width: i32, height: i32, scale: f64) {
256
+ this.col = col
257
+ this.row = row
258
+ this.cols = cols
259
+ this.rows = rows
260
+ this.width = width
261
+ this.height = height
262
+ this.scale = scale
263
+ }
264
+ }
265
+
266
+ /** `a / b` rounded up, for `a >= 0` and `b > 0`. */
267
+ const pixelsCeilDiv = (a: i32, b: i32): i32 => (a === 0 ? 0 : (((a - 1) / b) | 0) + 1)
268
+
269
+ /**
270
+ * The rect a `fbW × fbH` framebuffer is drawn in on a `cols × rows` area of
271
+ * cells `cellW × cellH` pixels each, by `backend` (a `GRAPHICS_` constant): as
272
+ * large as fits with the framebuffer's aspect ratio kept, and centred, so the
273
+ * spare cells on two sides are the letterbox. The rect starts at column and
274
+ * row 0 of the area; the caller adds the area's own offset.
275
+ *
276
+ * kitty and Sixel measure in the cell's pixels, taken as 8×16 when `cellW` or
277
+ * `cellH` is not positive (the terminal did not answer `CSI 16 t`). Half-blocks
278
+ * and ASCII take a cell as one pixel wide and two tall, the shape of a usual
279
+ * terminal font, so a square framebuffer stays square. An empty framebuffer or
280
+ * area answers an empty rect at 0, 0.
281
+ */
282
+ export const fitFramebuffer = (
283
+ fbW: i32,
284
+ fbH: i32,
285
+ cols: i32,
286
+ rows: i32,
287
+ cellW: i32,
288
+ cellH: i32,
289
+ backend: i32
290
+ ): PixelRect => {
291
+ if (fbW <= 0 || fbH <= 0 || cols <= 0 || rows <= 0) {
292
+ return new PixelRect(0, 0, 0, 0, 0, 0, 0.0)
293
+ }
294
+ const inPixels: boolean = backend === GRAPHICS_SIXEL || backend === GRAPHICS_KITTY
295
+ const unknownCell: boolean = cellW <= 0 || cellH <= 0
296
+ let pw: i32 = 1
297
+ let ph: i32 = 2
298
+ if (inPixels) {
299
+ pw = unknownCell ? PIXELS_CELL_W : cellW
300
+ ph = unknownCell ? PIXELS_CELL_H : cellH
301
+ }
302
+ // The products are f64, exact below 2^53, so that a large area cannot
303
+ // overflow i32 while the two aspect ratios are compared.
304
+ const availW: f64 = toF64(cols) * toF64(pw)
305
+ const availH: f64 = toF64(rows) * toF64(ph)
306
+ let w: i32 = 0
307
+ let h: i32 = 0
308
+ if (toF64(fbW) * availH >= toF64(fbH) * availW) {
309
+ w = toI32(availW)
310
+ h = toI32(Math.floor((toF64(fbH) * availW) / toF64(fbW)))
311
+ } else {
312
+ h = toI32(availH)
313
+ w = toI32(Math.floor((toF64(fbW) * availH) / toF64(fbH)))
314
+ }
315
+ w = w < 1 ? 1 : w
316
+ h = h < 1 ? 1 : h
317
+ const usedCols: i32 = pixelsCeilDiv(w, pw)
318
+ const usedRows: i32 = pixelsCeilDiv(h, ph)
319
+ const scale: f64 = toF64(w) / toF64(fbW)
320
+ const col: i32 = (cols - usedCols) >> 1
321
+ const row: i32 = (rows - usedRows) >> 1
322
+ if (backend === GRAPHICS_ASCII) {
323
+ return new PixelRect(col, row, usedCols, usedRows, usedCols, usedRows, scale)
324
+ }
325
+ return new PixelRect(col, row, usedCols, usedRows, w, h, scale)
326
+ }
327
+
328
+ /**
329
+ * A growable buffer of the bytes an encoder writes. `text()` is them as a
330
+ * string, ready for one write; `reset()` empties it and keeps its capacity, so
331
+ * one buffer serves every frame.
332
+ *
333
+ * It also holds the working arrays of `kittyFrame` and `sixelFrame`, grown to
334
+ * the largest frame and then reused, so that encoding a frame allocates
335
+ * nothing once the buffer has seen one as large: a frame loop calling an
336
+ * encoder 60 times a second runs in flat memory. An encoder's arrays could
337
+ * not be its own locals, because the arena cannot take a call's allocations
338
+ * back while that call grows a buffer it was handed (`bytes` above).
339
+ */
340
+ export class PixelBytes {
341
+ /** The bytes, of which the first `length` are written. */
342
+ bytes: u8[]
343
+ /** How many bytes are written. */
344
+ length: i32
345
+ /** One kitty chunk's RGB bytes. */
346
+ kittyRaw: u8[]
347
+ /** The framebuffer column and row each destination pixel samples. */
348
+ sampleXs: i32[]
349
+ sampleYs: i32[]
350
+ /** Every destination pixel's Sixel register, row by row. */
351
+ sixelReg: i32[]
352
+ /** Which registers a Sixel frame uses, 1 or 0. */
353
+ sixelUsed: u8[]
354
+ /** The first and last column of a band each register covers, -1 for none. */
355
+ sixelFirst: i32[]
356
+ sixelLast: i32[]
357
+ /** Each register's sixels across a band, all zero between bands. */
358
+ sixelBits: i32[]
359
+
360
+ /** An empty buffer with room for `capacity` bytes before it grows. */
361
+ constructor(capacity: i32) {
362
+ this.bytes = new Array<u8>(Math.max(capacity, 16))
363
+ this.bytes.fill(0)
364
+ this.length = 0
365
+ this.kittyRaw = new Array<u8>(PIXELS_KITTY_RAW)
366
+ this.kittyRaw.fill(0)
367
+ this.sampleXs = []
368
+ this.sampleYs = []
369
+ this.sixelReg = []
370
+ this.sixelUsed = new Array<u8>(SIXEL_REGISTERS)
371
+ this.sixelUsed.fill(0)
372
+ this.sixelFirst = new Array<i32>(SIXEL_REGISTERS)
373
+ this.sixelFirst.fill(-1)
374
+ this.sixelLast = new Array<i32>(SIXEL_REGISTERS)
375
+ this.sixelLast.fill(-1)
376
+ this.sixelBits = []
377
+ }
378
+
379
+ /** Forget what was written. */
380
+ reset(): void {
381
+ this.length = 0
382
+ }
383
+
384
+ /** Make room for `n` more bytes, doubling the array as often as that takes. */
385
+ reserve(n: i32): void {
386
+ const need: i32 = this.length + n
387
+ if (need <= toI32(this.bytes.length)) {
388
+ return
389
+ }
390
+ // Doubled in f64, which cannot overflow, and capped at the longest array.
391
+ let size: f64 = toF64(this.bytes.length)
392
+ while (size < toF64(need)) {
393
+ size = size * 2.0
394
+ }
395
+ const grown: u8[] = new Array<u8>(toI32(Math.min(size, 2147483647.0)))
396
+ grown.fill(0)
397
+ grown.set(this.bytes, 0)
398
+ this.bytes = grown
399
+ }
400
+
401
+ /** Append the byte `b`. */
402
+ put(b: i32): void {
403
+ this.reserve(PIXELS_ONE)
404
+ this.bytes[this.length] = toU8(b)
405
+ this.length = this.length + 1
406
+ }
407
+
408
+ /** Append `s`, which is ASCII. */
409
+ putText(s: string): void {
410
+ const n: i32 = toI32(s.length)
411
+ this.reserve(n)
412
+ for (let i: i32 = 0; i < n; i++) {
413
+ this.bytes[this.length + i] = toU8(s.charCodeAt(i))
414
+ }
415
+ this.length = this.length + n
416
+ }
417
+
418
+ /** Append `v`, at least 0, in decimal. */
419
+ putDecimal(v: i32): void {
420
+ if (v >= 10) {
421
+ this.putDecimal((v / 10) | 0)
422
+ }
423
+ this.put(PIXELS_DIGIT_0 + (v % 10))
424
+ }
425
+
426
+ /** What was written, as one string. */
427
+ text(): string {
428
+ return String.fromBytes(this.bytes, 0, this.length)
429
+ }
430
+ }
431
+
432
+ /**
433
+ * For each of `n` destination pixels along one axis, the framebuffer pixel it
434
+ * samples: the one under the destination pixel's centre, nearest-neighbour,
435
+ * computed once per frame rather than once per pixel.
436
+ */
437
+ const pixelsSampleMap = (n: i32, source: i32): i32[] => {
438
+ const map: i32[] = new Array<i32>(n)
439
+ pixelsSampleMapInto(map, n, source)
440
+ return map
441
+ }
442
+
443
+ /** `pixelsSampleMap` written into the first `n` entries of `map`, which is at least `n` long. */
444
+ const pixelsSampleMapInto = (map: i32[], n: i32, source: i32): void => {
445
+ for (let i: i32 = 0; i < n && i < toI32(map.length); i++) {
446
+ const s: i32 = toI32(Math.floor(((toF64(i) + 0.5) * toF64(source)) / toF64(n)))
447
+ map[i] = s >= source ? source - 1 : s
448
+ }
449
+ }
450
+
451
+ /**
452
+ * `a` when it is at least `n` long, else a new zeroed array for a `PixelBytes`
453
+ * working array: twice `a`'s length, or `n` if that is more, so frames that
454
+ * grow a little at a time reallocate a logarithmic number of times.
455
+ */
456
+ const pixelsAtLeast = (a: i32[], n: i32): i32[] => {
457
+ const had: i32 = toI32(a.length)
458
+ if (had >= n) {
459
+ return a
460
+ }
461
+ const twice: f64 = Math.min(toF64(had) * 2.0, 2147483647.0)
462
+ const grown: i32[] = new Array<i32>(twice > toF64(n) ? toI32(twice) : n)
463
+ grown.fill(0)
464
+ return grown
465
+ }
466
+
467
+ /** Append `CSI row ; col H`, the cursor to the 0-based cell (`col`, `row`), as `moveCursor` writes it. */
468
+ const pixelsMoveTo = (out: PixelBytes, col: i32, row: i32): void => {
469
+ out.put(PIXELS_ESC)
470
+ out.put(PIXELS_BRACKET)
471
+ out.putDecimal(row + 1)
472
+ out.put(PIXELS_SEMICOLON)
473
+ out.putDecimal(col + 1)
474
+ out.put(PIXELS_CUP)
475
+ }
476
+
477
+ /** Append `ESC _ G`, which opens a kitty graphics command (an APC). */
478
+ const pixelsKittyOpen = (out: PixelBytes): void => {
479
+ out.put(PIXELS_ESC)
480
+ out.putText("_G")
481
+ }
482
+
483
+ /** Append `ESC \`, the string terminator that closes an APC or a DCS. */
484
+ const pixelsStringEnd = (out: PixelBytes): void => {
485
+ out.put(PIXELS_ESC)
486
+ out.put(PIXELS_BACKSLASH)
487
+ }
488
+
489
+ /**
490
+ * Append one frame of `fb` in the kitty graphics protocol
491
+ * (https://sw.kovidgoyal.net/kitty/graphics-protocol/), placed over `rect`.
492
+ *
493
+ * 1. The cursor moves to the rect's first cell, where kitty places an image.
494
+ * 2. `a=T` transmits and displays at once: `f=24` is packed RGB, `t=d` direct
495
+ * transfer (`KITTY_TRANSFER_DIRECT`), `i` the image and `p=1` its one
496
+ * placement, `s` and `v` the framebuffer's own size, `c` and `r` the rect's
497
+ * cells, which kitty scales the image to — so a downgraded framebuffer
498
+ * costs fewer bytes and still fills the rect — `C=1` leaves the cursor
499
+ * where it was, and `q=2` suppresses every reply.
500
+ * 3. The RGB bytes go as base64 (RFC 4648 §4) in chunks of 4096 characters,
501
+ * the protocol's limit: each but the last carries `m=1`, the last `m=0`.
502
+ * Only the first chunk carries the keys; the rest carry `q=2` and `m`,
503
+ * since a terminal may answer a chunk that does not repeat `q`.
504
+ *
505
+ * Every frame reuses `imageId` and nothing is deleted first. Transmitting an
506
+ * id that exists replaces that image and frees the old one's data, and the
507
+ * same image and placement id replace the placement, so the terminal holds
508
+ * one frame however many are sent. The new frame takes over only when its
509
+ * last chunk (`m=0`) has arrived, so the old one stays on screen while a
510
+ * frame of hundreds of chunks streams in. Deleting first would blank the
511
+ * image for that whole time. `kittyDelete` removes the image when the program
512
+ * is done with it.
513
+ *
514
+ * `imageId` is 1 to 2^31 - 1, since 0 is the protocol's "no id"; anything
515
+ * else panics. An empty framebuffer or rect writes nothing.
516
+ */
517
+ export const kittyFrame = (fb: Framebuffer, rect: PixelRect, imageId: i32, out: PixelBytes): void => {
518
+ pixelsCheckImageId("kittyFrame", imageId)
519
+ const count: i32 = fb.width * fb.height
520
+ if (count === 0 || rect.cols <= 0 || rect.rows <= 0) {
521
+ return
522
+ }
523
+ pixelsMoveTo(out, rect.col, rect.row)
524
+ const raw: u8[] = out.kittyRaw
525
+ const total: i32 = count * 3
526
+ let sent: i32 = 0
527
+ let pixel: i32 = 0
528
+ while (sent < total) {
529
+ // The next chunk's pixels as RGB bytes, 1024 pixels to a full chunk.
530
+ const n: i32 = total - sent < PIXELS_KITTY_RAW ? total - sent : PIXELS_KITTY_RAW
531
+ // `k + 2 < raw.length` always holds below `n`; it proves the stores in range.
532
+ for (let k: i32 = 0; k < n && k + 2 < toI32(raw.length); k = k + 3) {
533
+ const c: i32 = fb.pixels[pixel]
534
+ raw[k] = toU8((c >> 16) & 255)
535
+ raw[k + 1] = toU8((c >> 8) & 255)
536
+ raw[k + 2] = toU8(c & 255)
537
+ pixel = pixel + 1
538
+ }
539
+ pixelsKittyOpen(out)
540
+ if (sent === 0) {
541
+ out.putText("a=T,f=24,t=d,i=")
542
+ out.putDecimal(imageId)
543
+ out.putText(",p=1,s=")
544
+ out.putDecimal(fb.width)
545
+ out.putText(",v=")
546
+ out.putDecimal(fb.height)
547
+ out.putText(",c=")
548
+ out.putDecimal(rect.cols)
549
+ out.putText(",r=")
550
+ out.putDecimal(rect.rows)
551
+ out.putText(",C=1,")
552
+ }
553
+ out.putText("q=2,")
554
+ sent = sent + n
555
+ out.putText(sent < total ? "m=1;" : "m=0;")
556
+ out.reserve(PIXELS_KITTY_CHUNK)
557
+ out.length = base64EncodeInto(raw, 0, n, out.bytes, out.length)
558
+ pixelsStringEnd(out)
559
+ }
560
+ }
561
+
562
+ /** Panic, as `who`, unless `imageId` is a kitty image id: 1 or more, 0 being "no id". */
563
+ const pixelsCheckImageId = (who: string, imageId: i32): void => {
564
+ if (imageId < 1) {
565
+ panic(`${who}: an image id below 1`)
566
+ }
567
+ }
568
+
569
+ /**
570
+ * The kitty command that deletes the image `imageId`, every placement of it
571
+ * and its data (`a=d,d=I`, the uppercase freeing the data), with `q=2` so the
572
+ * terminal does not answer. A program writes it when it stops drawing, and
573
+ * puts it in its terminal restore string, so the last frame does not stay
574
+ * behind. Panics, like `kittyFrame`, on an id below 1.
575
+ */
576
+ export const kittyDelete = (imageId: i32): string => {
577
+ pixelsCheckImageId("kittyDelete", imageId)
578
+ return `\x1b_Ga=d,d=I,i=${imageId},q=2\x1b\\`
579
+ }
580
+
581
+ /** The Sixel register of the palette colour with red level `r` (0–5), green `g` (0–6) and blue `b` (0–5). */
582
+ const pixelsSixelRegister = (r: i32, g: i32, b: i32): i32 => r * 42 + g * 6 + b
583
+
584
+ /**
585
+ * The level of one 0–255 component among `levels`, nearest, after `bias` (a
586
+ * dither offset, 0 without dithering) is added and the sum clamped to 0–255.
587
+ */
588
+ const pixelsLevel = (v: i32, bias: i32, levels: i32): i32 => {
589
+ const x: i32 = Math.min(Math.max(v + bias, 0), 255)
590
+ return ((x * (levels - 1) * 2 + 255) / 510) | 0
591
+ }
592
+
593
+ /**
594
+ * The dither offset for one component quantised to `levels`, at Bayer
595
+ * threshold `t` (0–15): spread evenly over one quantisation step, centred on
596
+ * zero, so a flat colour between two levels becomes a fixed pattern of both.
597
+ */
598
+ const pixelsDitherBias = (t: i32, levels: i32): i32 => (((t * 2 - 15) * 255) / ((levels - 1) * 32)) | 0
599
+
600
+ /** Append the sixel character for the six-pixel column `bits`, `count` times, run-length encoded. */
601
+ const pixelsSixelRun = (out: PixelBytes, bits: i32, count: i32): void => {
602
+ // `!n` followed by the character repeats it n times (DECGRI); three or fewer
603
+ // are as short written out.
604
+ if (count > 3) {
605
+ out.put(SIXEL_REPEAT)
606
+ out.putDecimal(count)
607
+ out.put(SIXEL_BLANK + bits)
608
+ } else {
609
+ for (let k: i32 = 0; k < count; k++) {
610
+ out.put(SIXEL_BLANK + bits)
611
+ }
612
+ }
613
+ }
614
+
615
+ /**
616
+ * Append one frame of `fb` as DEC Sixel graphics, drawn over `rect`'s
617
+ * `width × height` terminal pixels from its first cell. The format is DEC's
618
+ * (VT330/VT340 Programmer Reference, "Sixel Graphics"; DECSIXEL):
619
+ *
620
+ * - `ESC P q` opens the device control string with the default parameters, and
621
+ * `ESC \` closes it.
622
+ * - `"1;1;w;h` is the raster attributes (DECGRA): square pixels and the
623
+ * image's size, so the terminal can reserve it before the data arrives.
624
+ * - `#n;2;r;g;b` sets colour register `n` to an RGB colour in percent
625
+ * (DECGCI). The palette is fixed: 6 levels of red, 7 of green and 6 of blue,
626
+ * 252 registers (`SIXEL_REGISTERS`), the most that fits the 256 a
627
+ * terminal must offer. A register always holds the same colour, so frames
628
+ * never fight over the palette and nothing flickers; each frame declares the
629
+ * registers it uses, in ascending order.
630
+ * - The image goes in bands six pixels tall. Within a band, `#n` selects a
631
+ * register and each character `?`–`~` is a column of six pixels, one bit
632
+ * each, painted in it; `!n` repeats a character, `$` returns to the band's
633
+ * start for the next colour, and `-` moves to the next band.
634
+ *
635
+ * With `dither`, each pixel's components are offset by the 4×4 Bayer
636
+ * matrix's threshold before they are quantised (ordered dithering), so a
637
+ * gradient becomes a fine, stable pattern rather than steps. An empty rect
638
+ * writes nothing.
639
+ */
640
+ export const sixelFrame = (fb: Framebuffer, rect: PixelRect, dither: boolean, out: PixelBytes): void => {
641
+ const w: i32 = rect.width
642
+ const h: i32 = rect.height
643
+ if (w <= 0 || h <= 0 || fb.width <= 0 || fb.height <= 0) {
644
+ return
645
+ }
646
+ pixelsCheckSize("sixelFrame", w, h)
647
+ pixelsCheckSize("sixelFrame", w, SIXEL_REGISTERS)
648
+ // The working arrays live on `out`, grown once to the largest frame.
649
+ out.sampleXs = pixelsAtLeast(out.sampleXs, w)
650
+ out.sampleYs = pixelsAtLeast(out.sampleYs, h)
651
+ out.sixelReg = pixelsAtLeast(out.sixelReg, w * h)
652
+ out.sixelBits = pixelsAtLeast(out.sixelBits, SIXEL_REGISTERS * w)
653
+ const xs: i32[] = out.sampleXs
654
+ const ys: i32[] = out.sampleYs
655
+ pixelsSampleMapInto(xs, w, fb.width)
656
+ pixelsSampleMapInto(ys, h, fb.height)
657
+ // Every destination pixel's register, and which registers the frame uses.
658
+ const reg: i32[] = out.sixelReg
659
+ const used: u8[] = out.sixelUsed
660
+ used.fill(0)
661
+ // `ys` is at least `h` long and `xs` `w`: bounding the loops by their lengths
662
+ // too is what proves the reads in range.
663
+ for (let y: i32 = 0; y < h && y < toI32(ys.length); y++) {
664
+ const from: i32 = ys[y] * fb.width
665
+ for (let x: i32 = 0; x < w && x < toI32(xs.length); x++) {
666
+ const c: i32 = fb.pixels[from + xs[x]]
667
+ let br: i32 = 0
668
+ let bg: i32 = 0
669
+ let bb: i32 = 0
670
+ if (dither) {
671
+ const t: i32 = pixelsHex(PIXELS_BAYER, ((y & 3) << 2) | (x & 3), 1)
672
+ br = pixelsDitherBias(t, 6)
673
+ bg = pixelsDitherBias(t, 7)
674
+ bb = pixelsDitherBias(t, 6)
675
+ }
676
+ const n: i32 = pixelsSixelRegister(
677
+ pixelsLevel((c >> 16) & 255, br, 6),
678
+ pixelsLevel((c >> 8) & 255, bg, 7),
679
+ pixelsLevel(c & 255, bb, 6)
680
+ )
681
+ reg[y * w + x] = n
682
+ // Always true (a register is 0 to 251); it proves the store in range.
683
+ if (n >= 0 && n < toI32(used.length)) {
684
+ used[n] = 1
685
+ }
686
+ }
687
+ }
688
+ pixelsMoveTo(out, rect.col, rect.row)
689
+ out.put(PIXELS_ESC)
690
+ out.putText('Pq"1;1;')
691
+ out.putDecimal(w)
692
+ out.put(PIXELS_SEMICOLON)
693
+ out.putDecimal(h)
694
+ for (let n: i32 = 0; n < toI32(used.length); n++) {
695
+ if (used[n] !== 0) {
696
+ const r: i32 = (n / 42) | 0
697
+ const g: i32 = ((n / 6) | 0) % 7
698
+ const b: i32 = n % 6
699
+ out.put(SIXEL_COLOUR)
700
+ out.putDecimal(n)
701
+ out.putText(";2;")
702
+ out.putDecimal(((r * 100) / 5) | 0)
703
+ out.put(PIXELS_SEMICOLON)
704
+ out.putDecimal(((g * 100) / 6) | 0)
705
+ out.put(PIXELS_SEMICOLON)
706
+ out.putDecimal(((b * 100) / 5) | 0)
707
+ }
708
+ }
709
+ // Per band: the first and last column each register appears in, so a colour
710
+ // is only walked over the span it covers, and each register's sixels across
711
+ // the band, `n * w + x`, built in one pass over its pixels. Emitting a span
712
+ // zeroes it, which leaves `bits` all zero for the next band and the next
713
+ // frame.
714
+ const first: i32[] = out.sixelFirst
715
+ const last: i32[] = out.sixelLast
716
+ const bits: i32[] = out.sixelBits
717
+ for (let top: i32 = 0; top < h; top = top + 6) {
718
+ const bottom: i32 = top + 6 < h ? top + 6 : h
719
+ first.fill(-1)
720
+ for (let y: i32 = top; y < bottom; y++) {
721
+ const bit: i32 = PIXELS_ONE << (y - top)
722
+ for (let x: i32 = 0; x < w; x++) {
723
+ const n: i32 = reg[y * w + x]
724
+ // Never taken (a register is 0 to 251); it proves `first[n]` and
725
+ // `last[n]` in range.
726
+ if (n < 0 || n >= toI32(first.length) || n >= toI32(last.length)) {
727
+ continue
728
+ }
729
+ bits[n * w + x] = bits[n * w + x] | bit
730
+ if (first[n] < 0) {
731
+ first[n] = x
732
+ last[n] = x
733
+ } else if (x < first[n]) {
734
+ first[n] = x
735
+ } else if (x > last[n]) {
736
+ last[n] = x
737
+ }
738
+ }
739
+ }
740
+ if (top > 0) {
741
+ out.put(SIXEL_NEXT_LINE)
742
+ }
743
+ let firstColour: boolean = true
744
+ for (let n: i32 = 0; n < toI32(first.length) && n < toI32(last.length); n++) {
745
+ // Read once: the calls below drop what proves `n` in range.
746
+ const start: i32 = first[n]
747
+ const end: i32 = last[n]
748
+ if (start < 0) {
749
+ continue
750
+ }
751
+ if (!firstColour) {
752
+ out.put(SIXEL_RETURN)
753
+ }
754
+ firstColour = false
755
+ out.put(SIXEL_COLOUR)
756
+ out.putDecimal(n)
757
+ // Blank columns before the colour's first one, then its runs to its last.
758
+ pixelsSixelRun(out, 0, start)
759
+ let runBits: i32 = -1
760
+ let runCount: i32 = 0
761
+ const row: i32 = n * w
762
+ for (let x: i32 = start; x <= end; x++) {
763
+ const sixel: i32 = bits[row + x]
764
+ bits[row + x] = 0
765
+ if (sixel === runBits) {
766
+ runCount = runCount + 1
767
+ } else {
768
+ if (runCount > 0) {
769
+ pixelsSixelRun(out, runBits, runCount)
770
+ }
771
+ runBits = sixel
772
+ runCount = 1
773
+ }
774
+ }
775
+ pixelsSixelRun(out, runBits, runCount)
776
+ }
777
+ }
778
+ pixelsStringEnd(out)
779
+ }
780
+
781
+ /**
782
+ * Draw `fb` onto `canvas`'s pixels over `rect` (from `fitFramebuffer` with
783
+ * `GRAPHICS_HALFBLOCK`): two pixels a cell, the upper and lower half block.
784
+ * The image's own pixels are set; a rect row's lower half that the image does
785
+ * not reach is set to `COLOR_DEFAULT`. Each covered cell is repainted once,
786
+ * and `Screen.flush` sends only the cells whose glyph or colours changed, so
787
+ * a frame that barely moves costs almost nothing.
788
+ */
789
+ export const drawHalfBlocks = (fb: Framebuffer, canvas: Canvas, rect: PixelRect): void => {
790
+ if (rect.width <= 0 || rect.height <= 0 || fb.width <= 0 || fb.height <= 0) {
791
+ return
792
+ }
793
+ const xs: i32[] = pixelsSampleMap(rect.width, fb.width)
794
+ const ys: i32[] = pixelsSampleMap(rect.height, fb.height)
795
+ const left: i32 = rect.col
796
+ const top: i32 = rect.row * 2
797
+ for (let y: i32 = 0; y < rect.rows * 2; y++) {
798
+ const py: i32 = top + y
799
+ if (py < 0 || py >= canvas.height) {
800
+ continue
801
+ }
802
+ // `xs` is `rect.width` long and `ys` `rect.height`; their lengths bound the
803
+ // reads so the prover sees them in range.
804
+ for (let x: i32 = 0; x < toI32(xs.length); x++) {
805
+ const px: i32 = left + x
806
+ if (px < 0 || px >= canvas.width) {
807
+ continue
808
+ }
809
+ canvas.pixels[py * canvas.width + px] =
810
+ y < toI32(ys.length) ? fb.pixels[ys[y] * fb.width + xs[x]] : COLOR_DEFAULT
811
+ }
812
+ }
813
+ for (let row: i32 = rect.row; row < rect.row + rect.rows; row++) {
814
+ for (let col: i32 = left; col < left + rect.width; col++) {
815
+ if (row >= 0 && col >= 0 && row * 2 < canvas.height && col < canvas.width) {
816
+ canvas.paintCell(col, row)
817
+ }
818
+ }
819
+ }
820
+ }
821
+
822
+ /** The one of xterm's 16 standard colours nearest `colour` in RGB; a tie goes to the lower number. */
823
+ const pixelsNearestAnsi16 = (colour: i32): i32 => {
824
+ const r: i32 = (colour >> 16) & 255
825
+ const g: i32 = (colour >> 8) & 255
826
+ const b: i32 = colour & 255
827
+ let best: i32 = 0
828
+ let bestDistance: i32 = 2147483647
829
+ for (let i: i32 = 0; i < 16; i++) {
830
+ const c: i32 = pixelsHex(PIXELS_ANSI16, i * 6, 6)
831
+ const dr: i32 = r - ((c >> 16) & 255)
832
+ const dg: i32 = g - ((c >> 8) & 255)
833
+ const db: i32 = b - (c & 255)
834
+ const d: i32 = dr * dr + dg * dg + db * db
835
+ if (d < bestDistance) {
836
+ best = c
837
+ bestDistance = d
838
+ }
839
+ }
840
+ return best
841
+ }
842
+
843
+ /**
844
+ * Draw `fb` onto `screen`'s cells over `rect` (from `fitFramebuffer` with
845
+ * `GRAPHICS_ASCII`), one character a cell for terminals with no graphics: the
846
+ * framebuffer pixel under the cell's centre picks a character from the ramp
847
+ * ` .:-=+*#%@` by its luminance (Rec. 601 weights, darkest first) and is the
848
+ * character's colour, on the terminal's own background.
849
+ *
850
+ * When `colors` is 16 — the Linux console — the colour is first moved to the
851
+ * nearest of xterm's 16 standard colours. `Screen` sends colours as 24-bit or
852
+ * 256-palette SGR only, and a console that reduces those to its 16 itself then
853
+ * lands on the colour chosen here, not on a neighbour of it.
854
+ */
855
+ export const drawAscii = (fb: Framebuffer, screen: Screen, rect: PixelRect, colors: i32): void => {
856
+ if (rect.width <= 0 || rect.height <= 0 || fb.width <= 0 || fb.height <= 0) {
857
+ return
858
+ }
859
+ const xs: i32[] = pixelsSampleMap(rect.width, fb.width)
860
+ const ys: i32[] = pixelsSampleMap(rect.height, fb.height)
861
+ const ramp: i32 = toI32(PIXELS_RAMP.length)
862
+ // `ys` is `rect.height` long and `xs` `rect.width`; bounding the loops by
863
+ // their lengths proves the reads in range.
864
+ for (let y: i32 = 0; y < toI32(ys.length); y++) {
865
+ const from: i32 = ys[y] * fb.width
866
+ for (let x: i32 = 0; x < toI32(xs.length); x++) {
867
+ const c: i32 = fb.pixels[from + xs[x]]
868
+ const luma: i32 = ((((c >> 16) & 255) * 299 + ((c >> 8) & 255) * 587 + (c & 255) * 114) / 1000) | 0
869
+ const glyph: i32 = toI32(PIXELS_RAMP.charCodeAt(((luma * ramp) / 256) | 0))
870
+ const fg: i32 = colors === 16 ? pixelsNearestAnsi16(c) : c
871
+ screen.set(rect.col + x, rect.row + y, glyph, fg, COLOR_DEFAULT, ATTR_NONE)
872
+ }
873
+ }
874
+ }