@next_term/web 0.1.0-next.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 (61) hide show
  1. package/dist/accessibility.d.ts +46 -0
  2. package/dist/accessibility.d.ts.map +1 -0
  3. package/dist/accessibility.js +196 -0
  4. package/dist/accessibility.js.map +1 -0
  5. package/dist/addon.d.ts.map +1 -0
  6. package/dist/addon.js +2 -0
  7. package/dist/addon.js.map +1 -0
  8. package/dist/addons/fit.d.ts.map +1 -0
  9. package/dist/addons/fit.js +40 -0
  10. package/dist/addons/fit.js.map +1 -0
  11. package/dist/addons/search.d.ts +56 -0
  12. package/dist/addons/search.d.ts.map +1 -0
  13. package/dist/addons/search.js +178 -0
  14. package/dist/addons/search.js.map +1 -0
  15. package/dist/addons/web-links.d.ts +30 -0
  16. package/dist/addons/web-links.d.ts.map +1 -0
  17. package/dist/addons/web-links.js +170 -0
  18. package/dist/addons/web-links.js.map +1 -0
  19. package/dist/fit.d.ts.map +1 -0
  20. package/dist/fit.js +14 -0
  21. package/dist/fit.js.map +1 -0
  22. package/dist/index.d.ts +24 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +14 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/input-handler.d.ts +185 -0
  27. package/dist/input-handler.d.ts.map +1 -0
  28. package/dist/input-handler.js +1197 -0
  29. package/dist/input-handler.js.map +1 -0
  30. package/dist/parser-worker.d.ts.map +1 -0
  31. package/dist/parser-worker.js +128 -0
  32. package/dist/parser-worker.js.map +1 -0
  33. package/dist/render-bridge.d.ts +56 -0
  34. package/dist/render-bridge.d.ts.map +1 -0
  35. package/dist/render-bridge.js +158 -0
  36. package/dist/render-bridge.js.map +1 -0
  37. package/dist/render-worker.d.ts +62 -0
  38. package/dist/render-worker.d.ts.map +1 -0
  39. package/dist/render-worker.js +720 -0
  40. package/dist/render-worker.js.map +1 -0
  41. package/dist/renderer.d.ts +86 -0
  42. package/dist/renderer.d.ts.map +1 -0
  43. package/dist/renderer.js +454 -0
  44. package/dist/renderer.js.map +1 -0
  45. package/dist/shared-context.d.ts +93 -0
  46. package/dist/shared-context.d.ts.map +1 -0
  47. package/dist/shared-context.js +561 -0
  48. package/dist/shared-context.js.map +1 -0
  49. package/dist/web-terminal.d.ts +152 -0
  50. package/dist/web-terminal.d.ts.map +1 -0
  51. package/dist/web-terminal.js +684 -0
  52. package/dist/web-terminal.js.map +1 -0
  53. package/dist/webgl-renderer.d.ts +146 -0
  54. package/dist/webgl-renderer.d.ts.map +1 -0
  55. package/dist/webgl-renderer.js +1047 -0
  56. package/dist/webgl-renderer.js.map +1 -0
  57. package/dist/worker-bridge.d.ts +51 -0
  58. package/dist/worker-bridge.d.ts.map +1 -0
  59. package/dist/worker-bridge.js +185 -0
  60. package/dist/worker-bridge.js.map +1 -0
  61. package/package.json +36 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * SharedWebGLContext — manages a single WebGL2 context shared across
3
+ * multiple terminal panes.
4
+ *
5
+ * Chrome limits WebGL contexts to 16 per page. This class allows any number
6
+ * of terminal panes to render through one context by using `gl.viewport` and
7
+ * `gl.scissor` to partition the canvas into regions.
8
+ *
9
+ * The shared canvas is positioned as an overlay by the consumer (typically
10
+ * TerminalPane). Each registered terminal provides its CellGrid, CursorState,
11
+ * and a viewport rectangle (in CSS pixels relative to the canvas).
12
+ */
13
+ import type { CellGrid, CursorState, SelectionRange, Theme } from "@next_term/core";
14
+ export interface TerminalEntry {
15
+ grid: CellGrid;
16
+ cursor: CursorState;
17
+ selection: SelectionRange | null;
18
+ viewport: {
19
+ x: number;
20
+ y: number;
21
+ width: number;
22
+ height: number;
23
+ };
24
+ }
25
+ export declare class SharedWebGLContext {
26
+ private gl;
27
+ private canvas;
28
+ private terminals;
29
+ private disposed;
30
+ private rafId;
31
+ private bgProgram;
32
+ private glyphProgram;
33
+ private quadVBO;
34
+ private quadEBO;
35
+ private bgInstanceVBO;
36
+ private glyphInstanceVBO;
37
+ private bgVAO;
38
+ private glyphVAO;
39
+ private bgInstances;
40
+ private glyphInstances;
41
+ private atlas;
42
+ private theme;
43
+ private palette;
44
+ private paletteFloat;
45
+ private themeFgFloat;
46
+ private themeBgFloat;
47
+ private themeCursorFloat;
48
+ private fontSize;
49
+ private fontFamily;
50
+ private dpr;
51
+ private cellWidth;
52
+ private cellHeight;
53
+ constructor(options?: {
54
+ fontSize?: number;
55
+ fontFamily?: string;
56
+ theme?: Partial<Theme>;
57
+ devicePixelRatio?: number;
58
+ });
59
+ /**
60
+ * Initialize the WebGL context. Must be called after the canvas is in the
61
+ * DOM (or at least after construction if you plan to append it yourself).
62
+ */
63
+ init(): void;
64
+ addTerminal(id: string, grid: CellGrid, cursor: CursorState): void;
65
+ setViewport(id: string, x: number, y: number, width: number, height: number): void;
66
+ updateTerminal(id: string, grid: CellGrid, cursor: CursorState): void;
67
+ removeTerminal(id: string): void;
68
+ getTerminalIds(): string[];
69
+ /**
70
+ * Render all terminals in one frame.
71
+ */
72
+ render(): void;
73
+ /**
74
+ * Update the shared canvas size to match a container element.
75
+ */
76
+ syncCanvasSize(width: number, height: number): void;
77
+ getCanvas(): HTMLCanvasElement;
78
+ getCellSize(): {
79
+ width: number;
80
+ height: number;
81
+ };
82
+ startRenderLoop(): void;
83
+ stopRenderLoop(): void;
84
+ dispose(): void;
85
+ private renderTerminal;
86
+ private initGLResources;
87
+ private setupBgVAO;
88
+ private setupGlyphVAO;
89
+ private buildPaletteFloat;
90
+ private resolveColorFloat;
91
+ private measureCellSize;
92
+ }
93
+ //# sourceMappingURL=shared-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-context.d.ts","sourceRoot":"","sources":["../src/shared-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,561 @@
1
+ /**
2
+ * SharedWebGLContext — manages a single WebGL2 context shared across
3
+ * multiple terminal panes.
4
+ *
5
+ * Chrome limits WebGL contexts to 16 per page. This class allows any number
6
+ * of terminal panes to render through one context by using `gl.viewport` and
7
+ * `gl.scissor` to partition the canvas into regions.
8
+ *
9
+ * The shared canvas is positioned as an overlay by the consumer (typically
10
+ * TerminalPane). Each registered terminal provides its CellGrid, CursorState,
11
+ * and a viewport rectangle (in CSS pixels relative to the canvas).
12
+ */
13
+ import { DEFAULT_THEME } from "@next_term/core";
14
+ import { build256Palette } from "./renderer.js";
15
+ import { BG_INSTANCE_FLOATS, GLYPH_INSTANCE_FLOATS, GlyphAtlas, hexToFloat4, packBgInstance, packGlyphInstance, } from "./webgl-renderer.js";
16
+ // ---------------------------------------------------------------------------
17
+ // Attribute bit positions
18
+ // ---------------------------------------------------------------------------
19
+ const ATTR_BOLD = 0x01;
20
+ const ATTR_ITALIC = 0x02;
21
+ const ATTR_INVERSE = 0x40;
22
+ // ---------------------------------------------------------------------------
23
+ // Shader sources (same as webgl-renderer.ts)
24
+ // ---------------------------------------------------------------------------
25
+ const BG_VERTEX_SHADER = `#version 300 es
26
+ in vec2 a_position;
27
+ in vec2 a_cellPos;
28
+ in vec4 a_color;
29
+
30
+ uniform vec2 u_resolution;
31
+ uniform vec2 u_cellSize;
32
+
33
+ out vec4 v_color;
34
+
35
+ void main() {
36
+ vec2 cellPixelPos = a_cellPos * u_cellSize;
37
+ vec2 pos = cellPixelPos + a_position * u_cellSize;
38
+ vec2 clipPos = (pos / u_resolution) * 2.0 - 1.0;
39
+ clipPos.y = -clipPos.y;
40
+ gl_Position = vec4(clipPos, 0.0, 1.0);
41
+ v_color = a_color;
42
+ }
43
+ `;
44
+ const BG_FRAGMENT_SHADER = `#version 300 es
45
+ precision mediump float;
46
+ in vec4 v_color;
47
+ out vec4 fragColor;
48
+ void main() {
49
+ fragColor = v_color;
50
+ }
51
+ `;
52
+ const GLYPH_VERTEX_SHADER = `#version 300 es
53
+ in vec2 a_position;
54
+ in vec2 a_cellPos;
55
+ in vec4 a_color;
56
+ in vec4 a_texCoord;
57
+ in vec2 a_glyphSize;
58
+
59
+ uniform vec2 u_resolution;
60
+ uniform vec2 u_cellSize;
61
+
62
+ out vec4 v_color;
63
+ out vec2 v_texCoord;
64
+
65
+ void main() {
66
+ vec2 cellPixelPos = a_cellPos * u_cellSize;
67
+ vec2 size = (a_glyphSize.x > 0.0) ? a_glyphSize : u_cellSize;
68
+ vec2 pos = cellPixelPos + a_position * size;
69
+ vec2 clipPos = (pos / u_resolution) * 2.0 - 1.0;
70
+ clipPos.y = -clipPos.y;
71
+ gl_Position = vec4(clipPos, 0.0, 1.0);
72
+ v_color = a_color;
73
+ v_texCoord = a_texCoord.xy + a_position * a_texCoord.zw;
74
+ }
75
+ `;
76
+ const GLYPH_FRAGMENT_SHADER = `#version 300 es
77
+ precision mediump float;
78
+ in vec4 v_color;
79
+ in vec2 v_texCoord;
80
+ uniform sampler2D u_atlas;
81
+ out vec4 fragColor;
82
+ void main() {
83
+ float alpha = texture(u_atlas, v_texCoord).a;
84
+ fragColor = vec4(v_color.rgb, v_color.a * alpha);
85
+ }
86
+ `;
87
+ // ---------------------------------------------------------------------------
88
+ // Shader compilation helpers
89
+ // ---------------------------------------------------------------------------
90
+ function compileShader(gl, type, source) {
91
+ const shader = gl.createShader(type);
92
+ if (!shader)
93
+ throw new Error("Failed to create shader");
94
+ gl.shaderSource(shader, source);
95
+ gl.compileShader(shader);
96
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
97
+ const info = gl.getShaderInfoLog(shader);
98
+ gl.deleteShader(shader);
99
+ throw new Error(`Shader compile error: ${info}`);
100
+ }
101
+ return shader;
102
+ }
103
+ function createProgram(gl, vertSrc, fragSrc) {
104
+ const vert = compileShader(gl, gl.VERTEX_SHADER, vertSrc);
105
+ const frag = compileShader(gl, gl.FRAGMENT_SHADER, fragSrc);
106
+ const program = gl.createProgram();
107
+ if (!program)
108
+ throw new Error("Failed to create program");
109
+ gl.attachShader(program, vert);
110
+ gl.attachShader(program, frag);
111
+ gl.linkProgram(program);
112
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
113
+ const info = gl.getProgramInfoLog(program);
114
+ gl.deleteProgram(program);
115
+ throw new Error(`Program link error: ${info}`);
116
+ }
117
+ gl.detachShader(program, vert);
118
+ gl.detachShader(program, frag);
119
+ gl.deleteShader(vert);
120
+ gl.deleteShader(frag);
121
+ return program;
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // SharedWebGLContext
125
+ // ---------------------------------------------------------------------------
126
+ export class SharedWebGLContext {
127
+ gl = null;
128
+ canvas;
129
+ terminals = new Map();
130
+ disposed = false;
131
+ rafId = null;
132
+ // GL resources
133
+ bgProgram = null;
134
+ glyphProgram = null;
135
+ quadVBO = null;
136
+ quadEBO = null;
137
+ bgInstanceVBO = null;
138
+ glyphInstanceVBO = null;
139
+ bgVAO = null;
140
+ glyphVAO = null;
141
+ // Instance data (CPU side)
142
+ bgInstances;
143
+ glyphInstances;
144
+ // Glyph atlas
145
+ atlas;
146
+ // Theme / palette
147
+ theme;
148
+ palette;
149
+ paletteFloat = [];
150
+ themeFgFloat = [0, 0, 0, 1];
151
+ themeBgFloat = [0, 0, 0, 1];
152
+ themeCursorFloat = [0, 0, 0, 1];
153
+ fontSize;
154
+ fontFamily;
155
+ dpr;
156
+ cellWidth = 0;
157
+ cellHeight = 0;
158
+ constructor(options) {
159
+ this.fontSize = options?.fontSize ?? 14;
160
+ this.fontFamily = options?.fontFamily ?? "'Menlo', 'DejaVu Sans Mono', 'Consolas', monospace";
161
+ this.theme = { ...DEFAULT_THEME, ...options?.theme };
162
+ this.dpr =
163
+ options?.devicePixelRatio ?? (typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1);
164
+ this.palette = build256Palette(this.theme);
165
+ this.buildPaletteFloat();
166
+ this.measureCellSize();
167
+ this.atlas = new GlyphAtlas(Math.round(this.fontSize * this.dpr), this.fontFamily);
168
+ // Pre-allocate instance buffers
169
+ const maxCells = 80 * 24;
170
+ this.bgInstances = new Float32Array(maxCells * BG_INSTANCE_FLOATS);
171
+ this.glyphInstances = new Float32Array(maxCells * GLYPH_INSTANCE_FLOATS);
172
+ // Create the shared canvas
173
+ this.canvas = document.createElement("canvas");
174
+ this.canvas.style.display = "block";
175
+ this.canvas.style.position = "absolute";
176
+ this.canvas.style.top = "0";
177
+ this.canvas.style.left = "0";
178
+ this.canvas.style.pointerEvents = "none";
179
+ }
180
+ /**
181
+ * Initialize the WebGL context. Must be called after the canvas is in the
182
+ * DOM (or at least after construction if you plan to append it yourself).
183
+ */
184
+ init() {
185
+ this.gl = this.canvas.getContext("webgl2", {
186
+ alpha: true,
187
+ antialias: false,
188
+ premultipliedAlpha: false,
189
+ preserveDrawingBuffer: false,
190
+ });
191
+ if (!this.gl) {
192
+ throw new Error("WebGL2 is not available");
193
+ }
194
+ this.initGLResources();
195
+ }
196
+ // -----------------------------------------------------------------------
197
+ // Public API
198
+ // -----------------------------------------------------------------------
199
+ addTerminal(id, grid, cursor) {
200
+ this.terminals.set(id, {
201
+ grid,
202
+ cursor,
203
+ selection: null,
204
+ viewport: { x: 0, y: 0, width: 0, height: 0 },
205
+ });
206
+ }
207
+ setViewport(id, x, y, width, height) {
208
+ const entry = this.terminals.get(id);
209
+ if (entry) {
210
+ entry.viewport = { x, y, width, height };
211
+ }
212
+ }
213
+ updateTerminal(id, grid, cursor) {
214
+ const entry = this.terminals.get(id);
215
+ if (entry) {
216
+ entry.grid = grid;
217
+ entry.cursor = cursor;
218
+ }
219
+ }
220
+ removeTerminal(id) {
221
+ this.terminals.delete(id);
222
+ }
223
+ getTerminalIds() {
224
+ return Array.from(this.terminals.keys());
225
+ }
226
+ /**
227
+ * Render all terminals in one frame.
228
+ */
229
+ render() {
230
+ if (this.disposed || !this.gl)
231
+ return;
232
+ const gl = this.gl;
233
+ const canvasWidth = this.canvas.width;
234
+ const canvasHeight = this.canvas.height;
235
+ // Full-canvas clear
236
+ gl.viewport(0, 0, canvasWidth, canvasHeight);
237
+ gl.clearColor(0, 0, 0, 0);
238
+ gl.clear(gl.COLOR_BUFFER_BIT);
239
+ // Render each terminal in its viewport
240
+ for (const [_id, entry] of this.terminals) {
241
+ this.renderTerminal(entry);
242
+ }
243
+ gl.disable(gl.SCISSOR_TEST);
244
+ }
245
+ /**
246
+ * Update the shared canvas size to match a container element.
247
+ */
248
+ syncCanvasSize(width, height) {
249
+ this.canvas.width = Math.round(width * this.dpr);
250
+ this.canvas.height = Math.round(height * this.dpr);
251
+ this.canvas.style.width = `${width}px`;
252
+ this.canvas.style.height = `${height}px`;
253
+ }
254
+ getCanvas() {
255
+ return this.canvas;
256
+ }
257
+ getCellSize() {
258
+ return { width: this.cellWidth, height: this.cellHeight };
259
+ }
260
+ startRenderLoop() {
261
+ if (this.disposed)
262
+ return;
263
+ const loop = () => {
264
+ if (this.disposed)
265
+ return;
266
+ this.render();
267
+ this.rafId = requestAnimationFrame(loop);
268
+ };
269
+ this.rafId = requestAnimationFrame(loop);
270
+ }
271
+ stopRenderLoop() {
272
+ if (this.rafId !== null) {
273
+ cancelAnimationFrame(this.rafId);
274
+ this.rafId = null;
275
+ }
276
+ }
277
+ dispose() {
278
+ if (this.disposed)
279
+ return;
280
+ this.disposed = true;
281
+ this.stopRenderLoop();
282
+ const gl = this.gl;
283
+ if (gl) {
284
+ this.atlas.dispose(gl);
285
+ if (this.bgProgram)
286
+ gl.deleteProgram(this.bgProgram);
287
+ if (this.glyphProgram)
288
+ gl.deleteProgram(this.glyphProgram);
289
+ if (this.quadVBO)
290
+ gl.deleteBuffer(this.quadVBO);
291
+ if (this.quadEBO)
292
+ gl.deleteBuffer(this.quadEBO);
293
+ if (this.bgInstanceVBO)
294
+ gl.deleteBuffer(this.bgInstanceVBO);
295
+ if (this.glyphInstanceVBO)
296
+ gl.deleteBuffer(this.glyphInstanceVBO);
297
+ if (this.bgVAO)
298
+ gl.deleteVertexArray(this.bgVAO);
299
+ if (this.glyphVAO)
300
+ gl.deleteVertexArray(this.glyphVAO);
301
+ }
302
+ this.terminals.clear();
303
+ if (this.canvas.parentElement) {
304
+ this.canvas.parentElement.removeChild(this.canvas);
305
+ }
306
+ this.gl = null;
307
+ }
308
+ // -----------------------------------------------------------------------
309
+ // Per-terminal rendering
310
+ // -----------------------------------------------------------------------
311
+ renderTerminal(entry) {
312
+ if (!this.gl)
313
+ return;
314
+ const gl = this.gl;
315
+ const { grid, cursor, viewport } = entry;
316
+ const cols = grid.cols;
317
+ const rows = grid.rows;
318
+ // Convert CSS viewport to device pixels
319
+ const vpX = Math.round(viewport.x * this.dpr);
320
+ const vpY = Math.round(viewport.y * this.dpr);
321
+ const vpW = Math.round(viewport.width * this.dpr);
322
+ const vpH = Math.round(viewport.height * this.dpr);
323
+ // WebGL viewport Y is from bottom; canvas Y is from top
324
+ const canvasHeight = this.canvas.height;
325
+ const glY = canvasHeight - vpY - vpH;
326
+ gl.viewport(vpX, glY, vpW, vpH);
327
+ gl.enable(gl.SCISSOR_TEST);
328
+ gl.scissor(vpX, glY, vpW, vpH);
329
+ // Clear this region with background color
330
+ gl.clearColor(this.themeBgFloat[0], this.themeBgFloat[1], this.themeBgFloat[2], 1.0);
331
+ gl.clear(gl.COLOR_BUFFER_BIT);
332
+ // Ensure instance buffers are large enough
333
+ const totalCells = cols * rows;
334
+ if (this.bgInstances.length < totalCells * BG_INSTANCE_FLOATS) {
335
+ this.bgInstances = new Float32Array(totalCells * BG_INSTANCE_FLOATS);
336
+ }
337
+ if (this.glyphInstances.length < totalCells * GLYPH_INSTANCE_FLOATS) {
338
+ this.glyphInstances = new Float32Array(totalCells * GLYPH_INSTANCE_FLOATS);
339
+ }
340
+ // Build instance data
341
+ let bgCount = 0;
342
+ let glyphCount = 0;
343
+ for (let row = 0; row < rows; row++) {
344
+ for (let col = 0; col < cols; col++) {
345
+ const codepoint = grid.getCodepoint(row, col);
346
+ const fgIdx = grid.getFgIndex(row, col);
347
+ const bgIdx = grid.getBgIndex(row, col);
348
+ const attrs = grid.getAttrs(row, col);
349
+ const fgIsRGB = grid.isFgRGB(row, col);
350
+ const bgIsRGB = grid.isBgRGB(row, col);
351
+ let fg = this.resolveColorFloat(fgIdx, fgIsRGB, grid, col, true);
352
+ let bg = this.resolveColorFloat(bgIdx, bgIsRGB, grid, col, false);
353
+ if (attrs & ATTR_INVERSE) {
354
+ const tmp = fg;
355
+ fg = bg;
356
+ bg = tmp;
357
+ }
358
+ packBgInstance(this.bgInstances, bgCount * BG_INSTANCE_FLOATS, col, row, bg[0], bg[1], bg[2], bg[3]);
359
+ bgCount++;
360
+ if (codepoint > 0x20) {
361
+ const bold = !!(attrs & ATTR_BOLD);
362
+ const italic = !!(attrs & ATTR_ITALIC);
363
+ const glyph = this.atlas.getGlyph(codepoint, bold, italic);
364
+ if (glyph) {
365
+ packGlyphInstance(this.glyphInstances, glyphCount * GLYPH_INSTANCE_FLOATS, col, row, fg[0], fg[1], fg[2], fg[3], glyph.u, glyph.v, glyph.w, glyph.h, glyph.pw, glyph.ph);
366
+ glyphCount++;
367
+ }
368
+ }
369
+ }
370
+ grid.clearDirty(row);
371
+ }
372
+ // Upload atlas if dirty
373
+ this.atlas.upload(gl);
374
+ const cellW = this.cellWidth * this.dpr;
375
+ const cellH = this.cellHeight * this.dpr;
376
+ // --- Background pass ---
377
+ if (bgCount > 0 && this.bgProgram && this.bgVAO && this.bgInstanceVBO) {
378
+ gl.useProgram(this.bgProgram);
379
+ gl.uniform2f(gl.getUniformLocation(this.bgProgram, "u_resolution"), vpW, vpH);
380
+ gl.uniform2f(gl.getUniformLocation(this.bgProgram, "u_cellSize"), cellW, cellH);
381
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.bgInstanceVBO);
382
+ gl.bufferData(gl.ARRAY_BUFFER, this.bgInstances.subarray(0, bgCount * BG_INSTANCE_FLOATS), gl.DYNAMIC_DRAW);
383
+ gl.bindVertexArray(this.bgVAO);
384
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, bgCount);
385
+ }
386
+ // --- Glyph pass ---
387
+ if (glyphCount > 0 && this.glyphProgram && this.glyphVAO && this.glyphInstanceVBO) {
388
+ gl.enable(gl.BLEND);
389
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
390
+ gl.useProgram(this.glyphProgram);
391
+ gl.uniform2f(gl.getUniformLocation(this.glyphProgram, "u_resolution"), vpW, vpH);
392
+ gl.uniform2f(gl.getUniformLocation(this.glyphProgram, "u_cellSize"), cellW, cellH);
393
+ gl.activeTexture(gl.TEXTURE0);
394
+ gl.bindTexture(gl.TEXTURE_2D, this.atlas.getTexture());
395
+ gl.uniform1i(gl.getUniformLocation(this.glyphProgram, "u_atlas"), 0);
396
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphInstanceVBO);
397
+ gl.bufferData(gl.ARRAY_BUFFER, this.glyphInstances.subarray(0, glyphCount * GLYPH_INSTANCE_FLOATS), gl.DYNAMIC_DRAW);
398
+ gl.bindVertexArray(this.glyphVAO);
399
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, glyphCount);
400
+ gl.disable(gl.BLEND);
401
+ }
402
+ // --- Cursor ---
403
+ if (cursor.visible && this.bgProgram && this.bgVAO && this.bgInstanceVBO) {
404
+ const cc = this.themeCursorFloat;
405
+ gl.enable(gl.BLEND);
406
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
407
+ gl.useProgram(this.bgProgram);
408
+ gl.uniform2f(gl.getUniformLocation(this.bgProgram, "u_resolution"), vpW, vpH);
409
+ gl.uniform2f(gl.getUniformLocation(this.bgProgram, "u_cellSize"), cellW, cellH);
410
+ const cursorData = new Float32Array([cursor.col, cursor.row, cc[0], cc[1], cc[2], 0.5]);
411
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.bgInstanceVBO);
412
+ gl.bufferData(gl.ARRAY_BUFFER, cursorData, gl.DYNAMIC_DRAW);
413
+ gl.bindVertexArray(this.bgVAO);
414
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, 1);
415
+ gl.disable(gl.BLEND);
416
+ }
417
+ gl.bindVertexArray(null);
418
+ }
419
+ // -----------------------------------------------------------------------
420
+ // GL resource initialization
421
+ // -----------------------------------------------------------------------
422
+ initGLResources() {
423
+ const gl = this.gl;
424
+ if (!gl)
425
+ return;
426
+ this.bgProgram = createProgram(gl, BG_VERTEX_SHADER, BG_FRAGMENT_SHADER);
427
+ this.glyphProgram = createProgram(gl, GLYPH_VERTEX_SHADER, GLYPH_FRAGMENT_SHADER);
428
+ const quadVerts = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
429
+ const quadIndices = new Uint16Array([0, 1, 2, 2, 1, 3]);
430
+ this.quadVBO = gl.createBuffer();
431
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVBO);
432
+ gl.bufferData(gl.ARRAY_BUFFER, quadVerts, gl.STATIC_DRAW);
433
+ this.quadEBO = gl.createBuffer();
434
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.quadEBO);
435
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, quadIndices, gl.STATIC_DRAW);
436
+ this.bgInstanceVBO = gl.createBuffer();
437
+ this.glyphInstanceVBO = gl.createBuffer();
438
+ this.bgVAO = gl.createVertexArray();
439
+ gl.bindVertexArray(this.bgVAO);
440
+ this.setupBgVAO(gl);
441
+ gl.bindVertexArray(null);
442
+ this.glyphVAO = gl.createVertexArray();
443
+ gl.bindVertexArray(this.glyphVAO);
444
+ this.setupGlyphVAO(gl);
445
+ gl.bindVertexArray(null);
446
+ }
447
+ setupBgVAO(gl) {
448
+ const FLOAT = 4;
449
+ if (!this.bgProgram)
450
+ return;
451
+ const program = this.bgProgram;
452
+ const aPos = gl.getAttribLocation(program, "a_position");
453
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVBO);
454
+ gl.enableVertexAttribArray(aPos);
455
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
456
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.quadEBO);
457
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.bgInstanceVBO);
458
+ const stride = BG_INSTANCE_FLOATS * FLOAT;
459
+ const aCellPos = gl.getAttribLocation(program, "a_cellPos");
460
+ gl.enableVertexAttribArray(aCellPos);
461
+ gl.vertexAttribPointer(aCellPos, 2, gl.FLOAT, false, stride, 0);
462
+ gl.vertexAttribDivisor(aCellPos, 1);
463
+ const aColor = gl.getAttribLocation(program, "a_color");
464
+ gl.enableVertexAttribArray(aColor);
465
+ gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, stride, 2 * FLOAT);
466
+ gl.vertexAttribDivisor(aColor, 1);
467
+ }
468
+ setupGlyphVAO(gl) {
469
+ const FLOAT = 4;
470
+ if (!this.glyphProgram)
471
+ return;
472
+ const program = this.glyphProgram;
473
+ const aPos = gl.getAttribLocation(program, "a_position");
474
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVBO);
475
+ gl.enableVertexAttribArray(aPos);
476
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
477
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.quadEBO);
478
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphInstanceVBO);
479
+ const stride = GLYPH_INSTANCE_FLOATS * FLOAT;
480
+ const aCellPos = gl.getAttribLocation(program, "a_cellPos");
481
+ gl.enableVertexAttribArray(aCellPos);
482
+ gl.vertexAttribPointer(aCellPos, 2, gl.FLOAT, false, stride, 0);
483
+ gl.vertexAttribDivisor(aCellPos, 1);
484
+ const aColor = gl.getAttribLocation(program, "a_color");
485
+ gl.enableVertexAttribArray(aColor);
486
+ gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, stride, 2 * FLOAT);
487
+ gl.vertexAttribDivisor(aColor, 1);
488
+ const aTexCoord = gl.getAttribLocation(program, "a_texCoord");
489
+ gl.enableVertexAttribArray(aTexCoord);
490
+ gl.vertexAttribPointer(aTexCoord, 4, gl.FLOAT, false, stride, 6 * FLOAT);
491
+ gl.vertexAttribDivisor(aTexCoord, 1);
492
+ const aGlyphSize = gl.getAttribLocation(program, "a_glyphSize");
493
+ gl.enableVertexAttribArray(aGlyphSize);
494
+ gl.vertexAttribPointer(aGlyphSize, 2, gl.FLOAT, false, stride, 10 * FLOAT);
495
+ gl.vertexAttribDivisor(aGlyphSize, 1);
496
+ }
497
+ // -----------------------------------------------------------------------
498
+ // Color resolution
499
+ // -----------------------------------------------------------------------
500
+ buildPaletteFloat() {
501
+ this.paletteFloat = this.palette.map((c) => hexToFloat4(c));
502
+ this.themeFgFloat = hexToFloat4(this.theme.foreground);
503
+ this.themeBgFloat = hexToFloat4(this.theme.background);
504
+ this.themeCursorFloat = hexToFloat4(this.theme.cursor);
505
+ }
506
+ resolveColorFloat(colorIdx, isRGB, grid, col, isForeground) {
507
+ if (isRGB) {
508
+ const offset = isForeground ? col : 256 + col;
509
+ const rgb = grid.rgbColors[offset];
510
+ const r = ((rgb >> 16) & 0xff) / 255;
511
+ const g = ((rgb >> 8) & 0xff) / 255;
512
+ const b = (rgb & 0xff) / 255;
513
+ return [r, g, b, 1.0];
514
+ }
515
+ if (isForeground && colorIdx === 7)
516
+ return this.themeFgFloat;
517
+ if (!isForeground && colorIdx === 0)
518
+ return this.themeBgFloat;
519
+ if (colorIdx >= 0 && colorIdx < 256) {
520
+ return this.paletteFloat[colorIdx];
521
+ }
522
+ return isForeground ? this.themeFgFloat : this.themeBgFloat;
523
+ }
524
+ // -----------------------------------------------------------------------
525
+ // Cell measurement
526
+ // -----------------------------------------------------------------------
527
+ measureCellSize() {
528
+ const offscreen = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(100, 100) : null;
529
+ let measureCtx = null;
530
+ if (offscreen) {
531
+ measureCtx = offscreen.getContext("2d");
532
+ }
533
+ else if (typeof document !== "undefined") {
534
+ const tmpCanvas = document.createElement("canvas");
535
+ tmpCanvas.width = 100;
536
+ tmpCanvas.height = 100;
537
+ measureCtx = tmpCanvas.getContext("2d");
538
+ }
539
+ if (!measureCtx) {
540
+ this.cellWidth = Math.ceil(this.fontSize * 0.6);
541
+ this.cellHeight = Math.ceil(this.fontSize * 1.2);
542
+ return;
543
+ }
544
+ const font = `${this.fontSize}px ${this.fontFamily}`;
545
+ measureCtx.font = font;
546
+ const metrics = measureCtx.measureText("M");
547
+ this.cellWidth = Math.ceil(metrics.width);
548
+ if (typeof metrics.fontBoundingBoxAscent === "number" &&
549
+ typeof metrics.fontBoundingBoxDescent === "number") {
550
+ this.cellHeight = Math.ceil(metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent);
551
+ }
552
+ else {
553
+ this.cellHeight = Math.ceil(this.fontSize * 1.2);
554
+ }
555
+ if (this.cellWidth <= 0)
556
+ this.cellWidth = Math.ceil(this.fontSize * 0.6);
557
+ if (this.cellHeight <= 0)
558
+ this.cellHeight = Math.ceil(this.fontSize * 1.2);
559
+ }
560
+ }
561
+ //# sourceMappingURL=shared-context.js.map