@jsenv/terminal-recorder 1.5.6 → 1.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/xterm.html CHANGED
@@ -16,11 +16,1540 @@
16
16
  window.resolveXtermReadyPromise = resolve;
17
17
  });
18
18
  </script>
19
- <script type="module">
20
- import { initTerminal } from "/js/terminal_recorder.js";
19
+ <script type="module">import "/jsenv_terminal_recorder_node_modules.js";
21
20
 
22
- window.initTerminal = initTerminal;
21
+ const createLzwEncoder = ({ width, height, pixels, colorDepth }) => {
22
+ var EOF = -1;
23
+ var imgW;
24
+ var imgH;
25
+ var pixAry;
26
+ var initCodeSize;
27
+ var remaining;
28
+ var curPixel;
29
+
30
+ // GIFCOMPR.C - GIF Image compression routines
31
+ // Lempel-Ziv compression based on 'compress'. GIF modifications by
32
+ // David Rowley (mgardi@watdcsu.waterloo.edu)
33
+ // General DEFINEs
34
+
35
+ var BITS = 12;
36
+ var HSIZE = 5003; // 80% occupancy
37
+
38
+ // GIF Image compression - modified 'compress'
39
+ // Based on: compress.c - File compression ala IEEE Computer, June 1984.
40
+ // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
41
+ // Jim McKie (decvax!mcvax!jim)
42
+ // Steve Davies (decvax!vax135!petsd!peora!srd)
43
+ // Ken Turkowski (decvax!decwrl!turtlevax!ken)
44
+ // James A. Woods (decvax!ihnp4!ames!jaw)
45
+ // Joe Orost (decvax!vax135!petsd!joe)
46
+
47
+ var n_bits; // number of bits/code
48
+ var maxbits = BITS; // user settable max # bits/code
49
+ var maxcode; // maximum code, given n_bits
50
+ var maxmaxcode = 1 << BITS; // should NEVER generate this code
51
+ var htab = [];
52
+ var codetab = [];
53
+ var hsize = HSIZE; // for dynamic table sizing
54
+ var free_ent = 0; // first unused entry
55
+
56
+ // block compression parameters -- after all codes are used up,
57
+ // and compression rate changes, start over.
58
+
59
+ var clear_flg = false;
60
+
61
+ // Algorithm: use open addressing double hashing (no chaining) on the
62
+ // prefix code / next character combination. We do a variant of Knuth's
63
+ // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
64
+ // secondary probe. Here, the modular division first probe is gives way
65
+ // to a faster exclusive-or manipulation. Also do block compression with
66
+ // an adaptive reset, whereby the code table is cleared when the compression
67
+ // ratio decreases, but after the table fills. The variable-length output
68
+ // codes are re-sized at this point, and a special CLEAR code is generated
69
+ // for the decompressor. Late addition: construct the table according to
70
+ // file size for noticeable speed improvement on small files. Please direct
71
+ // questions about this implementation to ames!jaw.
72
+
73
+ var g_init_bits;
74
+ var ClearCode;
75
+ var EOFCode;
76
+
77
+ // output
78
+ // Output the given code.
79
+ // Inputs:
80
+ // code: A n_bits-bit integer. If == -1, then EOF. This assumes
81
+ // that n_bits =< wordsize - 1.
82
+ // Outputs:
83
+ // Outputs code to the file.
84
+ // Assumptions:
85
+ // Chars are 8 bits long.
86
+ // Algorithm:
87
+ // Maintain a BITS character long buffer (so that 8 codes will
88
+ // fit in it exactly). Use the VAX insv instruction to insert each
89
+ // code in turn. When the buffer fills up empty it and start over.
90
+
91
+ var cur_accum = 0;
92
+ var cur_bits = 0;
93
+ var masks = [
94
+ 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff,
95
+ 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff,
96
+ ];
97
+
98
+ // Number of characters so far in this 'packet'
99
+ var a_count;
100
+
101
+ // Define the storage for the packet accumulator
102
+ var accum = [];
103
+
104
+ imgW = width;
105
+ imgH = height;
106
+ pixAry = pixels;
107
+ initCodeSize = Math.max(2, colorDepth);
108
+
109
+ // Add a character to the end of the current packet, and if it is 254
110
+ // characters, flush the packet to disk.
111
+ const char_out = (c, outs) => {
112
+ accum[a_count++] = c;
113
+ if (a_count >= 254) flush_char(outs);
114
+ };
115
+
116
+ // Clear out the hash table
117
+ // table clear for block compress
118
+ const cl_block = (outs) => {
119
+ cl_hash(hsize);
120
+ free_ent = ClearCode + 2;
121
+ clear_flg = true;
122
+ output(ClearCode, outs);
123
+ };
124
+
125
+ // reset code table
126
+ const cl_hash = (hsize) => {
127
+ for (var i = 0; i < hsize; ++i) htab[i] = -1;
128
+ };
129
+
130
+ // Flush the packet to disk, and reset the accumulator
131
+ const flush_char = (outs) => {
132
+ if (a_count > 0) {
133
+ outs.writeByte(a_count);
134
+ outs.writeBytes(accum, 0, a_count);
135
+ a_count = 0;
136
+ }
137
+ };
138
+
139
+ const MAXCODE = (n_bits) => {
140
+ return (1 << n_bits) - 1;
141
+ };
142
+
143
+ // ----------------------------------------------------------------------------
144
+ // Return the next pixel from the image
145
+ // ----------------------------------------------------------------------------
146
+
147
+ const nextPixel = () => {
148
+ if (remaining === 0) return EOF;
149
+ --remaining;
150
+ var pix = pixAry[curPixel++];
151
+ return pix & 0xff;
152
+ };
153
+
154
+ const output = (code, outs) => {
155
+ cur_accum &= masks[cur_bits];
156
+
157
+ if (cur_bits > 0) cur_accum |= code << cur_bits;
158
+ else cur_accum = code;
159
+
160
+ cur_bits += n_bits;
161
+
162
+ while (cur_bits >= 8) {
163
+ char_out(cur_accum & 0xff, outs);
164
+ cur_accum >>= 8;
165
+ cur_bits -= 8;
166
+ }
167
+
168
+ // If the next entry is going to be too big for the code size,
169
+ // then increase it, if possible.
170
+
171
+ if (free_ent > maxcode || clear_flg) {
172
+ if (clear_flg) {
173
+ maxcode = MAXCODE((n_bits = g_init_bits));
174
+ clear_flg = false;
175
+ } else {
176
+ ++n_bits;
177
+ if (n_bits === maxbits) maxcode = maxmaxcode;
178
+ else maxcode = MAXCODE(n_bits);
179
+ }
180
+ }
181
+
182
+ if (code === EOFCode) {
183
+ // At EOF, write the rest of the buffer.
184
+ while (cur_bits > 0) {
185
+ char_out(cur_accum & 0xff, outs);
186
+ cur_accum >>= 8;
187
+ cur_bits -= 8;
188
+ }
189
+
190
+ flush_char(outs);
191
+ }
192
+ };
193
+
194
+ const lzwencoder = {
195
+ compress: (init_bits, outs) => {
196
+ var fcode;
197
+ var i; /* = 0 */
198
+ var c;
199
+ var ent;
200
+ var disp;
201
+ var hsize_reg;
202
+ var hshift;
203
+
204
+ // Set up the globals: g_init_bits - initial number of bits
205
+ g_init_bits = init_bits;
206
+
207
+ // Set up the necessary values
208
+ clear_flg = false;
209
+ n_bits = g_init_bits;
210
+ maxcode = MAXCODE(n_bits);
211
+
212
+ ClearCode = 1 << (init_bits - 1);
213
+ EOFCode = ClearCode + 1;
214
+ free_ent = ClearCode + 2;
215
+
216
+ a_count = 0; // clear packet
217
+
218
+ ent = nextPixel();
219
+
220
+ hshift = 0;
221
+ for (fcode = hsize; fcode < 65536; fcode *= 2) ++hshift;
222
+ hshift = 8 - hshift; // set hash code range bound
223
+
224
+ hsize_reg = hsize;
225
+ cl_hash(hsize_reg); // clear hash table
226
+
227
+ output(ClearCode, outs);
228
+
229
+ outer_loop: while ((c = nextPixel()) !== EOF) {
230
+ fcode = (c << maxbits) + ent;
231
+ i = (c << hshift) ^ ent; // xor hashing
232
+
233
+ if (htab[i] === fcode) {
234
+ ent = codetab[i];
235
+ continue;
236
+ } else if (htab[i] >= 0) {
237
+ // non-empty slot
238
+
239
+ disp = hsize_reg - i; // secondary hash (after G. Knott)
240
+ if (i === 0) disp = 1;
241
+
242
+ do {
243
+ if ((i -= disp) < 0) i += hsize_reg;
244
+
245
+ if (htab[i] === fcode) {
246
+ ent = codetab[i];
247
+ continue outer_loop;
248
+ }
249
+ } while (htab[i] >= 0);
250
+ }
251
+
252
+ output(ent, outs);
253
+ ent = c;
254
+ if (free_ent < maxmaxcode) {
255
+ codetab[i] = free_ent++; // code -> hashtable
256
+ htab[i] = fcode;
257
+ } else cl_block(outs);
258
+ }
259
+
260
+ // Put out the final code.
261
+ output(ent, outs);
262
+ output(EOFCode, outs);
263
+ },
264
+ encode: (os) => {
265
+ os.writeByte(initCodeSize); // write "initial code size" byte
266
+ remaining = imgW * imgH; // reset navigation variables
267
+ curPixel = 0;
268
+ lzwencoder.compress(initCodeSize + 1, os); // compress and write the pixel data
269
+ os.writeByte(0); // write block terminator
270
+ },
271
+ };
272
+ return lzwencoder;
273
+ };
274
+
275
+ /*
276
+ * NeuQuant Neural-Net Quantization Algorithm
277
+ * ------------------------------------------
278
+ *
279
+ * Copyright (c) 1994 Anthony Dekker
280
+ *
281
+ * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
282
+ * "Kohonen neural networks for optimal colour quantization" in "Network:
283
+ * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
284
+ * the algorithm.
285
+ *
286
+ * Any party obtaining a copy of these files from the author, directly or
287
+ * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
288
+ * world-wide, paid up, royalty-free, nonexclusive right and license to deal in
289
+ * this software and documentation files (the "Software"), including without
290
+ * limitation the rights to use, copy, modify, merge, publish, distribute,
291
+ * sublicense, and/or sell copies of the Software, and to permit persons who
292
+ * receive copies from any such party to do so, with the only requirement being
293
+ * that this copyright notice remain intact.
294
+ */
295
+
296
+ /*
297
+ * This class handles Neural-Net quantization algorithm
298
+ * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
299
+ * @author Thibault Imbert (AS3 version - bytearray.org)
300
+ * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif)
301
+ * @version 0.1 AS3 implementation
302
+ */
303
+
304
+ const createNeuQuant = ({ pixels, len, sample }) => {
305
+ var netsize = 256; /* number of colours used */
306
+
307
+ /* four primes near 500 - assume no image has a length so large */
308
+ /* that it is divisible by all four primes */
309
+
310
+ var prime1 = 499;
311
+ var prime2 = 491;
312
+ var prime3 = 487;
313
+ var prime4 = 503;
314
+ var minpicturebytes = 3 * prime4; /* minimum size for input image */
315
+
316
+ /*
317
+ * Program Skeleton ---------------- [select samplefac in range 1..30] [read
318
+ * image from input file] pic = (unsigned char*) malloc(3*width*height);
319
+ * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
320
+ * image header, using writecolourmap(f)] inxbuild(); write output image using
321
+ * inxsearch(b,g,r)
322
+ */
323
+
324
+ /*
325
+ * Network Definitions -------------------
326
+ */
327
+
328
+ var maxnetpos = netsize - 1;
329
+ var netbiasshift = 4; /* bias for colour values */
330
+ var ncycles = 100; /* no. of learning cycles */
331
+
332
+ /* defs for freq and bias */
333
+ var intbiasshift = 16; /* bias for fractions */
334
+ var intbias = 1 << intbiasshift;
335
+ var gammashift = 10; /* gamma = 1024 */
336
+ var betashift = 10;
337
+ var beta = intbias >> betashift; /* beta = 1/1024 */
338
+ var betagamma = intbias << (gammashift - betashift);
339
+
340
+ /* defs for decreasing radius factor */
341
+ var initrad = netsize >> 3; /* for 256 cols, radius starts */
342
+ var radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
343
+ var radiusbias = 1 << radiusbiasshift;
344
+ var initradius = initrad * radiusbias; /* and decreases by a */
345
+ var radiusdec = 30; /* factor of 1/30 each cycle */
346
+
347
+ /* defs for decreasing alpha factor */
348
+ var alphabiasshift = 10; /* alpha starts at 1.0 */
349
+ var initalpha = 1 << alphabiasshift;
350
+ var alphadec; /* biased by 10 bits */
351
+
352
+ /* radbias and alpharadbias used for radpower calculation */
353
+ var radbiasshift = 8;
354
+ var radbias = 1 << radbiasshift;
355
+ var alpharadbshift = alphabiasshift + radbiasshift;
356
+ var alpharadbias = 1 << alpharadbshift;
357
+
358
+ /*
359
+ * Types and Global Variables --------------------------
360
+ */
361
+
362
+ var thepicture; /* the input image itself */
363
+ var lengthcount; /* lengthcount = H*W*3 */
364
+ var samplefac; /* sampling factor 1..30 */
365
+
366
+ // typedef int pixel[4]; /* BGRc */
367
+ var network; /* the network itself - [netsize][4] */
368
+ var netindex = [];
369
+
370
+ /* for network lookup - really 256 */
371
+ var bias = [];
372
+
373
+ /* bias and freq arrays for learning */
374
+ var freq = [];
375
+ var radpower = [];
376
+
377
+ var i;
378
+ var p;
379
+ thepicture = pixels;
380
+ lengthcount = len;
381
+ samplefac = sample;
382
+ network = new Array(netsize);
383
+
384
+ for (i = 0; i < netsize; i++) {
385
+ network[i] = new Array(4);
386
+ p = network[i];
387
+ p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
388
+ freq[i] = intbias / netsize; /* 1/netsize */
389
+ bias[i] = 0;
390
+ }
391
+
392
+ const colorMap = () => {
393
+ var map = [];
394
+ var index = new Array(netsize);
395
+
396
+ for (var i = 0; i < netsize; i++) index[network[i][3]] = i;
397
+
398
+ var k = 0;
399
+ for (var l = 0; l < netsize; l++) {
400
+ var j = index[l];
401
+ map[k++] = network[j][0];
402
+ map[k++] = network[j][1];
403
+ map[k++] = network[j][2];
404
+ }
405
+
406
+ return map;
407
+ };
408
+
409
+ /*
410
+ * Insertion sort of network and building of netindex[0..255] (to do after
411
+ * unbias)
412
+ * -------------------------------------------------------------------------------
413
+ */
414
+
415
+ const inxbuild = () => {
416
+ var i;
417
+ var j;
418
+ var smallpos;
419
+ var smallval;
420
+ var p;
421
+ var q;
422
+ var previouscol;
423
+ var startpos;
424
+
425
+ previouscol = 0;
426
+ startpos = 0;
427
+ for (i = 0; i < netsize; i++) {
428
+ p = network[i];
429
+ smallpos = i;
430
+ smallval = p[1]; /* index on g */
431
+
432
+ /* find smallest in i..netsize-1 */
433
+ for (j = i + 1; j < netsize; j++) {
434
+ q = network[j];
435
+ if (q[1] < smallval) {
436
+ /* index on g */
437
+ smallpos = j;
438
+ smallval = q[1]; /* index on g */
439
+ }
440
+ }
441
+ q = network[smallpos];
442
+
443
+ /* swap p (i) and q (smallpos) entries */
444
+ if (i !== smallpos) {
445
+ j = q[0];
446
+ q[0] = p[0];
447
+ p[0] = j;
448
+ j = q[1];
449
+ q[1] = p[1];
450
+ p[1] = j;
451
+ j = q[2];
452
+ q[2] = p[2];
453
+ p[2] = j;
454
+ j = q[3];
455
+ q[3] = p[3];
456
+ p[3] = j;
457
+ }
458
+
459
+ /* smallval entry is now in position i */
460
+
461
+ if (smallval !== previouscol) {
462
+ netindex[previouscol] = (startpos + i) >> 1;
463
+
464
+ for (j = previouscol + 1; j < smallval; j++) netindex[j] = i;
465
+
466
+ previouscol = smallval;
467
+ startpos = i;
468
+ }
469
+ }
470
+
471
+ netindex[previouscol] = (startpos + maxnetpos) >> 1;
472
+ for (j = previouscol + 1; j < 256; j++)
473
+ netindex[j] = maxnetpos; /* really 256 */
474
+ };
475
+
476
+ /*
477
+ * Main Learning Loop ------------------
478
+ */
479
+ const learn = () => {
480
+ var i;
481
+ var j;
482
+ var b;
483
+ var g;
484
+ var r;
485
+ var radius;
486
+ var rad;
487
+ var alpha;
488
+ var step;
489
+ var delta;
490
+ var samplepixels;
491
+ var p;
492
+ var pix;
493
+ var lim;
494
+
495
+ if (lengthcount < minpicturebytes) samplefac = 1;
496
+
497
+ alphadec = 30 + (samplefac - 1) / 3;
498
+ p = thepicture;
499
+ pix = 0;
500
+ lim = lengthcount;
501
+ samplepixels = lengthcount / (3 * samplefac);
502
+ delta = (samplepixels / ncycles) | 0;
503
+ alpha = initalpha;
504
+ radius = initradius;
505
+
506
+ rad = radius >> radiusbiasshift;
507
+ if (rad <= 1) rad = 0;
508
+
509
+ for (i = 0; i < rad; i++)
510
+ radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
511
+
512
+ if (lengthcount < minpicturebytes) {
513
+ step = 3;
514
+ } else if (lengthcount % prime1 !== 0) {
515
+ step = 3 * prime1;
516
+ } else if (lengthcount % prime2 === 0) {
517
+ if (lengthcount % prime3 === 0) {
518
+ step = 3 * prime4;
519
+ } else {
520
+ step = 3 * prime3;
521
+ }
522
+ } else {
523
+ step = 3 * prime2;
524
+ }
525
+
526
+ i = 0;
527
+ while (i < samplepixels) {
528
+ b = (p[pix + 0] & 0xff) << netbiasshift;
529
+ g = (p[pix + 1] & 0xff) << netbiasshift;
530
+ r = (p[pix + 2] & 0xff) << netbiasshift;
531
+ j = contest(b, g, r);
532
+
533
+ altersingle(alpha, j, b, g, r);
534
+ if (rad !== 0) alterneigh(rad, j, b, g, r); /* alter neighbours */
535
+
536
+ pix += step;
537
+ if (pix >= lim) pix -= lengthcount;
538
+
539
+ i++;
540
+
541
+ if (delta === 0) delta = 1;
542
+
543
+ if (i % delta === 0) {
544
+ alpha -= alpha / alphadec;
545
+ radius -= radius / radiusdec;
546
+ rad = radius >> radiusbiasshift;
547
+
548
+ if (rad <= 1) rad = 0;
549
+
550
+ for (j = 0; j < rad; j++)
551
+ radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
552
+ }
553
+ }
554
+ };
555
+
556
+ /*
557
+ * Unbias network to give byte values 0..255 and record position i to prepare
558
+ * for sort
559
+ * -----------------------------------------------------------------------------------
560
+ */
561
+ const unbiasnet = () => {
562
+ let i = 0;
563
+ while (i < netsize) {
564
+ network[i][0] >>= netbiasshift;
565
+ network[i][1] >>= netbiasshift;
566
+ network[i][2] >>= netbiasshift;
567
+ network[i][3] = i; /* record colour no */
568
+ i++;
569
+ }
570
+ };
571
+
572
+ /*
573
+ * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
574
+ * radpower[|i-j|]
575
+ * ---------------------------------------------------------------------------------
576
+ */
577
+ const alterneigh = (rad, i, b, g, r) => {
578
+ var j;
579
+ var k;
580
+ var lo;
581
+ var hi;
582
+ var a;
583
+ var m;
584
+ var p;
585
+
586
+ lo = i - rad;
587
+ if (lo < -1) lo = -1;
588
+
589
+ hi = i + rad;
590
+ if (hi > netsize) hi = netsize;
591
+
592
+ j = i + 1;
593
+ k = i - 1;
594
+ m = 1;
595
+
596
+ while (j < hi || k > lo) {
597
+ a = radpower[m++];
598
+
599
+ if (j < hi) {
600
+ p = network[j++];
601
+
602
+ try {
603
+ p[0] -= (a * (p[0] - b)) / alpharadbias;
604
+ p[1] -= (a * (p[1] - g)) / alpharadbias;
605
+ p[2] -= (a * (p[2] - r)) / alpharadbias;
606
+ } catch (e) {} // prevents 1.3 miscompilation
607
+ }
608
+
609
+ if (k > lo) {
610
+ p = network[k--];
611
+
612
+ try {
613
+ p[0] -= (a * (p[0] - b)) / alpharadbias;
614
+ p[1] -= (a * (p[1] - g)) / alpharadbias;
615
+ p[2] -= (a * (p[2] - r)) / alpharadbias;
616
+ } catch (e) {}
617
+ }
618
+ }
619
+ };
620
+
621
+ /*
622
+ * Move neuron i towards biased (b,g,r) by factor alpha
623
+ * ----------------------------------------------------
624
+ */
625
+ const altersingle = (alpha, i, b, g, r) => {
626
+ /* alter hit neuron */
627
+ var n = network[i];
628
+ n[0] -= (alpha * (n[0] - b)) / initalpha;
629
+ n[1] -= (alpha * (n[1] - g)) / initalpha;
630
+ n[2] -= (alpha * (n[2] - r)) / initalpha;
631
+ };
632
+
633
+ /*
634
+ * Search for biased BGR values ----------------------------
635
+ */
636
+ const contest = (b, g, r) => {
637
+ /* finds closest neuron (min dist) and updates freq */
638
+ /* finds best neuron (min dist-bias) and returns position */
639
+ /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
640
+ /* bias[i] = gamma*((1/netsize)-freq[i]) */
641
+
642
+ var i;
643
+ var dist;
644
+ var a;
645
+ var biasdist;
646
+ var betafreq;
647
+ var bestpos;
648
+ var bestbiaspos;
649
+ var bestd;
650
+ var bestbiasd;
651
+ var n;
652
+
653
+ bestd = 2147483647;
654
+ bestbiasd = bestd;
655
+ bestpos = -1;
656
+ bestbiaspos = bestpos;
657
+
658
+ for (i = 0; i < netsize; i++) {
659
+ n = network[i];
660
+ dist = n[0] - b;
661
+ if (dist < 0) dist = -dist;
662
+ a = n[1] - g;
663
+ if (a < 0) a = -a;
664
+ dist += a;
665
+ a = n[2] - r;
666
+ if (a < 0) a = -a;
667
+ dist += a;
668
+
669
+ if (dist < bestd) {
670
+ bestd = dist;
671
+ bestpos = i;
672
+ }
673
+
674
+ biasdist = dist - (bias[i] >> (intbiasshift - netbiasshift));
675
+
676
+ if (biasdist < bestbiasd) {
677
+ bestbiasd = biasdist;
678
+ bestbiaspos = i;
679
+ }
680
+
681
+ betafreq = freq[i] >> betashift;
682
+ freq[i] -= betafreq;
683
+ bias[i] += betafreq << gammashift;
684
+ }
685
+
686
+ freq[bestpos] += beta;
687
+ bias[bestpos] -= betagamma;
688
+ return bestbiaspos;
689
+ };
690
+
691
+ const neuquant = {
692
+ /*
693
+ ** Search for BGR values 0..255 (after net is unbiased) and return colour
694
+ * index
695
+ * ----------------------------------------------------------------------------
696
+ */
697
+ map: (b, g, r) => {
698
+ var i;
699
+ var j;
700
+ var dist;
701
+ var a;
702
+ var bestd;
703
+ var p;
704
+ var best;
705
+
706
+ bestd = 1000; /* biggest possible dist is 256*3 */
707
+ best = -1;
708
+ i = netindex[g]; /* index on g */
709
+ j = i - 1; /* start at netindex[g] and work outwards */
710
+
711
+ while (i < netsize || j >= 0) {
712
+ if (i < netsize) {
713
+ p = network[i];
714
+ dist = p[1] - g; /* inx key */
715
+
716
+ if (dist >= bestd) i = netsize; /* stop iter */
717
+ else {
718
+ i++;
719
+ if (dist < 0) dist = -dist;
720
+ a = p[0] - b;
721
+ if (a < 0) a = -a;
722
+ dist += a;
723
+
724
+ if (dist < bestd) {
725
+ a = p[2] - r;
726
+ if (a < 0) a = -a;
727
+ dist += a;
728
+
729
+ if (dist < bestd) {
730
+ bestd = dist;
731
+ best = p[3];
732
+ }
733
+ }
734
+ }
735
+ }
736
+
737
+ if (j >= 0) {
738
+ p = network[j];
739
+ dist = g - p[1]; /* inx key - reverse dif */
740
+
741
+ if (dist >= bestd) j = -1; /* stop iter */
742
+ else {
743
+ j--;
744
+ if (dist < 0) dist = -dist;
745
+ a = p[0] - b;
746
+ if (a < 0) a = -a;
747
+ dist += a;
748
+
749
+ if (dist < bestd) {
750
+ a = p[2] - r;
751
+ if (a < 0) a = -a;
752
+ dist += a;
753
+ if (dist < bestd) {
754
+ bestd = dist;
755
+ best = p[3];
756
+ }
757
+ }
758
+ }
759
+ }
760
+ }
761
+
762
+ return best;
763
+ },
764
+ process: () => {
765
+ learn();
766
+ unbiasnet();
767
+ inxbuild();
768
+ return colorMap();
769
+ },
770
+ };
771
+ return neuquant;
772
+ };
773
+
774
+ // https://github.com/incubated-geek-cc/video-to-GIF/blob/main/js/GIFEncoder.js
775
+ // https://github.com/jnordberg/gif.js
776
+ // https://github.com/jnordberg/gif.js/blob/master/src/GIFEncoder.js
777
+
778
+
779
+ const createGifEncoder = ({
780
+ width,
781
+ height,
782
+ /*
783
+ * Sets quality of color quantization (conversion of images to the maximum 256
784
+ * colors allowed by the GIF specification). Lower values (minimum = 1)
785
+ * produce better colors, but slow processing significantly. 10 is the
786
+ * default, and produces good color mapping at reasonable speeds. Values
787
+ * greater than 20 do not yield significant improvements in speed.
788
+ */
789
+ quality = 10,
790
+ /**
791
+ * Sets the number of times the set of GIF frames should be played.
792
+ * -1 means no repeat, 0 means indefinitely
793
+ *
794
+ * int number of iterations.
795
+ */
796
+ repeat = -1,
797
+ comment = "",
798
+ }) => {
799
+ const byteArray = [];
800
+ const writeByte = (data) => {
801
+ byteArray.push(data);
802
+ };
803
+ const writeUTFBytes = (string) => {
804
+ let i = 0;
805
+ while (i < string.length) {
806
+ writeByte(string.charCodeAt(i));
807
+ i++;
808
+ }
809
+ };
810
+ const writeBytes = (array, offset = 0, length = array.length) => {
811
+ let i = offset;
812
+ while (i < length) {
813
+ writeByte(array[i]);
814
+ i++;
815
+ }
816
+ };
817
+ let charCodeMap;
818
+ const fillCharCodes = () => {
819
+ if (charCodeMap) {
820
+ return;
821
+ }
822
+ charCodeMap = new Map();
823
+ let i = 0;
824
+ while (i < 256) {
825
+ charCodeMap.set(i, String.fromCharCode(i));
826
+ i++;
827
+ }
828
+ };
829
+ const readAsBinaryString = () => {
830
+ fillCharCodes();
831
+ let binaryString = "";
832
+ for (const byte of byteArray) {
833
+ binaryString += charCodeMap.get(byte);
834
+ }
835
+ return binaryString;
836
+ };
837
+
838
+ let transparent = null; // transparent color if given
839
+ let transIndex = 0; // transparent index in color table
840
+ let colorDepth; // number of bit planes
841
+ let usedEntry = []; // active palette entries
842
+ let palSize = 7; // color table size (bits-1)
843
+ let dispose = -1; // disposal code (-1 = use default
844
+ let firstFrame = true;
845
+ let finished = false;
846
+
847
+ const writeShort = (pValue) => {
848
+ writeByte(pValue & 0xff);
849
+ writeByte((pValue >> 8) & 0xff);
850
+ };
851
+
852
+ const encoder = {
853
+ /**
854
+ * Sets the GIF frame disposal code for the last added frame and any
855
+ * subsequent frames. Default is 0 if no transparent color has been set,
856
+ * otherwise 2.
857
+ * @param code
858
+ * int disposal code.
859
+ */
860
+ setDispose: (code) => {
861
+ if (code >= 0) dispose = code;
862
+ },
863
+
864
+ /**
865
+ * Sets the transparent color for the last added frame and any subsequent
866
+ * frames. Since all colors are subject to modification in the quantization
867
+ * process, the color in the final palette for each frame closest to the given
868
+ * color becomes the transparent color for that frame. May be set to null to
869
+ * indicate no transparent color.
870
+ * @param
871
+ * Color to be treated as transparent on display.
872
+ */
873
+ setTransparent: (c) => {
874
+ transparent = c;
875
+ },
876
+
877
+ /**
878
+ * The addFrame method takes an incoming BitmapData object to create each frames
879
+ * @param
880
+ * BitmapData object to be treated as a GIF's frame
881
+ */
882
+ addFrame: (context, options) => {
883
+ let delay = 0;
884
+ if (options) {
885
+ if (options.quality) {
886
+ quality = options.quality < 1 ? 1 : options.quality;
887
+ }
888
+ if (options.delay) {
889
+ delay = Math.round(options.delay / 10);
890
+ }
891
+ }
892
+
893
+ const canvas = context.canvas;
894
+ try {
895
+ const imageData = context.getImageData(
896
+ 0,
897
+ 0,
898
+ canvas.width,
899
+ canvas.height,
900
+ ).data;
901
+
902
+ const pixels = [];
903
+ // build pixels
904
+ {
905
+ var count = 0;
906
+ let y = 0;
907
+ while (y < height) {
908
+ let x = 0;
909
+ while (x < width) {
910
+ const b = y * width * 4 + x * 4;
911
+ pixels[count++] = imageData[b];
912
+ pixels[count++] = imageData[b + 1];
913
+ pixels[count++] = imageData[b + 2];
914
+ x++;
915
+ }
916
+ y++;
917
+ }
918
+ }
919
+ let colorTab;
920
+ let indexedPixels = [];
921
+ // analyze pixels ( build color table & map pixels)
922
+ {
923
+ var len = pixels.length;
924
+ var nPix = len / 3;
925
+ const neuQuant = createNeuQuant({
926
+ pixels,
927
+ len,
928
+ sample: quality,
929
+ });
930
+
931
+ // initialize quantizer
932
+ colorTab = neuQuant.process(); // create reduced palette
933
+
934
+ // map image pixels to new palette
935
+ var k = 0;
936
+ for (var j = 0; j < nPix; j++) {
937
+ var index = neuQuant.map(
938
+ pixels[k++] & 0xff,
939
+ pixels[k++] & 0xff,
940
+ pixels[k++] & 0xff,
941
+ );
942
+ usedEntry[index] = true;
943
+ indexedPixels[j] = index;
944
+ }
945
+
946
+ pixels.length = 0;
947
+ colorDepth = 8;
948
+ palSize = 7;
949
+
950
+ // get closest match to transparent color if specified
951
+ if (transparent !== null) {
952
+ /**
953
+ * Returns index of palette color closest to c
954
+ */
955
+ const findClosest = (c) => {
956
+ var r = (c & 0xff0000) >> 16;
957
+ var g = (c & 0x00ff00) >> 8;
958
+ var b = c & 0x0000ff;
959
+ var minpos = 0;
960
+ var dmin = 256 * 256 * 256;
961
+ var len = colorTab.length;
962
+
963
+ for (var i = 0; i < len; ) {
964
+ var dr = r - (colorTab[i++] & 0xff);
965
+ var dg = g - (colorTab[i++] & 0xff);
966
+ var db = b - (colorTab[i] & 0xff);
967
+ var d = dr * dr + dg * dg + db * db;
968
+ var index = i / 3;
969
+ if (usedEntry[index] && d < dmin) {
970
+ dmin = d;
971
+ minpos = index;
972
+ }
973
+ i++;
974
+ }
975
+ return minpos;
976
+ };
977
+ transIndex = colorTab === null ? -1 : findClosest(transparent);
978
+ }
979
+ }
980
+
981
+ const writePalette = () => {
982
+ writeBytes(colorTab);
983
+ var n = 3 * 256 - colorTab.length;
984
+ for (var i = 0; i < n; i++) writeByte(0);
985
+ };
986
+
987
+ if (firstFrame) {
988
+ // write logical screen descriptior
989
+ {
990
+ // logical screen size
991
+ writeShort(width);
992
+ writeShort(height);
993
+ // packed fields
994
+ writeByte(
995
+ 0x80 | // 1 : global color table flag = 1 (gct used)
996
+ 0x70 | // 2-4 : color resolution = 7
997
+ 0x00 | // 5 : gct sort flag = 0
998
+ palSize,
999
+ ); // 6-8 : gct size
1000
+
1001
+ writeByte(0); // background color index
1002
+ writeByte(0); // pixel aspect ratio - assume 1:1
1003
+ }
1004
+
1005
+ // write palette
1006
+ writePalette(); // global color table
1007
+
1008
+ // Writes Netscape application extension to define repeat count.
1009
+ if (repeat >= 0) {
1010
+ writeByte(0x21); // extension introducer
1011
+ writeByte(0xff); // app extension label
1012
+ writeByte(11); // block size
1013
+ writeUTFBytes("NETSCAPE2.0"); // app id + auth code
1014
+ writeByte(3); // sub-block size
1015
+ writeByte(1); // loop sub-block id
1016
+ writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
1017
+ writeByte(0); // block terminator
1018
+ }
1019
+ }
1020
+
1021
+ // write graphic control extension
1022
+ {
1023
+ writeByte(0x21); // extension introducer
1024
+ writeByte(0xf9); // GCE label
1025
+ writeByte(4); // data block size
1026
+ var transp;
1027
+ var disp;
1028
+ if (transparent === null) {
1029
+ transp = 0;
1030
+ disp = 0; // dispose = no action
1031
+ } else {
1032
+ transp = 1;
1033
+ disp = 2; // force clear if using transparent color
1034
+ }
1035
+ if (dispose >= 0) {
1036
+ disp = dispose & 7; // user override
1037
+ }
1038
+ disp <<= 2;
1039
+ // packed fields
1040
+ writeByte(
1041
+ 0 | // 1:3 reserved
1042
+ disp | // 4:6 disposal
1043
+ 0 | // 7 user input - 0 = none
1044
+ transp,
1045
+ ); // 8 transparency flag
1046
+
1047
+ writeShort(delay); // delay x 1/100 sec
1048
+ writeByte(transIndex); // transparent color index
1049
+ writeByte(0); // block terminator
1050
+ }
1051
+
1052
+ // write comment
1053
+ if (comment !== "") {
1054
+ writeByte(0x21); // extension introducer
1055
+ writeByte(0xfe); // comment label
1056
+ writeByte(comment.length); // Block Size (s)
1057
+ writeUTFBytes(comment);
1058
+ writeByte(0); // block terminator
1059
+ }
1060
+
1061
+ // write image descriptor
1062
+ {
1063
+ writeByte(0x2c); // image separator
1064
+ writeShort(0); // image position x,y = 0,0
1065
+ writeShort(0);
1066
+ writeShort(width); // image size
1067
+ writeShort(height);
1068
+
1069
+ // packed fields
1070
+ if (firstFrame) {
1071
+ // no LCT - GCT is used for first (or only) frame
1072
+ writeByte(0);
1073
+ } else {
1074
+ // specify normal LCT
1075
+ writeByte(
1076
+ 0x80 | // 1 local color table 1=yes
1077
+ 0 | // 2 interlace - 0=no
1078
+ 0 | // 3 sorted - 0=no
1079
+ 0 | // 4-5 reserved
1080
+ palSize,
1081
+ ); // 6-8 size of color table
1082
+ }
1083
+ }
1084
+
1085
+ if (!firstFrame) {
1086
+ writePalette(); // local color table
1087
+ }
1088
+ colorTab = null;
1089
+
1090
+ // encode and write pixel data
1091
+ {
1092
+ const lzwEncoder = createLzwEncoder({
1093
+ width,
1094
+ height,
1095
+ pixels: indexedPixels,
1096
+ colorDepth,
1097
+ });
1098
+ lzwEncoder.encode({
1099
+ writeByte,
1100
+ writeBytes,
1101
+ });
1102
+ }
1103
+ firstFrame = false;
1104
+ indexedPixels.length = 0;
1105
+ return true;
1106
+ } catch {
1107
+ return false;
1108
+ }
1109
+ },
1110
+
1111
+ /**
1112
+ * Adds final trailer to the GIF stream, if you don't call the finish method
1113
+ * the GIF asDataUrl will not be valid.
1114
+ */
1115
+ finish: () => {
1116
+ if (finished) return false;
1117
+ finished = true;
1118
+
1119
+ try {
1120
+ writeByte(0x3b); // gif trailer
1121
+ return true;
1122
+ } catch {
1123
+ return false;
1124
+ }
1125
+ },
1126
+
1127
+ readAsBinaryString,
1128
+ toDataUrl: () => {
1129
+ const gifAsBinaryString = readAsBinaryString();
1130
+ return `data:image/gif;base64,${encode64(gifAsBinaryString)}`;
1131
+ },
1132
+ };
1133
+
1134
+ writeUTFBytes("GIF89a"); // header
1135
+
1136
+ return encoder;
1137
+ };
1138
+
1139
+ const encode64 = (input) => {
1140
+ let output = "";
1141
+ let i = 0;
1142
+ let l = input.length;
1143
+ let key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
1144
+ let chr1;
1145
+ let chr2;
1146
+ let chr3;
1147
+ let enc1;
1148
+ let enc2;
1149
+ let enc3;
1150
+ let enc4;
1151
+ while (i < l) {
1152
+ chr1 = input.charCodeAt(i++);
1153
+ chr2 = input.charCodeAt(i++);
1154
+ chr3 = input.charCodeAt(i++);
1155
+ enc1 = chr1 >> 2;
1156
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
1157
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
1158
+ enc4 = chr3 & 63;
1159
+ if (isNaN(chr2)) enc3 = enc4 = 64;
1160
+ else if (isNaN(chr3)) enc4 = 64;
1161
+ output =
1162
+ output +
1163
+ key.charAt(enc1) +
1164
+ key.charAt(enc2) +
1165
+ key.charAt(enc3) +
1166
+ key.charAt(enc4);
1167
+ }
1168
+ return output;
1169
+ };
1170
+
1171
+ /*
1172
+ on veut pouvoir:
1173
+ - mettre le terminal dans un "fake terminal" genre
1174
+ avec le style de celui svg
1175
+ - enregister une vidéo du terminal
1176
+ ça faut que je regarde les apis canvas
1177
+
1178
+ https://github.com/welefen/canvas2video/blob/master/src/index.ts
1179
+ https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream
1180
+ https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder
1181
+ https://webglfundamentals.org/webgl/lessons/webgl-tips.html
1182
+ https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts
1183
+ */
1184
+
1185
+
1186
+ const { Terminal } = window;
1187
+ const { FitAddon } = window.FitAddon;
1188
+ const { WebglAddon } = window.WebglAddon;
1189
+ const { SerializeAddon } = window.SerializeAddon;
1190
+
1191
+ const initTerminal = ({
1192
+ cols = 80,
1193
+ rows = 25,
1194
+ paddingLeft = 15,
1195
+ paddingRight = 15,
1196
+ fontFamily = "SauceCodePro Nerd Font, Source Code Pro, Courier",
1197
+ fontSize = 12,
1198
+ convertEol = true,
1199
+ textInViewport,
1200
+ gif,
1201
+ video,
1202
+ logs,
1203
+ }) => {
1204
+ if (textInViewport === true) textInViewport = {};
1205
+ if (gif === true) gif = {};
1206
+ if (video === true) video = {};
1207
+ if (!textInViewport && !video && !gif) {
1208
+ throw new Error("ansi, video or gif must be enabled");
1209
+ }
1210
+
1211
+ const log = (...args) => {
1212
+ if (logs) {
1213
+ console.log(...args);
1214
+ }
1215
+ };
1216
+
1217
+ log("init", { cols, rows, fontFamily, fontSize, convertEol, gif, video });
1218
+
1219
+ const cssUrl = new URL("/css/xterm.css", import.meta.url);
1220
+ const link = document.createElement("link");
1221
+ link.rel = "stylesheet";
1222
+ link.href = cssUrl;
1223
+ document.head.appendChild(link);
1224
+
1225
+ const term = new Terminal({
1226
+ convertEol,
1227
+ disableStdin: true,
1228
+ // cursorBlink: false,
1229
+ cursorInactiveStyle: "none",
1230
+ cols,
1231
+ rows,
1232
+ fontFamily,
1233
+ fontSize,
1234
+ // lineHeight: 18,
1235
+ theme: {
1236
+ background: "#282c34",
1237
+ foreground: "#abb2bf",
1238
+ selectionBackground: "#001122",
1239
+ selectionForeground: "#777777",
1240
+ brightRed: "#B22222",
1241
+ brightBlue: "#87CEEB",
1242
+ },
1243
+ });
1244
+ const serializeAddon = new SerializeAddon();
1245
+ term.loadAddon(serializeAddon);
1246
+ const fitAddon = new FitAddon();
1247
+ term.loadAddon(fitAddon);
1248
+ const webglAddon = new WebglAddon(
1249
+ // we pass true to enable preserveDrawingBuffer
1250
+ // it's not actually useful thanks to term.write callback +
1251
+ // call on _innerRefresh() before context.drawImage
1252
+ // but let's keep it nevertheless
1253
+ true,
1254
+ );
1255
+ term.loadAddon(webglAddon);
1256
+ const terminalElement = document.querySelector("#terminal");
1257
+ term.open(terminalElement);
1258
+ fitAddon.fit();
1259
+
1260
+ const canvas = document.querySelector("#canvas");
1261
+ if (!canvas) {
1262
+ throw new Error('Cannot find <canvas id="canvas"> in the document');
1263
+ }
1264
+ const xTermCanvasSelector = "#terminal canvas.xterm-link-layer + canvas";
1265
+ const xtermCanvas = document.querySelector(xTermCanvasSelector);
1266
+ if (!xtermCanvas) {
1267
+ throw new Error(`Cannot find xterm canvas (${xTermCanvasSelector})`);
1268
+ }
1269
+
1270
+ return {
1271
+ term,
1272
+ startRecording: async () => {
1273
+ const context = canvas.getContext("2d", { willReadFrequently: true });
1274
+ context.imageSmoothingEnabled = true;
1275
+ const headerHeight = 40;
1276
+ canvas.width = paddingLeft + xtermCanvas.width + paddingRight;
1277
+ canvas.height = headerHeight + xtermCanvas.height;
1278
+ {
1279
+ drawRectangle(context, {
1280
+ x: 0,
1281
+ y: 0,
1282
+ width: canvas.width,
1283
+ height: canvas.height,
1284
+ fill: "#282c34",
1285
+ stroke: "rgba(255,255,255,0.35)",
1286
+ strokeWidth: 10,
1287
+ radius: 8,
1288
+ });
1289
+ drawCircle(context, {
1290
+ x: 20,
1291
+ y: headerHeight / 2,
1292
+ radius: 6,
1293
+ fill: "#ff5f57",
1294
+ });
1295
+ drawCircle(context, {
1296
+ x: 40,
1297
+ y: headerHeight / 2,
1298
+ radius: 6,
1299
+ fill: "#febc2e",
1300
+ });
1301
+ drawCircle(context, {
1302
+ x: 60,
1303
+ y: headerHeight / 2,
1304
+ radius: 6,
1305
+ fill: "#28c840",
1306
+ });
1307
+
1308
+ // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text
1309
+ const text = "Terminal";
1310
+ context.font = `${fontSize}px ${fontFamily}`;
1311
+ context.textBaseline = "middle";
1312
+ context.textAlign = "center";
1313
+ // const textSize = context.measureText(text);
1314
+ context.fillStyle = "#abb2bf";
1315
+ context.fillText(text, canvas.width / 2, headerHeight / 2);
1316
+ }
1317
+
1318
+ const records = {};
1319
+ let writePromise = Promise.resolve();
1320
+ const frameCallbackSet = new Set();
1321
+ const stopCallbackSet = new Set();
1322
+ if (textInViewport) {
1323
+ log("start recording text in viewport");
1324
+ // https://github.com/xtermjs/xterm.js/issues/3681
1325
+ // https://github.com/xtermjs/xterm.js/issues/3681
1326
+ // get the first line
1327
+ // term.buffer.active.getLine(term.buffer.active.viewportY).translateToString()
1328
+ // get the last line
1329
+ // term.buffer.active.getLine(term.buffer.active.length -1).translateToString()
1330
+ // https://github.com/xtermjs/xterm.js/blob/a7952ff36c60ee6dce9141744b1355a5d582ee39/addons/addon-serialize/src/SerializeAddon.ts
1331
+ stopCallbackSet.add(() => {
1332
+ const startY = term.buffer.active.viewportY;
1333
+ const endY = term.buffer.active.length - 1;
1334
+ const range = {
1335
+ start: startY,
1336
+ end: endY,
1337
+ };
1338
+ log(`lines in viewport: ${startY}:${endY}`);
1339
+ const output = serializeAddon.serialize({
1340
+ range,
1341
+ });
1342
+ log(`text in viewport: ${output}`);
1343
+ records.textInViewport = output;
1344
+ });
1345
+ }
1346
+ if (video) {
1347
+ log("start recording video");
1348
+ const { mimeType = "video/webm;codecs=h264", msAddedAtTheEnd } = video;
1349
+ if (!MediaRecorder.isTypeSupported(mimeType)) {
1350
+ throw new Error(`MediaRecorder does not support "${mimeType}"`);
1351
+ }
1352
+ const stream = canvas.captureStream();
1353
+ const mediaRecorder = new MediaRecorder(stream, {
1354
+ videoBitsPerSecond: 2_500_000,
1355
+ mimeType,
1356
+ });
1357
+ const chunks = [];
1358
+ mediaRecorder.ondataavailable = (e) => {
1359
+ if (e.data && e.data.size) {
1360
+ chunks.push(e.data);
1361
+ }
1362
+ };
1363
+ log("starting media recorder");
1364
+ const startPromise = new Promise((resolve, reject) => {
1365
+ mediaRecorder.onstart = () => {
1366
+ resolve();
1367
+ };
1368
+ mediaRecorder.onerror = (e) => {
1369
+ log("media recorder error");
1370
+ reject(e);
1371
+ };
1372
+ mediaRecorder.start();
1373
+ });
1374
+ let timeout;
1375
+ let hasTimedout = false;
1376
+ const MS_ALLOCATED_TO_MEDIA_RECORDER = 2_000;
1377
+ await Promise.race([
1378
+ startPromise,
1379
+ new Promise((resolve) => {
1380
+ timeout = setTimeout(() => {
1381
+ hasTimedout = true;
1382
+ resolve();
1383
+ }, MS_ALLOCATED_TO_MEDIA_RECORDER);
1384
+ }),
1385
+ ]);
1386
+ if (hasTimedout) {
1387
+ throw new Error(
1388
+ `media recorder did not start in less than ${MS_ALLOCATED_TO_MEDIA_RECORDER}ms`,
1389
+ );
1390
+ }
1391
+ clearTimeout(timeout);
1392
+ log("media recorder started");
1393
+
1394
+ stopCallbackSet.add(async () => {
1395
+ if (msAddedAtTheEnd) {
1396
+ await new Promise((resolve) => {
1397
+ setTimeout(resolve, msAddedAtTheEnd);
1398
+ });
1399
+ replicateXterm();
1400
+ } else {
1401
+ replicateXterm();
1402
+ }
1403
+ await new Promise((resolve) => {
1404
+ setTimeout(resolve, 150);
1405
+ });
1406
+ await new Promise((resolve, reject) => {
1407
+ mediaRecorder.onstop = () => {
1408
+ resolve();
1409
+ };
1410
+ mediaRecorder.onerror = (e) => {
1411
+ reject(e);
1412
+ };
1413
+ mediaRecorder.stop();
1414
+ });
1415
+ const blob = new Blob(chunks, { type: mediaRecorder.mimeType });
1416
+ const videoBinaryString = await new Promise((resolve, reject) => {
1417
+ const reader = new FileReader();
1418
+ reader.onloadend = () => resolve(reader.result);
1419
+ reader.onerror = reject;
1420
+ reader.readAsBinaryString(blob);
1421
+ });
1422
+ records.video = videoBinaryString;
1423
+ });
1424
+ }
1425
+ if (gif) {
1426
+ log("start recording gif");
1427
+ const { repeat = -1, quality, msAddedAtTheEnd } = gif;
1428
+ const gifEncoder = createGifEncoder({
1429
+ width: canvas.width,
1430
+ height: canvas.height,
1431
+ repeat: repeat === true ? 0 : repeat === false ? -1 : repeat,
1432
+ quality,
1433
+ });
1434
+ frameCallbackSet.add(({ delay }) => {
1435
+ log(`add frame to gif with delay of ${delay}ms`);
1436
+ gifEncoder.addFrame(context, { delay });
1437
+ });
1438
+ stopCallbackSet.add(async () => {
1439
+ const now = Date.now();
1440
+ drawFrame({
1441
+ delay: now - previousTime,
1442
+ });
1443
+ if (msAddedAtTheEnd) {
1444
+ drawFrame({
1445
+ delay: now - msAddedAtTheEnd,
1446
+ });
1447
+ }
1448
+ gifEncoder.finish();
1449
+ log("gif recording stopped");
1450
+ const gifBinaryString = gifEncoder.readAsBinaryString();
1451
+ records.gif = gifBinaryString;
1452
+ });
1453
+ log("gif recorder started");
1454
+ }
1455
+
1456
+ const startTime = Date.now();
1457
+ let previousTime = startTime;
1458
+ let isFirstDraw = true;
1459
+ const drawFrame = (options) => {
1460
+ for (const frameCallback of frameCallbackSet) {
1461
+ frameCallback(options);
1462
+ }
1463
+ };
1464
+
1465
+ const replicateXterm = () => {
1466
+ context.drawImage(
1467
+ xtermCanvas,
1468
+ 0,
1469
+ 0,
1470
+ xtermCanvas.width,
1471
+ xtermCanvas.height,
1472
+ paddingLeft,
1473
+ headerHeight,
1474
+ xtermCanvas.width,
1475
+ xtermCanvas.height,
1476
+ );
1477
+ };
1478
+
1479
+ replicateXterm();
1480
+
1481
+ return {
1482
+ writeIntoTerminal: async (data, options = {}) => {
1483
+ if (isFirstDraw) {
1484
+ isFirstDraw = false;
1485
+ drawFrame({ delay: 0 });
1486
+ }
1487
+ let { delay } = options;
1488
+ if (delay === undefined) {
1489
+ const now = Date.now();
1490
+ delay = now - previousTime;
1491
+ }
1492
+ writePromise = new Promise((resolve) => {
1493
+ log(`write data:`, data);
1494
+ term.write(data, () => {
1495
+ term._core._renderService._renderDebouncer._innerRefresh();
1496
+ replicateXterm();
1497
+ drawFrame({ delay });
1498
+ resolve();
1499
+ });
1500
+ });
1501
+ await writePromise;
1502
+ },
1503
+ stopRecording: async () => {
1504
+ await writePromise;
1505
+ const promises = [];
1506
+ for (const stopCallback of stopCallbackSet) {
1507
+ promises.push(stopCallback());
1508
+ }
1509
+ await Promise.all(promises);
1510
+ return records;
1511
+ },
1512
+ };
1513
+ },
1514
+ };
1515
+ };
1516
+
1517
+ const drawRectangle = (
1518
+ context,
1519
+ { x, y, width, height, radius, fill, stroke, strokeWidth },
1520
+ ) => {
1521
+ context.beginPath();
1522
+ {
1523
+ context.roundRect(x, y, width, height, [radius]);
1524
+ }
1525
+ {
1526
+ context.fillStyle = fill;
1527
+ context.fill();
1528
+ }
1529
+ {
1530
+ context.strokeWidth = strokeWidth;
1531
+ context.strokeStyle = stroke;
1532
+ context.stroke();
1533
+ }
1534
+ };
1535
+ const drawCircle = (context, { x, y, radius, fill, stroke, borderWidth }) => {
1536
+ context.beginPath();
1537
+ context.arc(x, y, radius, 0, 2 * Math.PI, false);
1538
+ if (fill) {
1539
+ context.fillStyle = fill;
1540
+ context.fill();
1541
+ }
1542
+ if (borderWidth) {
1543
+ context.lineWidth = 5;
1544
+ }
1545
+ if (stroke) {
1546
+ context.strokeStyle = stroke;
1547
+ context.stroke();
1548
+ }
1549
+ };
1550
+
1551
+ window.initTerminal = initTerminal;
23
1552
  window.resolveXtermReadyPromise();
24
- </script>
1553
+ </script>
25
1554
  </body>
26
1555
  </html>