coneko-term 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mm1995tk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,18 @@
1
+ /** 画素の並び(RGBA、1 画素 4 バイト、まっすぐなアルファ)とその大きさ。 */
2
+ export interface Pixels {
3
+ rgba: Uint8ClampedArray;
4
+ width: number;
5
+ height: number;
6
+ }
7
+ /** 1 色(0xRRGGBBAA)で塗った画素の並び。 */
8
+ export declare function solid(width: number, height: number, color: number): Pixels;
9
+ /**
10
+ * `src` の四角(`sx`, `sy` から幅 `w`・高さ `h`)を、`dst` の `dx`, `dy` へ重ねる。
11
+ * `overwrite` なら置き換え、そうでなければアルファ合成(`src` が上)。どちらかの画素の外に
12
+ * はみ出す分は切る。
13
+ */
14
+ export declare function compose(dst: Pixels, src: Pixels, sx: number, sy: number, dx: number, dy: number, w: number, h: number, overwrite: boolean): void;
15
+ /** 画素の並びの写し。 */
16
+ export declare function clone(p: Pixels): Pixels;
17
+ /** 生の RGB(1 画素 3 バイト)か RGBA(4 バイト)から、RGBA の画素の並びを作る。長さが合わなければ `undefined`。 */
18
+ export declare function fromRaw(bytes: Uint8Array, bpp: 3 | 4, width: number, height: number): Pixels | undefined;
@@ -0,0 +1,72 @@
1
+ /** 1 色(0xRRGGBBAA)で塗った画素の並び。 */
2
+ export function solid(width, height, color) {
3
+ const rgba = new Uint8ClampedArray(width * height * 4);
4
+ const r = (color >>> 24) & 0xff;
5
+ const g = (color >>> 16) & 0xff;
6
+ const b = (color >>> 8) & 0xff;
7
+ const a = color & 0xff;
8
+ if (r || g || b || a) {
9
+ for (let i = 0; i < rgba.length; i += 4) {
10
+ rgba[i] = r;
11
+ rgba[i + 1] = g;
12
+ rgba[i + 2] = b;
13
+ rgba[i + 3] = a;
14
+ }
15
+ }
16
+ return { rgba, width, height };
17
+ }
18
+ /**
19
+ * `src` の四角(`sx`, `sy` から幅 `w`・高さ `h`)を、`dst` の `dx`, `dy` へ重ねる。
20
+ * `overwrite` なら置き換え、そうでなければアルファ合成(`src` が上)。どちらかの画素の外に
21
+ * はみ出す分は切る。
22
+ */
23
+ export function compose(dst, src, sx, sy, dx, dy, w, h, overwrite) {
24
+ // はみ出す分を切る
25
+ const w2 = Math.min(w, src.width - sx, dst.width - dx);
26
+ const h2 = Math.min(h, src.height - sy, dst.height - dy);
27
+ for (let y = 0; y < h2; y++) {
28
+ let s = ((sy + y) * src.width + sx) * 4;
29
+ let d = ((dy + y) * dst.width + dx) * 4;
30
+ for (let x = 0; x < w2; x++, s += 4, d += 4) {
31
+ const sa = src.rgba[s + 3];
32
+ if (overwrite || sa === 255) {
33
+ dst.rgba[d] = src.rgba[s];
34
+ dst.rgba[d + 1] = src.rgba[s + 1];
35
+ dst.rgba[d + 2] = src.rgba[s + 2];
36
+ dst.rgba[d + 3] = sa;
37
+ continue;
38
+ }
39
+ if (sa === 0)
40
+ continue;
41
+ const da = dst.rgba[d + 3];
42
+ // 上に重ねる(over): 出力のアルファ = sa + da * (1 - sa)
43
+ const wd = (da * (255 - sa)) / 255;
44
+ const oa = sa + wd;
45
+ for (let c = 0; c < 3; c++) {
46
+ dst.rgba[d + c] = (src.rgba[s + c] * sa + dst.rgba[d + c] * wd) / oa;
47
+ }
48
+ dst.rgba[d + 3] = oa;
49
+ }
50
+ }
51
+ }
52
+ /** 画素の並びの写し。 */
53
+ export function clone(p) {
54
+ return { rgba: p.rgba.slice(), width: p.width, height: p.height };
55
+ }
56
+ /** 生の RGB(1 画素 3 バイト)か RGBA(4 バイト)から、RGBA の画素の並びを作る。長さが合わなければ `undefined`。 */
57
+ export function fromRaw(bytes, bpp, width, height) {
58
+ if (!(width > 0) || !(height > 0) || bytes.length !== width * height * bpp) {
59
+ return undefined;
60
+ }
61
+ if (bpp === 4) {
62
+ return { rgba: new Uint8ClampedArray(bytes), width, height };
63
+ }
64
+ const rgba = new Uint8ClampedArray(width * height * 4);
65
+ for (let i = 0, j = 0; i < bytes.length; i += 3, j += 4) {
66
+ rgba[j] = bytes[i];
67
+ rgba[j + 1] = bytes[i + 1];
68
+ rgba[j + 2] = bytes[i + 2];
69
+ rgba[j + 3] = 255;
70
+ }
71
+ return { rgba, width, height };
72
+ }
@@ -0,0 +1,116 @@
1
+ import { type Pixels } from "./animation.js";
2
+ /** `WasmTerminal.placements` の 1 つの配置が使う数。並びは `ImagePlacement` の順。 */
3
+ export declare const PLACEMENT_STRIDE = 16;
4
+ /** `z` がこれより小さい画像は、背景色の下に描く(kitty の仕様)。 */
5
+ export declare const Z_UNDER_BACKGROUND = -1073741824;
6
+ /** 画面に置いてある画像の配置 1 つ。 */
7
+ export interface ImagePlacement {
8
+ /** 左上のセルの行。見えている画面の一番上を 0 とする。スクロールバックの中は負。 */
9
+ row: number;
10
+ /** 画像の ID。 */
11
+ imageId: number;
12
+ /** 配置の番号(0 は無い)。 */
13
+ placementId: number;
14
+ /** 左上のセルの列。 */
15
+ col: number;
16
+ /** 元の画像の使う部分の左端・上端・幅・高さ(画素)。幅と高さが 0 なら端まで。 */
17
+ srcX: number;
18
+ srcY: number;
19
+ srcW: number;
20
+ srcH: number;
21
+ /** 左上のセルの中でのずれ(画素)。 */
22
+ offsetX: number;
23
+ offsetY: number;
24
+ /** 使うセルの列数・行数。 */
25
+ cols: number;
26
+ rows: number;
27
+ /** 重ねる順。負なら字の下、0 以上なら字の上。 */
28
+ z: number;
29
+ /** 画素のまま置くか(そうでなければ `cols` x `rows` のセルいっぱいに引き伸ばす)。 */
30
+ native: boolean;
31
+ /** 画素で決めた描く大きさ(幅・高さ)。0 なら `native` と `cols` x `rows` で決める。 */
32
+ destW: number;
33
+ destH: number;
34
+ }
35
+ /** `WasmTerminal.placements` の並びを読む。 */
36
+ export declare function parsePlacements(flat: ArrayLike<number>): ImagePlacement[];
37
+ /** 描ける画像(`ImageBitmap` など)。 */
38
+ export interface DrawableImage {
39
+ readonly width: number;
40
+ readonly height: number;
41
+ close?(): void;
42
+ }
43
+ /** 画像を読み解く道具。既定はブラウザーの機能で、テストでは入れ替える。 */
44
+ export interface ImageDecoder {
45
+ /** zlib(`o=z`)を戻す。 */
46
+ inflate(bytes: Uint8Array): Promise<Uint8Array>;
47
+ /** PNG を読み解く。 */
48
+ fromPng(bytes: Uint8Array): Promise<DrawableImage>;
49
+ /** RGBA(1 画素 4 バイト)の画素から作る。 */
50
+ fromRgba(rgba: Uint8ClampedArray, width: number, height: number): Promise<DrawableImage>;
51
+ /** PNG を RGBA の画素に読み解く。アニメーションのコマの合成に使う。無ければ、PNG の画像はアニメーションにせず、根だけを描く。 */
52
+ toRgba?(bytes: Uint8Array): Promise<Pixels>;
53
+ }
54
+ /** `DecompressionStream`、`createImageBitmap`、`ImageData` を使う、既定の `ImageDecoder`。 */
55
+ export declare const browserImageDecoder: ImageDecoder;
56
+ /** `ImageCache` が `Terminal` から読むもの。 */
57
+ export interface ImageSource {
58
+ /** 持っている画像の `[ID, 通し番号]` の並び。 */
59
+ imageSerials(): ArrayLike<number>;
60
+ /** 画像の `[通し番号, 形式(0 RGB、1 RGBA、2 PNG), 圧縮, 幅, 高さ]`。無ければ空。 */
61
+ imageInfo(id: number): ArrayLike<number>;
62
+ /** 画像のバイト列(圧縮も PNG もそのまま)。 */
63
+ imageData(id: number): Uint8Array;
64
+ /** アニメーションの状態 `[コマの数, いまのコマ, 再生の状態(1 止まる、2・3 回す), いまのコマの表示時間(ミリ秒), 操作の数]`。無ければ空。 */
65
+ animationInfo?(id: number): ArrayLike<number>;
66
+ /** いまのコマの表示時間が過ぎたと知らせる。次のコマへ進んだら `true`。 */
67
+ animationStep?(id: number): boolean;
68
+ /** コマを作る `index` 番目の操作(`WasmTerminal.frame_op` の並び)。 */
69
+ frameOp?(id: number, index: number): ArrayLike<number>;
70
+ /** 操作のバイト列。 */
71
+ frameOpData?(id: number, index: number): Uint8Array;
72
+ }
73
+ /**
74
+ * 読み解いた画像を ID ごとに覚えておく。画像が消えたり、同じ ID で別の画像に変わったら捨てる
75
+ * (`sync`)。読み解けない画像は描かない(エラーにしない)。読み解きは非同期なので、
76
+ * 終わったら `onReady` を呼ぶ。
77
+ *
78
+ * コマを足した画像(アニメーション)は、コマを作る操作を順に当てて画素を作り、いまのコマの
79
+ * 絵を返す(`get`)。コマを進める時刻は `tick` で決める。時計はこちらが持つ。
80
+ */
81
+ export declare class ImageCache {
82
+ private source;
83
+ private decoder;
84
+ private onReady;
85
+ private entries;
86
+ private timers;
87
+ constructor(source: ImageSource, decoder: ImageDecoder, onReady: () => void);
88
+ /** 持っている画像に合わせて、いらなくなったものを捨てる。 */
89
+ sync(): void;
90
+ /** 画像 `id` の、コマを足してあれば、その状態。 */
91
+ private animation;
92
+ /**
93
+ * 時刻 `now`(ミリ秒)で、画像 `id` のコマを進める。表示時間が過ぎていたら次のコマへ。
94
+ * 回し続けている(また呼ぶ必要がある)なら `true`。
95
+ */
96
+ tick(id: number, now: number): boolean;
97
+ /** 画像 `id` の絵が変わるたびに増える数(アニメーションでなければ 0)。 */
98
+ version(id: number): number;
99
+ /** 読み解き終えた画像。まだ、または読み解けなかったら `undefined`(初めて頼まれたら読み解きを始める)。 */
100
+ get(id: number): DrawableImage | undefined;
101
+ /** 画像そのもの(根)を読み解き始める。 */
102
+ private startDecode;
103
+ private drop;
104
+ private decode;
105
+ /** 形式 `format`(0 RGB、1 RGBA、2 PNG)のデータを、画素の並びに読み解く。 */
106
+ private toPixels;
107
+ private getAnimated;
108
+ /** 覚えている絵が多すぎたら、見せていない古いものから閉じる。 */
109
+ private trim;
110
+ /** 届いている操作を、まだ当てていないぶん順に当てる。 */
111
+ private pump;
112
+ /** 操作 1 つを当てる。変わったコマの番号(1 始まり)を返す。 */
113
+ private apply;
114
+ /** 全部捨てる。 */
115
+ dispose(): void;
116
+ }
package/dist/images.js ADDED
@@ -0,0 +1,359 @@
1
+ import { clone, compose, fromRaw, solid } from "./animation.js";
2
+ /** `WasmTerminal.placements` の 1 つの配置が使う数。並びは `ImagePlacement` の順。 */
3
+ export const PLACEMENT_STRIDE = 16;
4
+ /** `z` がこれより小さい画像は、背景色の下に描く(kitty の仕様)。 */
5
+ export const Z_UNDER_BACKGROUND = -1_073_741_824;
6
+ /** `WasmTerminal.placements` の並びを読む。 */
7
+ export function parsePlacements(flat) {
8
+ const out = [];
9
+ for (let o = 0; o + PLACEMENT_STRIDE <= flat.length; o += PLACEMENT_STRIDE) {
10
+ out.push({
11
+ row: flat[o],
12
+ imageId: flat[o + 1],
13
+ placementId: flat[o + 2],
14
+ col: flat[o + 3],
15
+ srcX: flat[o + 4],
16
+ srcY: flat[o + 5],
17
+ srcW: flat[o + 6],
18
+ srcH: flat[o + 7],
19
+ offsetX: flat[o + 8],
20
+ offsetY: flat[o + 9],
21
+ cols: flat[o + 10],
22
+ rows: flat[o + 11],
23
+ z: flat[o + 12],
24
+ native: flat[o + 13] !== 0,
25
+ destW: flat[o + 14],
26
+ destH: flat[o + 15],
27
+ });
28
+ }
29
+ return out;
30
+ }
31
+ /** `DecompressionStream`、`createImageBitmap`、`ImageData` を使う、既定の `ImageDecoder`。 */
32
+ export const browserImageDecoder = {
33
+ async inflate(bytes) {
34
+ const stream = new Blob([bytes])
35
+ .stream()
36
+ .pipeThrough(new DecompressionStream("deflate"));
37
+ return new Uint8Array(await new Response(stream).arrayBuffer());
38
+ },
39
+ fromPng(bytes) {
40
+ return createImageBitmap(new Blob([bytes], { type: "image/png" }));
41
+ },
42
+ fromRgba(rgba, width, height) {
43
+ return createImageBitmap(new ImageData(rgba, width, height));
44
+ },
45
+ async toRgba(bytes) {
46
+ const bitmap = await createImageBitmap(new Blob([bytes], { type: "image/png" }));
47
+ try {
48
+ const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
49
+ const ctx = canvas.getContext("2d");
50
+ if (!ctx)
51
+ throw new Error("coneko-term: canvas 2d context is unavailable");
52
+ ctx.drawImage(bitmap, 0, 0);
53
+ const data = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
54
+ return { rgba: data.data, width: bitmap.width, height: bitmap.height };
55
+ }
56
+ finally {
57
+ bitmap.close();
58
+ }
59
+ },
60
+ };
61
+ /** 絵を覚えておくコマの数。これを超えたら古いものから閉じる。 */
62
+ const BITMAP_LIMIT = 8;
63
+ /**
64
+ * 読み解いた画像を ID ごとに覚えておく。画像が消えたり、同じ ID で別の画像に変わったら捨てる
65
+ * (`sync`)。読み解けない画像は描かない(エラーにしない)。読み解きは非同期なので、
66
+ * 終わったら `onReady` を呼ぶ。
67
+ *
68
+ * コマを足した画像(アニメーション)は、コマを作る操作を順に当てて画素を作り、いまのコマの
69
+ * 絵を返す(`get`)。コマを進める時刻は `tick` で決める。時計はこちらが持つ。
70
+ */
71
+ export class ImageCache {
72
+ source;
73
+ decoder;
74
+ onReady;
75
+ entries = new Map();
76
+ timers = new Map();
77
+ constructor(source, decoder, onReady) {
78
+ this.source = source;
79
+ this.decoder = decoder;
80
+ this.onReady = onReady;
81
+ }
82
+ /** 持っている画像に合わせて、いらなくなったものを捨てる。 */
83
+ sync() {
84
+ if (this.entries.size === 0)
85
+ return;
86
+ const alive = new Map();
87
+ const flat = this.source.imageSerials();
88
+ for (let i = 0; i + 1 < flat.length; i += 2)
89
+ alive.set(flat[i], flat[i + 1]);
90
+ for (const [id, entry] of this.entries) {
91
+ if (alive.get(id) !== entry.serial)
92
+ this.drop(id, entry);
93
+ }
94
+ for (const id of this.timers.keys()) {
95
+ if (!alive.has(id))
96
+ this.timers.delete(id);
97
+ }
98
+ }
99
+ /** 画像 `id` の、コマを足してあれば、その状態。 */
100
+ animation(id) {
101
+ const info = this.source.animationInfo?.(id);
102
+ return info && info.length >= 5 && info[4] > 0 ? info : undefined;
103
+ }
104
+ /**
105
+ * 時刻 `now`(ミリ秒)で、画像 `id` のコマを進める。表示時間が過ぎていたら次のコマへ。
106
+ * 回し続けている(また呼ぶ必要がある)なら `true`。
107
+ */
108
+ tick(id, now) {
109
+ const info = this.animation(id);
110
+ if (!info)
111
+ return false;
112
+ const [frames, current, state, gap] = Array.from(info);
113
+ if (frames < 2)
114
+ return false;
115
+ const timer = this.timers.get(id);
116
+ if (!timer || timer.current !== current || state === 1) {
117
+ this.timers.set(id, { current, since: now });
118
+ return state !== 1;
119
+ }
120
+ if (now - timer.since >= Math.max(gap, 0)) {
121
+ if (this.source.animationStep?.(id)) {
122
+ const after = this.source.animationInfo?.(id);
123
+ this.timers.set(id, { current: after?.[1] ?? current, since: now });
124
+ }
125
+ else {
126
+ timer.since = now;
127
+ }
128
+ }
129
+ return true;
130
+ }
131
+ /** 画像 `id` の絵が変わるたびに増える数(アニメーションでなければ 0)。 */
132
+ version(id) {
133
+ return this.entries.get(id)?.anim?.version ?? 0;
134
+ }
135
+ /** 読み解き終えた画像。まだ、または読み解けなかったら `undefined`(初めて頼まれたら読み解きを始める)。 */
136
+ get(id) {
137
+ let entry = this.entries.get(id);
138
+ const anim = entry?.anim?.dead ? undefined : this.animation(id);
139
+ if (anim) {
140
+ if (!entry) {
141
+ const info = this.source.imageInfo(id);
142
+ if (info.length < 5)
143
+ return undefined;
144
+ entry = { serial: info[0] };
145
+ this.entries.set(id, entry);
146
+ }
147
+ return this.getAnimated(id, entry, anim);
148
+ }
149
+ if (entry) {
150
+ // コマを使えない画像は、根だけを通常の画像として描く
151
+ if (entry.anim?.dead && !entry.started)
152
+ this.startDecode(id, entry);
153
+ return entry.image;
154
+ }
155
+ const info = this.source.imageInfo(id);
156
+ if (info.length < 5)
157
+ return undefined;
158
+ const fresh = { serial: info[0] };
159
+ this.entries.set(id, fresh);
160
+ this.startDecode(id, fresh);
161
+ return undefined;
162
+ }
163
+ /** 画像そのもの(根)を読み解き始める。 */
164
+ startDecode(id, fresh) {
165
+ const info = this.source.imageInfo(id);
166
+ if (info.length < 5)
167
+ return;
168
+ fresh.started = true;
169
+ const data = this.source.imageData(id);
170
+ void this.decode(info, data).then((image) => {
171
+ if (image && this.entries.get(id) === fresh) {
172
+ fresh.image = image;
173
+ this.onReady();
174
+ }
175
+ else {
176
+ image?.close?.();
177
+ }
178
+ }, () => { });
179
+ }
180
+ drop(id, entry) {
181
+ this.entries.delete(id);
182
+ entry.image?.close?.();
183
+ if (entry.anim) {
184
+ const all = new Set([...entry.anim.bitmaps.values(), entry.anim.shown]);
185
+ for (const b of all)
186
+ b?.close?.();
187
+ entry.anim.bitmaps.clear();
188
+ entry.anim.shown = undefined;
189
+ }
190
+ }
191
+ async decode(info, data) {
192
+ const [, format, compressed, width, height] = Array.from(info);
193
+ let bytes = data;
194
+ if (compressed)
195
+ bytes = await this.decoder.inflate(bytes);
196
+ if (format === 2)
197
+ return this.decoder.fromPng(bytes);
198
+ const pixels = fromRaw(bytes, format === 0 ? 3 : 4, width, height);
199
+ if (!pixels)
200
+ return undefined;
201
+ return this.decoder.fromRgba(pixels.rgba, width, height);
202
+ }
203
+ /** 形式 `format`(0 RGB、1 RGBA、2 PNG)のデータを、画素の並びに読み解く。 */
204
+ async toPixels(format, compressed, data, width, height) {
205
+ const bytes = compressed ? await this.decoder.inflate(data) : data;
206
+ if (format === 2)
207
+ return this.decoder.toRgba?.(bytes);
208
+ return fromRaw(bytes, format === 0 ? 3 : 4, width, height);
209
+ }
210
+ getAnimated(id, entry, info) {
211
+ const a = (entry.anim ??= {
212
+ frames: [],
213
+ applied: 0,
214
+ busy: false,
215
+ dead: false,
216
+ bitmaps: new Map(),
217
+ pending: new Set(),
218
+ version: 0,
219
+ });
220
+ if (a.applied < info[4] && !a.busy && !a.dead)
221
+ void this.pump(id, entry, a);
222
+ const frame = info[1];
223
+ const bitmap = a.bitmaps.get(frame);
224
+ if (bitmap) {
225
+ if (a.shown !== bitmap) {
226
+ a.shown = bitmap;
227
+ a.version++;
228
+ }
229
+ return bitmap;
230
+ }
231
+ const pixels = a.frames[frame - 1];
232
+ if (pixels && !a.pending.has(frame)) {
233
+ a.pending.add(frame);
234
+ void this.decoder.fromRgba(pixels.rgba.slice(), pixels.width, pixels.height).then((image) => {
235
+ a.pending.delete(frame);
236
+ if (this.entries.get(id) !== entry) {
237
+ image.close?.();
238
+ return;
239
+ }
240
+ // 操作でこのコマが変わっていたら、古い絵なので使わない
241
+ if (a.frames[frame - 1] !== pixels) {
242
+ image.close?.();
243
+ }
244
+ else {
245
+ a.bitmaps.set(frame, image);
246
+ this.trim(a);
247
+ }
248
+ this.onReady();
249
+ }, () => a.pending.delete(frame));
250
+ }
251
+ return a.shown ?? entry.image;
252
+ }
253
+ /** 覚えている絵が多すぎたら、見せていない古いものから閉じる。 */
254
+ trim(a) {
255
+ for (const [frame, image] of a.bitmaps) {
256
+ if (a.bitmaps.size <= BITMAP_LIMIT)
257
+ break;
258
+ if (image === a.shown)
259
+ continue;
260
+ a.bitmaps.delete(frame);
261
+ image.close?.();
262
+ }
263
+ }
264
+ /** 届いている操作を、まだ当てていないぶん順に当てる。 */
265
+ async pump(id, entry, a) {
266
+ a.busy = true;
267
+ const alive = () => this.entries.get(id) === entry;
268
+ try {
269
+ if (a.frames.length === 0) {
270
+ const info = this.source.imageInfo(id);
271
+ if (info.length < 5)
272
+ return;
273
+ const [, format, compressed, width, height] = Array.from(info);
274
+ const root = await this.toPixels(format, compressed !== 0, this.source.imageData(id), width, height);
275
+ if (!alive())
276
+ return;
277
+ if (!root) {
278
+ a.dead = true;
279
+ return;
280
+ }
281
+ a.frames.push(root);
282
+ }
283
+ for (;;) {
284
+ const total = this.animation(id)?.[4] ?? 0;
285
+ if (a.applied >= total || !alive())
286
+ break;
287
+ const op = this.source.frameOp?.(id, a.applied);
288
+ if (!op || op.length === 0)
289
+ break;
290
+ const touched = await this.apply(id, a, op, a.applied);
291
+ if (!alive())
292
+ return;
293
+ a.applied++;
294
+ // 変わったコマの絵は作り直す
295
+ const old = a.bitmaps.get(touched);
296
+ a.bitmaps.delete(touched);
297
+ if (old && old !== a.shown)
298
+ old.close?.();
299
+ }
300
+ }
301
+ catch {
302
+ // 根が読めなければ、コマは使わない。操作が当てられないだけなら、飛ばして次へ
303
+ if (a.frames.length === 0)
304
+ a.dead = true;
305
+ }
306
+ finally {
307
+ a.busy = false;
308
+ }
309
+ if (alive())
310
+ this.onReady();
311
+ }
312
+ /** 操作 1 つを当てる。変わったコマの番号(1 始まり)を返す。 */
313
+ async apply(id, a, op, index) {
314
+ const root = a.frames[0];
315
+ if (op[0] === 1) {
316
+ const [, srcFrame, dstFrame, sx, sy, dx, dy, w, h, overwrite] = Array.from(op);
317
+ const src = a.frames[srcFrame - 1];
318
+ const dst = a.frames[dstFrame - 1];
319
+ if (src && dst) {
320
+ // 絵を作っている途中の写しと取り違えないよう、新しい画素に差し替える
321
+ const next = clone(dst);
322
+ compose(next, src, sx, sy, dx, dy, w, h, overwrite !== 0);
323
+ a.frames[dstFrame - 1] = next;
324
+ }
325
+ return dstFrame;
326
+ }
327
+ const [, target, baseKind, baseValue, x, y, width, height, overwrite, format, compressed] = Array.from(op);
328
+ const base = baseKind === 0
329
+ ? a.frames[target - 1]
330
+ : baseKind === 1
331
+ ? a.frames[baseValue - 1]
332
+ : undefined;
333
+ // 差し替えるので、元の画素は書き換えない
334
+ const canvas = base
335
+ ? clone(base)
336
+ : solid(root.width, root.height, baseKind === 2 ? baseValue : 0);
337
+ // 読み解けないデータでも、コマは作る(下地のまま)
338
+ const data = this.source.frameOpData?.(id, index) ?? new Uint8Array();
339
+ try {
340
+ const pixels = await this.toPixels(format, compressed !== 0, data, width, height);
341
+ if (pixels)
342
+ compose(canvas, pixels, 0, 0, x, y, pixels.width, pixels.height, overwrite !== 0);
343
+ }
344
+ catch {
345
+ // 下地のまま
346
+ }
347
+ if (target > a.frames.length)
348
+ a.frames.push(canvas);
349
+ else
350
+ a.frames[target - 1] = canvas;
351
+ return target;
352
+ }
353
+ /** 全部捨てる。 */
354
+ dispose() {
355
+ for (const [id, entry] of [...this.entries])
356
+ this.drop(id, entry);
357
+ this.timers.clear();
358
+ }
359
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `init()` に渡せる .wasm。
3
+ * .wasm のバイト列(`BufferSource`)か、コンパイル済みの `WebAssembly.Module`。
4
+ */
5
+ export type WasmSource = BufferSource | WebAssembly.Module;
6
+ /**
7
+ * WebAssembly を読み込む。ほかの関数や `Terminal` を使う前に呼ぶ。2 回目以降は何もしない。
8
+ * 引数を省くと、パッケージ内の .wasm を fetch で読む。
9
+ * Node.js などで fetch できないときは、.wasm のバイト列かモジュールを渡す。
10
+ */
11
+ export declare function init(source?: WasmSource): Promise<void>;
12
+ /** coneko-term の版。`init()` のあとに呼ぶ。 */
13
+ export declare function version(): string;
14
+ export { CELL_STRIDE, CLUSTER_FLAG, Terminal, type ClipboardItem, type ClipboardLocation, type ClipboardReadError, type ClipboardReadRequest, type ClipboardWriteRequest, type ClipboardWriteStatus, type DndErrorName, type DndEvent, type DndLocation, type DndOperation, type DndOperations, type DragImage, type DropDataForm, type DropRequest, type FileTransferErrorName, type FileTransferEvent, type FileTransferFileType, type FileTransferListing, type FileTransferStart, type CursorState, type ExtraCursorState, type MouseInput, type ProgramKind, type ProgramState, type ProgramStatus, type ProgramStatusChange, type NotificationSupport, type TerminalNotification, type WheelInput, type Disposable, type RowView, type SizedInfo, type TerminalOptions, type Theme, } from "./terminal.js";
15
+ export { browserEnv, CanvasRenderer, Palette, SCROLLBAR_WIDTH, proposeDimensions, type CellSize, type FitInput, type RenderEnv, type RenderOptions, type RenderSource, } from "./renderer.js";
16
+ export { PLACEMENT_STRIDE, Z_UNDER_BACKGROUND, ImageCache, browserImageDecoder, parsePlacements, type DrawableImage, type ImageDecoder, type ImagePlacement, type ImageSource, } from "./images.js";
17
+ export { SelectionModel, type BufferPosition, type SelectionHost, type SelectionKind, type SelectionRange, } from "./selection.js";
18
+ export { bracketTextForPaste, evaluateKeyboardEvent, KITTY_DISAMBIGUATE, KITTY_REPORT_ALL_KEYS, KITTY_REPORT_ALTERNATE_KEYS, KITTY_REPORT_EVENT_TYPES, KITTY_REPORT_TEXT, prepareTextForTerminal, type KeyEventLike, type KeyEventType, type KeyResult, type KeyResultType, } from "./keyboard.js";
19
+ export { CompositionHelper, KeyHandler, type CompositionAnchor, type CompositionViewLike, type KeyHandlerHost, type TextareaLike, } from "./input.js";
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import wasmInit, { version as wasmVersion } from "./wasm/coneko_term_wasm.js";
2
+ import { setMemory } from "./memory.js";
3
+ /**
4
+ * WebAssembly を読み込む。ほかの関数や `Terminal` を使う前に呼ぶ。2 回目以降は何もしない。
5
+ * 引数を省くと、パッケージ内の .wasm を fetch で読む。
6
+ * Node.js などで fetch できないときは、.wasm のバイト列かモジュールを渡す。
7
+ */
8
+ export async function init(source) {
9
+ const out = await wasmInit(source === undefined ? undefined : { module_or_path: source });
10
+ setMemory(out.memory);
11
+ }
12
+ /** coneko-term の版。`init()` のあとに呼ぶ。 */
13
+ export function version() {
14
+ return wasmVersion();
15
+ }
16
+ export { CELL_STRIDE, CLUSTER_FLAG, Terminal, } from "./terminal.js";
17
+ export { browserEnv, CanvasRenderer, Palette, SCROLLBAR_WIDTH, proposeDimensions, } from "./renderer.js";
18
+ export { PLACEMENT_STRIDE, Z_UNDER_BACKGROUND, ImageCache, browserImageDecoder, parsePlacements, } from "./images.js";
19
+ export { SelectionModel, } from "./selection.js";
20
+ export { bracketTextForPaste, evaluateKeyboardEvent, KITTY_DISAMBIGUATE, KITTY_REPORT_ALL_KEYS, KITTY_REPORT_ALTERNATE_KEYS, KITTY_REPORT_EVENT_TYPES, KITTY_REPORT_TEXT, prepareTextForTerminal, } from "./keyboard.js";
21
+ export { CompositionHelper, KeyHandler, } from "./input.js";