@xterm/addon-image 0.10.0-beta.160 → 0.10.0-beta.161

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xterm/addon-image",
3
- "version": "0.10.0-beta.160",
3
+ "version": "0.10.0-beta.161",
4
4
  "author": {
5
5
  "name": "The xterm.js authors",
6
6
  "url": "https://xtermjs.org/"
@@ -27,8 +27,8 @@
27
27
  "sixel": "^0.16.0",
28
28
  "xterm-wasm-parts": "^0.3.0"
29
29
  },
30
- "commit": "4388a4ac134c9d6c9bd8c115ba0375ddf8570b06",
30
+ "commit": "3a9bfa94bc41fb3f53b8926392d9cab854cab867",
31
31
  "peerDependencies": {
32
- "@xterm/xterm": "^6.1.0-beta.160"
32
+ "@xterm/xterm": "^6.1.0-beta.161"
33
33
  }
34
34
  }
package/src/ImageAddon.ts CHANGED
@@ -8,6 +8,8 @@ import type { ImageAddon as IImageApi } from '@xterm/addon-image';
8
8
  import { IIPHandler } from './IIPHandler';
9
9
  import { ImageRenderer } from './ImageRenderer';
10
10
  import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
11
+ import { KittyGraphicsHandler } from './kitty/KittyGraphicsHandler';
12
+ import { KittyImageStorage } from './kitty/KittyImageStorage';
11
13
  import { SixelHandler } from './SixelHandler';
12
14
  import { SixelImageStorage } from './SixelImageStorage';
13
15
  import { IIPImageStorage } from './IIPImageStorage';
@@ -24,7 +26,9 @@ const DEFAULT_OPTIONS: IImageAddonOptions = {
24
26
  storageLimit: 128,
25
27
  showPlaceholder: true,
26
28
  iipSupport: true,
27
- iipSizeLimit: 20000000
29
+ iipSizeLimit: 20000000,
30
+ kittySupport: true,
31
+ kittySizeLimit: 20000000
28
32
  };
29
33
 
30
34
  // max palette size supported by the sixel lib (compile time setting)
@@ -148,6 +152,18 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
148
152
  terminal._core._inputHandler._parser.registerOscHandler(1337, iipHandler)
149
153
  );
150
154
  }
155
+
156
+ // Kitty graphics handler
157
+ if (this._opts.kittySupport) {
158
+ const kittyStorage = new KittyImageStorage(this._storage!);
159
+ const kittyHandler = new KittyGraphicsHandler(this._opts, this._renderer!, kittyStorage, terminal);
160
+ this._handlers.set('kitty', kittyHandler);
161
+ this._disposeLater(
162
+ kittyStorage,
163
+ kittyHandler,
164
+ terminal._core._inputHandler._parser.registerApcHandler(0x47, kittyHandler)
165
+ );
166
+ }
151
167
  }
152
168
 
153
169
  // Note: storageLimit is skipped here to not intoduce a surprising side effect.
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { toRGBA8888 } from 'sixel/lib/Colors';
7
7
  import { IDisposable } from '@xterm/xterm';
8
- import { ICellSize, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
8
+ import { ICellSize, ImageLayer, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
9
9
  import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
10
10
 
11
11
  const PLACEHOLDER_LENGTH = 4096;
@@ -18,8 +18,9 @@ const PLACEHOLDER_HEIGHT = 24;
18
18
  * - draw image tiles onRender
19
19
  */
20
20
  export class ImageRenderer extends Disposable implements IDisposable {
21
- public canvas: HTMLCanvasElement | undefined;
22
- private _ctx: CanvasRenderingContext2D | null | undefined;
21
+ /** @deprecated Kept for backward compat — points to top layer canvas. */
22
+ public get canvas(): HTMLCanvasElement | undefined { return this._layers.get('top')?.canvas; }
23
+ private _layers = new Map<ImageLayer, CanvasRenderingContext2D>();
23
24
  private _placeholder: HTMLCanvasElement | undefined;
24
25
  private _placeholderBitmap: ImageBitmap | undefined;
25
26
  private _optionsRefresh = this._register(new MutableDisposable());
@@ -86,6 +87,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
86
87
  });
87
88
  this._register(toDisposable(() => {
88
89
  this.removeLayerFromDom();
90
+ this.removeLayerFromDom('bottom');
89
91
  if (this._terminal._core && this._oldOpen) {
90
92
  this._terminal._core.open = this._oldOpen;
91
93
  this._oldOpen = undefined;
@@ -95,8 +97,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
95
97
  this._oldSetRenderer = undefined;
96
98
  }
97
99
  this._renderService = undefined;
98
- this.canvas = undefined;
99
- this._ctx = undefined;
100
+ this._layers.clear();
100
101
  this._placeholderBitmap?.close();
101
102
  this._placeholderBitmap = undefined;
102
103
  this._placeholder = undefined;
@@ -140,27 +141,38 @@ export class ImageRenderer extends Disposable implements IDisposable {
140
141
  /**
141
142
  * Clear a region of the image layer canvas.
142
143
  */
143
- public clearLines(start: number, end: number): void {
144
- this._ctx?.clearRect(
145
- 0,
146
- start * (this.dimensions?.css.cell.height || 0),
147
- this.dimensions?.css.canvas.width || 0,
148
- (++end - start) * (this.dimensions?.css.cell.height || 0)
149
- );
144
+ public clearLines(start: number, end: number, layer?: ImageLayer): void {
145
+ const y = start * (this.dimensions?.css.cell.height || 0);
146
+ const w = this.dimensions?.css.canvas.width || 0;
147
+ const h = (++end - start) * (this.dimensions?.css.cell.height || 0);
148
+ if (!layer || layer === 'top') {
149
+ this._layers.get('top')?.clearRect(0, y, w, h);
150
+ }
151
+ if (!layer || layer === 'bottom') {
152
+ this._layers.get('bottom')?.clearRect(0, y, w, h);
153
+ }
150
154
  }
151
155
 
152
156
  /**
153
157
  * Clear whole image canvas.
154
158
  */
155
- public clearAll(): void {
156
- this._ctx?.clearRect(0, 0, this.canvas?.width || 0, this.canvas?.height || 0);
159
+ public clearAll(layer?: ImageLayer): void {
160
+ if (!layer || layer === 'top') {
161
+ const ctx = this._layers.get('top');
162
+ ctx?.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
163
+ }
164
+ if (!layer || layer === 'bottom') {
165
+ const ctx = this._layers.get('bottom');
166
+ ctx?.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
167
+ }
157
168
  }
158
169
 
159
170
  /**
160
171
  * Draw neighboring tiles on the image layer canvas.
161
172
  */
162
173
  public draw(imgSpec: IImageSpec, tileId: number, col: number, row: number, count: number = 1): void {
163
- if (!this._ctx) {
174
+ const ctx = this._layers.get(imgSpec.layer);
175
+ if (!ctx) {
164
176
  return;
165
177
  }
166
178
  const { width, height } = this.cellSize;
@@ -187,7 +199,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
187
199
  // Note: For not pixel perfect aligned cells like in the DOM renderer
188
200
  // this will move a tile slightly to the top/left (subpixel range, thus ignore it).
189
201
  // FIX #34: avoid striping on displays with pixelDeviceRatio != 1 by ceiling height and width
190
- this._ctx.drawImage(
202
+ ctx.drawImage(
191
203
  img,
192
204
  Math.floor(sx), Math.floor(sy), Math.ceil(finalWidth), Math.ceil(finalHeight),
193
205
  Math.floor(dx), Math.floor(dy), Math.ceil(finalWidth), Math.ceil(finalHeight)
@@ -227,7 +239,8 @@ export class ImageRenderer extends Disposable implements IDisposable {
227
239
  * Draw a line with placeholder on the image layer canvas.
228
240
  */
229
241
  public drawPlaceholder(col: number, row: number, count: number = 1): void {
230
- if (this._ctx) {
242
+ const ctx = this._layers.get('top');
243
+ if (ctx) {
231
244
  const { width, height } = this.cellSize;
232
245
 
233
246
  // Don't try to draw anything, if we cannot get valid renderer metrics.
@@ -241,7 +254,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
241
254
  this._createPlaceHolder(height + 1);
242
255
  }
243
256
  if (!this._placeholder) return;
244
- this._ctx.drawImage(
257
+ ctx.drawImage(
245
258
  this._placeholderBitmap ?? this._placeholder!,
246
259
  col * width,
247
260
  (row * height) % 2 ? 0 : 1, // needs %2 offset correction
@@ -260,12 +273,13 @@ export class ImageRenderer extends Disposable implements IDisposable {
260
273
  * Checked once from `ImageStorage.render`.
261
274
  */
262
275
  public rescaleCanvas(): void {
263
- if (!this.canvas) {
264
- return;
265
- }
266
- if (this.canvas.width !== this.dimensions!.css.canvas.width || this.canvas.height !== this.dimensions!.css.canvas.height) {
267
- this.canvas.width = this.dimensions!.css.canvas.width || 0;
268
- this.canvas.height = this.dimensions!.css.canvas.height || 0;
276
+ const w = this.dimensions?.css.canvas.width || 0;
277
+ const h = this.dimensions?.css.canvas.height || 0;
278
+ for (const ctx of this._layers.values()) {
279
+ if (ctx.canvas.width !== w || ctx.canvas.height !== h) {
280
+ ctx.canvas.width = w;
281
+ ctx.canvas.height = h;
282
+ }
269
283
  }
270
284
  }
271
285
 
@@ -304,37 +318,64 @@ export class ImageRenderer extends Disposable implements IDisposable {
304
318
  this._renderService = this._terminal._core._renderService;
305
319
  this._oldSetRenderer = this._renderService.setRenderer.bind(this._renderService);
306
320
  this._renderService.setRenderer = (renderer: any) => {
307
- this.removeLayerFromDom();
321
+ for (const key of [...this._layers.keys()]) {
322
+ this.removeLayerFromDom(key);
323
+ }
308
324
  this._oldSetRenderer?.call(this._renderService, renderer);
309
325
  };
310
326
  }
311
327
 
312
- public insertLayerToDom(): void {
328
+ public insertLayerToDom(layer: ImageLayer = 'top'): void {
313
329
  // make sure that the terminal is attached to a document and to DOM
314
- if (this.document && this._terminal._core.screenElement) {
315
- if (!this.canvas) {
316
- this.canvas = ImageRenderer.createCanvas(
317
- this.document, this.dimensions?.css.canvas.width || 0,
318
- this.dimensions?.css.canvas.height || 0
319
- );
320
- this.canvas.classList.add('xterm-image-layer');
321
- this._terminal._core.screenElement.appendChild(this.canvas);
322
- this._ctx = this.canvas.getContext('2d', { alpha: true, desynchronized: true });
323
- this.clearAll();
324
- }
325
- } else {
330
+ if (!this.document || !this._terminal._core.screenElement) {
326
331
  console.warn('image addon: cannot insert output canvas to DOM, missing document or screenElement');
332
+ return;
333
+ }
334
+ if (this._layers.has(layer)) {
335
+ return;
327
336
  }
337
+ const canvas = ImageRenderer.createCanvas(
338
+ this.document, this.dimensions?.css.canvas.width || 0,
339
+ this.dimensions?.css.canvas.height || 0
340
+ );
341
+ canvas.classList.add(`xterm-image-layer-${layer}`);
342
+ const screenElement = this._terminal._core.screenElement;
343
+ if (layer === 'bottom') {
344
+ // Use z-index:-1 so it paints behind non-positioned text elements.
345
+ // The screen element needs to be a stacking context to contain the
346
+ // negative z-index, otherwise it would go behind the entire terminal.
347
+ canvas.style.zIndex = '-1';
348
+ screenElement.style.zIndex = '0';
349
+ screenElement.insertBefore(canvas, screenElement.firstChild);
350
+ } else {
351
+ // Explicit z-index ensures the image canvas reliably stacks above
352
+ // the text layer (DOM renderer rows). z-index: 0 is below the
353
+ // selection overlay (z-index: 1).
354
+ canvas.style.zIndex = '0';
355
+ screenElement.style.zIndex = '0';
356
+ screenElement.appendChild(canvas);
357
+ }
358
+ const ctx = canvas.getContext('2d', { alpha: true, desynchronized: true });
359
+ if (!ctx) {
360
+ canvas.remove();
361
+ return;
362
+ }
363
+ this._layers.set(layer, ctx);
364
+ this.clearAll(layer);
328
365
  }
329
366
 
330
- public removeLayerFromDom(): void {
331
- if (this.canvas) {
332
- this._ctx = undefined;
333
- this.canvas.remove();
334
- this.canvas = undefined;
367
+ public removeLayerFromDom(layer: ImageLayer = 'top'): void {
368
+ const ctx = this._layers.get(layer);
369
+ if (ctx) {
370
+ ctx.canvas.remove();
371
+ this._layers.delete(layer);
335
372
  }
336
373
  }
337
374
 
375
+ public hasLayer(layer: ImageLayer): boolean {
376
+ return this._layers.has(layer);
377
+ }
378
+
338
379
  private _createPlaceHolder(height: number = PLACEHOLDER_HEIGHT): void {
339
380
  this._placeholderBitmap?.close();
340
381
  this._placeholderBitmap = undefined;
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { IDisposable } from '@xterm/xterm';
7
7
  import { ImageRenderer } from './ImageRenderer';
8
- import { ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec, IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes, UnderlineStyle } from './Types';
8
+ import { ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec, IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes, UnderlineStyle, ImageLayer } from './Types';
9
9
 
10
10
 
11
11
  // fallback default cell size
@@ -124,6 +124,7 @@ export class ImageStorage implements IDisposable {
124
124
  private _pixelLimit: number = 2500000;
125
125
 
126
126
  private _viewportMetrics: { cols: number, rows: number };
127
+ public onImageDeleted: ((storageId: number) => void) | undefined;
127
128
 
128
129
  constructor(
129
130
  private _terminal: ITerminalExt,
@@ -189,11 +190,13 @@ export class ImageStorage implements IDisposable {
189
190
 
190
191
  private _delImg(id: number): void {
191
192
  const spec = this._images.get(id);
193
+ if (!spec) return;
192
194
  this._images.delete(id);
193
195
  // FIXME: really ugly workaround to get bitmaps deallocated :(
194
- if (spec && window.ImageBitmap && spec.orig instanceof ImageBitmap) {
196
+ if (window.ImageBitmap && spec.orig instanceof ImageBitmap) {
195
197
  spec.orig.close();
196
198
  }
199
+ this.onImageDeleted?.(id);
197
200
  }
198
201
 
199
202
  /**
@@ -216,14 +219,28 @@ export class ImageStorage implements IDisposable {
216
219
  this._fullyCleared = false;
217
220
  }
218
221
 
222
+ /**
223
+ * Delete an image by its internal storage ID.
224
+ * Used by protocols that support explicit deletion (e.g. Kitty a=d).
225
+ */
226
+ public deleteImage(id: number): void {
227
+ const spec = this._images.get(id);
228
+ if (spec) {
229
+ spec.marker?.dispose();
230
+ this._delImg(id);
231
+ }
232
+ }
233
+
219
234
  /**
220
235
  * Method to add an image to the storage.
221
236
  * @param img - The image to add (canvas or bitmap).
222
237
  * @param scrolling - When true, cursor advances with the image (lineFeed per row).
223
238
  * When false, image is placed at (0,0) and cursor is restored (DECSET 80 / sixel origin mode).
239
+ * @param layer - Which canvas layer to render on ('top' or 'bottom').
240
+ * @param zIndex - Z-index for image layering within the same layer.
224
241
  * @returns The internal image ID assigned to the stored image.
225
242
  */
226
- public addImage(img: HTMLCanvasElement | ImageBitmap, scrolling: boolean): number {
243
+ public addImage(img: HTMLCanvasElement | ImageBitmap, scrolling: boolean, layer: ImageLayer = 'top', zIndex: number = 0): number {
227
244
  // never allow storage to exceed memory limit
228
245
  this._evictOldest(img.width * img.height);
229
246
 
@@ -312,7 +329,9 @@ export class ImageStorage implements IDisposable {
312
329
  actualCellSize: { ...cellSize }, // clone needed, since later modified
313
330
  marker: endMarker || undefined,
314
331
  tileCount,
315
- bufferType: this._terminal.buffer.active.type
332
+ bufferType: this._terminal.buffer.active.type,
333
+ layer,
334
+ zIndex
316
335
  };
317
336
 
318
337
  // finally add the image
@@ -327,16 +346,30 @@ export class ImageStorage implements IDisposable {
327
346
  */
328
347
  // TODO: Should we move this to the ImageRenderer?
329
348
  public render(range: { start: number, end: number }): void {
330
- // setup image canvas in case we have none yet, but have images in store
331
- if (!this._renderer.canvas && this._images.size) {
332
- this._renderer.insertLayerToDom();
333
- // safety measure - in case we cannot spawn a canvas at all, just exit
334
- if (!this._renderer.canvas) {
335
- return;
349
+ // Determine which layers have images
350
+ let hasTopImages = false;
351
+ let hasBottomImages = false;
352
+ for (const spec of this._images.values()) {
353
+ if (spec.layer === 'bottom') {
354
+ hasBottomImages = true;
355
+ } else {
356
+ hasTopImages = true;
336
357
  }
358
+ if (hasTopImages && hasBottomImages) break;
359
+ }
360
+
361
+ // Lazily insert layers that are needed
362
+ if (hasTopImages && !this._renderer.hasLayer('top')) {
363
+ this._renderer.insertLayerToDom('top');
364
+ if (!this._renderer.hasLayer('top')) return;
337
365
  }
366
+ if (hasBottomImages && !this._renderer.hasLayer('bottom')) {
367
+ this._renderer.insertLayerToDom('bottom');
368
+ }
369
+
338
370
  // rescale if needed
339
371
  this._renderer.rescaleCanvas();
372
+
340
373
  // exit early if we dont have any images to test for
341
374
  if (!this._images.size) {
342
375
  if (!this._fullyCleared) {
@@ -344,12 +377,25 @@ export class ImageStorage implements IDisposable {
344
377
  this._fullyCleared = true;
345
378
  this._needsFullClear = false;
346
379
  }
347
- if (this._renderer.canvas) {
348
- this._renderer.removeLayerFromDom();
380
+ if (this._renderer.hasLayer('top')) {
381
+ this._renderer.removeLayerFromDom('top');
382
+ }
383
+ if (this._renderer.hasLayer('bottom')) {
384
+ this._renderer.removeLayerFromDom('bottom');
349
385
  }
350
386
  return;
351
387
  }
352
388
 
389
+ // Remove layers no longer needed
390
+ if (!hasTopImages && this._renderer.hasLayer('top')) {
391
+ this._renderer.clearAll('top');
392
+ this._renderer.removeLayerFromDom('top');
393
+ }
394
+ if (!hasBottomImages && this._renderer.hasLayer('bottom')) {
395
+ this._renderer.clearAll('bottom');
396
+ this._renderer.removeLayerFromDom('bottom');
397
+ }
398
+
353
399
  // buffer switches force a full clear
354
400
  if (this._needsFullClear) {
355
401
  this._renderer.clearAll();
@@ -364,50 +410,77 @@ export class ImageStorage implements IDisposable {
364
410
  // clear drawing area
365
411
  this._renderer.clearLines(start, end);
366
412
 
367
- // walk all cells in viewport and draw tiles found
413
+ // Collect draw calls so we can sort by z-index (lower z drawn first).
414
+ const drawCalls: { imgSpec: IImageSpec, tileId: number, col: number, row: number, count: number }[] = [];
415
+ const placeholderCalls: { col: number, row: number, count: number }[] = [];
416
+
417
+ // walk all cells in viewport and collect tiles found
418
+ // Note: We check _extendedAttrs directly (not just HAS_EXTENDED flag)
419
+ // because text writes clear the BG flag but leave image tile data intact.
420
+ // This lets top-layer images survive text overwrites (kitty C=1 behavior).
368
421
  for (let row = start; row <= end; ++row) {
369
422
  const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt;
370
423
  if (!line) return;
371
424
  for (let col = 0; col < cols; ++col) {
425
+ let e: IExtendedAttrsImage;
372
426
  if (line.getBg(col) & BgFlags.HAS_EXTENDED) {
373
- let e: IExtendedAttrsImage = line._extendedAttrs[col] ?? EMPTY_ATTRS;
374
- const imageId = e.imageId;
375
- if (imageId === undefined || imageId === -1) {
427
+ e = line._extendedAttrs[col] ?? EMPTY_ATTRS;
428
+ } else {
429
+ const maybeImg = line._extendedAttrs[col] as IExtendedAttrsImage | undefined;
430
+ if (!maybeImg || maybeImg.imageId === undefined || maybeImg.imageId === -1) {
376
431
  continue;
377
432
  }
378
- const imgSpec = this._images.get(imageId);
379
- if (e.tileId !== -1) {
380
- const startTile = e.tileId;
381
- const startCol = col;
382
- let count = 1;
383
- /**
384
- * merge tiles to the right into a single draw call, if:
385
- * - not at end of line
386
- * - cell has same image id
387
- * - cell has consecutive tile id
388
- */
389
- while (
390
- ++col < cols
391
- && (line.getBg(col) & BgFlags.HAS_EXTENDED)
392
- && (e = line._extendedAttrs[col] ?? EMPTY_ATTRS)
393
- && (e.imageId === imageId)
394
- && (e.tileId === startTile + count)
395
- ) {
396
- count++;
433
+ e = maybeImg;
434
+ }
435
+ const imageId = e.imageId;
436
+ if (imageId === undefined || imageId === -1) {
437
+ continue;
438
+ }
439
+ const imgSpec = this._images.get(imageId);
440
+ if (e.tileId !== -1) {
441
+ const startTile = e.tileId;
442
+ const startCol = col;
443
+ let count = 1;
444
+ /**
445
+ * merge tiles to the right into a single draw call, if:
446
+ * - not at end of line
447
+ * - cell has same image id
448
+ * - cell has consecutive tile id
449
+ * Also check _extendedAttrs directly for cells where text cleared HAS_EXTENDED.
450
+ */
451
+ while (++col < cols) {
452
+ const nextE = line._extendedAttrs[col] as IExtendedAttrsImage | undefined;
453
+ if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
454
+ break;
397
455
  }
398
- col--;
399
- if (imgSpec) {
400
- if (imgSpec.actual) {
401
- this._renderer.draw(imgSpec, startTile, startCol, row, count);
402
- }
403
- } else if (this._opts.showPlaceholder) {
404
- this._renderer.drawPlaceholder(startCol, row, count);
456
+ e = nextE;
457
+ count++;
458
+ }
459
+ col--;
460
+ if (imgSpec) {
461
+ if (imgSpec.actual) {
462
+ drawCalls.push({ imgSpec, tileId: startTile, col: startCol, row, count });
405
463
  }
406
- this._fullyCleared = false;
464
+ } else if (this._opts.showPlaceholder) {
465
+ placeholderCalls.push({ col: startCol, row, count });
407
466
  }
467
+ this._fullyCleared = false;
408
468
  }
409
469
  }
410
470
  }
471
+
472
+ // Sort by z-index so lower z draws first (higher z renders on top)
473
+ drawCalls.sort((a, b) => a.imgSpec.zIndex - b.imgSpec.zIndex);
474
+
475
+ // Draw placeholders first (lowest priority)
476
+ for (const call of placeholderCalls) {
477
+ this._renderer.drawPlaceholder(call.col, call.row, call.count);
478
+ }
479
+
480
+ // Draw images in z-index order
481
+ for (const call of drawCalls) {
482
+ this._renderer.draw(call.imgSpec, call.tileId, call.col, call.row, call.count);
483
+ }
411
484
  }
412
485
 
413
486
  public viewportResize(metrics: { cols: number, rows: number }): void {
package/src/Types.ts CHANGED
@@ -8,7 +8,7 @@ import { IDisposable, IMarker, Terminal } from '@xterm/xterm';
8
8
  // private imports from base repo we build against
9
9
  import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/buffer/Constants';
10
10
  import type { AttributeData } from 'common/buffer/AttributeData';
11
- import type { IParams, IDcsHandler, IOscHandler, IEscapeSequenceParser } from 'common/parser/Types';
11
+ import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';
12
12
  import type { IBufferLine, IExtendedAttrs, IInputHandler } from 'common/Types';
13
13
  import type { ITerminal, ReadonlyColorSet } from 'browser/Types';
14
14
  import type { IRenderDimensions } from 'browser/renderer/shared/Types';
@@ -22,7 +22,7 @@ export const enum Cell {
22
22
  }
23
23
 
24
24
  // export some privates for local usage
25
- export { AttributeData, IParams, IDcsHandler, IOscHandler, BgFlags, IRenderDimensions, IRenderService, Content, ExtFlags, Attributes, UnderlineStyle, ReadonlyColorSet };
25
+ export { AttributeData, IParams, IDcsHandler, IOscHandler, IApcHandler, BgFlags, IRenderDimensions, IRenderService, Content, ExtFlags, Attributes, UnderlineStyle, ReadonlyColorSet };
26
26
 
27
27
  /**
28
28
  * Plugin ctor options.
@@ -38,6 +38,8 @@ export interface IImageAddonOptions {
38
38
  sixelSizeLimit: number;
39
39
  iipSupport: boolean;
40
40
  iipSizeLimit: number;
41
+ kittySupport: boolean;
42
+ kittySizeLimit: number;
41
43
  }
42
44
 
43
45
  export interface IResetHandler {
@@ -97,6 +99,8 @@ export interface ICellSize {
97
99
  height: number;
98
100
  }
99
101
 
102
+ export type ImageLayer = 'top' | 'bottom';
103
+
100
104
  export interface IImageSpec {
101
105
  orig: HTMLCanvasElement | ImageBitmap | undefined;
102
106
  origCellSize: ICellSize;
@@ -105,4 +109,6 @@ export interface IImageSpec {
105
109
  marker: IMarker | undefined;
106
110
  tileCount: number;
107
111
  bufferType: 'alternate' | 'normal';
112
+ layer: ImageLayer;
113
+ zIndex: number;
108
114
  }