@xterm/addon-image 0.10.0-beta.31 → 0.10.0-beta.310

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.
@@ -5,8 +5,11 @@
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';
9
-
8
+ import type {
9
+ ITerminalExt, IImageAddonOptions, IImageSpec, ICellSize, IAddImageOpts
10
+ } from './Types';
11
+ import type { IBufferLine } from 'common/buffer/Types';
12
+ import { CellData } from 'common/buffer/CellData';
10
13
 
11
14
  // fallback default cell size
12
15
  export const CELL_SIZE_DEFAULT: ICellSize = {
@@ -14,93 +17,12 @@ export const CELL_SIZE_DEFAULT: ICellSize = {
14
17
  height: 14
15
18
  };
16
19
 
17
- /**
18
- * Extend extended attribute to also hold image tile information.
19
- *
20
- * Object definition is copied from base repo to fully mimick its behavior.
21
- * Image data is added as additional public properties `imageId` and `tileId`.
22
- */
23
- class ExtendedAttrsImage implements IExtendedAttrsImage {
24
- private _ext: number = 0;
25
- public get ext(): number {
26
- if (this._urlId) {
27
- return (
28
- (this._ext & ~ExtFlags.UNDERLINE_STYLE) |
29
- (this.underlineStyle << 26)
30
- );
31
- }
32
- return this._ext;
33
- }
34
- public set ext(value: number) { this._ext = value; }
35
-
36
- public get underlineStyle(): UnderlineStyle {
37
- // Always return the URL style if it has one
38
- if (this._urlId) {
39
- return UnderlineStyle.DASHED;
40
- }
41
- return (this._ext & ExtFlags.UNDERLINE_STYLE) >> 26;
42
- }
43
- public set underlineStyle(value: UnderlineStyle) {
44
- this._ext &= ~ExtFlags.UNDERLINE_STYLE;
45
- this._ext |= (value << 26) & ExtFlags.UNDERLINE_STYLE;
46
- }
47
-
48
- public get underlineColor(): number {
49
- return this._ext & (Attributes.CM_MASK | Attributes.RGB_MASK);
50
- }
51
- public set underlineColor(value: number) {
52
- this._ext &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
53
- this._ext |= value & (Attributes.CM_MASK | Attributes.RGB_MASK);
54
- }
55
-
56
- public get underlineVariantOffset(): number {
57
- const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29;
58
- if (val < 0) {
59
- return val ^ 0xFFFFFFF8;
60
- }
61
- return val;
62
- }
63
- public set underlineVariantOffset(value: number) {
64
- this._ext &= ~ExtFlags.VARIANT_OFFSET;
65
- this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET;
66
- }
67
-
68
- private _urlId: number = 0;
69
- public get urlId(): number {
70
- return this._urlId;
71
- }
72
- public set urlId(value: number) {
73
- this._urlId = value;
74
- }
75
-
20
+ class ImageTileInfo {
76
21
  constructor(
77
- ext: number = 0,
78
- urlId: number = 0,
79
22
  public imageId = -1,
80
- public tileId = -1
81
- ) {
82
- this._ext = ext;
83
- this._urlId = urlId;
84
- }
85
-
86
- public clone(): IExtendedAttrsImage {
87
- /**
88
- * Technically we dont need a clone variant of ExtendedAttrsImage,
89
- * as we never clone a cell holding image data.
90
- * Note: Clone is only meant to be used by the InputHandler for
91
- * sticky attributes, which is never the case for image data.
92
- * We still provide a proper clone method to reflect the full ext attr
93
- * state in case there are future use cases for clone.
94
- */
95
- return new ExtendedAttrsImage(this._ext, this._urlId, this.imageId, this.tileId);
96
- }
97
-
98
- public isEmpty(): boolean {
99
- return this.underlineStyle === UnderlineStyle.NONE && this._urlId === 0 && this.imageId === -1;
23
+ public tileId = -1) {
100
24
  }
101
25
  }
102
- const EMPTY_ATTRS = new ExtendedAttrsImage();
103
-
104
26
 
105
27
  /**
106
28
  * ImageStorage - extension of CoreTerminal:
@@ -122,8 +44,11 @@ export class ImageStorage implements IDisposable {
122
44
  private _needsFullClear = false;
123
45
  // hard limit of stored pixels (fallback limit of 10 MB)
124
46
  private _pixelLimit: number = 2500000;
47
+ private _workCell: CellData = new CellData();
125
48
 
126
49
  private _viewportMetrics: { cols: number, rows: number };
50
+ public onImageAdded: (() => void) | undefined;
51
+ public onImageDeleted: ((storageId: number) => void) | undefined;
127
52
 
128
53
  constructor(
129
54
  private _terminal: ITerminalExt,
@@ -132,8 +57,10 @@ export class ImageStorage implements IDisposable {
132
57
  ) {
133
58
  try {
134
59
  this.setLimit(this._opts.storageLimit);
135
- } catch (e: any) {
136
- console.error(e.message);
60
+ } catch (e: unknown) {
61
+ if (e instanceof Error) {
62
+ console.error(e.message);
63
+ }
137
64
  console.warn(`storageLimit is set to ${this.getLimit()} MB`);
138
65
  }
139
66
  this._viewportMetrics = {
@@ -187,11 +114,13 @@ export class ImageStorage implements IDisposable {
187
114
 
188
115
  private _delImg(id: number): void {
189
116
  const spec = this._images.get(id);
117
+ if (!spec) return;
190
118
  this._images.delete(id);
191
119
  // FIXME: really ugly workaround to get bitmaps deallocated :(
192
- if (spec && window.ImageBitmap && spec.orig instanceof ImageBitmap) {
120
+ if (window.ImageBitmap && spec.orig instanceof ImageBitmap) {
193
121
  spec.orig.close();
194
122
  }
123
+ this.onImageDeleted?.(id);
195
124
  }
196
125
 
197
126
  /**
@@ -215,27 +144,29 @@ export class ImageStorage implements IDisposable {
215
144
  }
216
145
 
217
146
  /**
218
- * Only advance text cursor.
219
- * This is an edge case from empty sixels carrying only a height but no pixels.
220
- * Partially fixes https://github.com/jerch/xterm-addon-image/issues/37.
147
+ * Delete an image by its internal storage ID.
148
+ * Used by protocols that support explicit deletion (e.g. Kitty a=d).
221
149
  */
222
- public advanceCursor(height: number): void {
223
- if (this._opts.sixelScrolling) {
224
- let cellSize = this._renderer.cellSize;
225
- if (cellSize.width === -1 || cellSize.height === -1) {
226
- cellSize = CELL_SIZE_DEFAULT;
227
- }
228
- const rows = Math.ceil(height / cellSize.height);
229
- for (let i = 1; i < rows; ++i) {
230
- this._terminal._core._inputHandler.lineFeed();
231
- }
150
+ public deleteImage(id: number): void {
151
+ const spec = this._images.get(id);
152
+ if (spec) {
153
+ spec.marker?.dispose();
154
+ this._delImg(id);
232
155
  }
233
156
  }
234
157
 
235
158
  /**
236
159
  * Method to add an image to the storage.
160
+ * @param img - The image to add (canvas or bitmap).
161
+ * @param opts - Options for addImage:
162
+ * - scrolling: When true, cursor advances with the image.
163
+ * When false, image is placed at ORIGIN and cursor does not move.
164
+ * - layer: Which canvas layer to render on ('top' or 'bottom').
165
+ * - zIndex: Z-index for image layering within the same layer.
166
+ * - cursorPos: 'vt340' for bottom-left, 'iip' for bottom.right.
167
+ * @returns The internal image ID assigned to the stored image.
237
168
  */
238
- public addImage(img: HTMLCanvasElement | ImageBitmap): void {
169
+ public addImage(img: HTMLCanvasElement | ImageBitmap, opts: IAddImageOpts): number {
239
170
  // never allow storage to exceed memory limit
240
171
  this._evictOldest(img.width * img.height);
241
172
 
@@ -257,7 +188,7 @@ export class ImageStorage implements IDisposable {
257
188
  let offset = originX;
258
189
  let tileCount = 0;
259
190
 
260
- if (!this._opts.sixelScrolling) {
191
+ if (!opts.scrolling) {
261
192
  buffer.x = 0;
262
193
  buffer.y = 0;
263
194
  offset = 0;
@@ -265,13 +196,13 @@ export class ImageStorage implements IDisposable {
265
196
 
266
197
  this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
267
198
  for (let row = 0; row < rows; ++row) {
268
- const line = buffer.lines.get(buffer.y + buffer.ybase);
199
+ const line = buffer.lines.get(buffer.y + buffer.ybase)!;
269
200
  for (let col = 0; col < cols; ++col) {
270
201
  if (offset + col >= termCols) break;
271
- this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);
202
+ this._writeToCell(line, offset + col, imageId, row * cols + col);
272
203
  tileCount++;
273
204
  }
274
- if (this._opts.sixelScrolling) {
205
+ if (opts.scrolling) {
275
206
  if (row < rows - 1) this._terminal._core._inputHandler.lineFeed();
276
207
  } else {
277
208
  if (++buffer.y >= termRows) break;
@@ -281,8 +212,12 @@ export class ImageStorage implements IDisposable {
281
212
  this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
282
213
 
283
214
  // cursor positioning modes
284
- if (this._opts.sixelScrolling) {
285
- buffer.x = offset;
215
+ if (opts.scrolling) {
216
+ if (opts.cursorPos === 'iip') {
217
+ buffer.x = Math.min(offset + cols, termCols);
218
+ } else {
219
+ buffer.x = offset;
220
+ }
286
221
  } else {
287
222
  buffer.x = originX;
288
223
  buffer.y = originY;
@@ -324,11 +259,15 @@ export class ImageStorage implements IDisposable {
324
259
  actualCellSize: { ...cellSize }, // clone needed, since later modified
325
260
  marker: endMarker || undefined,
326
261
  tileCount,
327
- bufferType: this._terminal.buffer.active.type
262
+ bufferType: this._terminal.buffer.active.type,
263
+ layer: opts.layer,
264
+ zIndex: opts.zIndex
328
265
  };
329
266
 
330
267
  // finally add the image
331
268
  this._images.set(imageId, imgSpec);
269
+ this.onImageAdded?.();
270
+ return imageId;
332
271
  }
333
272
 
334
273
 
@@ -338,16 +277,30 @@ export class ImageStorage implements IDisposable {
338
277
  */
339
278
  // TODO: Should we move this to the ImageRenderer?
340
279
  public render(range: { start: number, end: number }): void {
341
- // setup image canvas in case we have none yet, but have images in store
342
- if (!this._renderer.canvas && this._images.size) {
343
- this._renderer.insertLayerToDom();
344
- // safety measure - in case we cannot spawn a canvas at all, just exit
345
- if (!this._renderer.canvas) {
346
- return;
280
+ // Determine which layers have images
281
+ let hasTopImages = false;
282
+ let hasBottomImages = false;
283
+ for (const spec of this._images.values()) {
284
+ if (spec.layer === 'bottom') {
285
+ hasBottomImages = true;
286
+ } else {
287
+ hasTopImages = true;
347
288
  }
289
+ if (hasTopImages && hasBottomImages) break;
348
290
  }
291
+
292
+ // Lazily insert layers that are needed
293
+ if (hasTopImages && !this._renderer.hasLayer('top')) {
294
+ this._renderer.insertLayerToDom('top');
295
+ if (!this._renderer.hasLayer('top')) return;
296
+ }
297
+ if (hasBottomImages && !this._renderer.hasLayer('bottom')) {
298
+ this._renderer.insertLayerToDom('bottom');
299
+ }
300
+
349
301
  // rescale if needed
350
302
  this._renderer.rescaleCanvas();
303
+
351
304
  // exit early if we dont have any images to test for
352
305
  if (!this._images.size) {
353
306
  if (!this._fullyCleared) {
@@ -355,12 +308,25 @@ export class ImageStorage implements IDisposable {
355
308
  this._fullyCleared = true;
356
309
  this._needsFullClear = false;
357
310
  }
358
- if (this._renderer.canvas) {
359
- this._renderer.removeLayerFromDom();
311
+ if (this._renderer.hasLayer('top')) {
312
+ this._renderer.removeLayerFromDom('top');
313
+ }
314
+ if (this._renderer.hasLayer('bottom')) {
315
+ this._renderer.removeLayerFromDom('bottom');
360
316
  }
361
317
  return;
362
318
  }
363
319
 
320
+ // Remove layers no longer needed
321
+ if (!hasTopImages && this._renderer.hasLayer('top')) {
322
+ this._renderer.clearAll('top');
323
+ this._renderer.removeLayerFromDom('top');
324
+ }
325
+ if (!hasBottomImages && this._renderer.hasLayer('bottom')) {
326
+ this._renderer.clearAll('bottom');
327
+ this._renderer.removeLayerFromDom('bottom');
328
+ }
329
+
364
330
  // buffer switches force a full clear
365
331
  if (this._needsFullClear) {
366
332
  this._renderer.clearAll();
@@ -375,13 +341,17 @@ export class ImageStorage implements IDisposable {
375
341
  // clear drawing area
376
342
  this._renderer.clearLines(start, end);
377
343
 
378
- // walk all cells in viewport and draw tiles found
344
+ // Collect draw calls so we can sort by z-index (lower z drawn first).
345
+ const drawCalls: { imgSpec: IImageSpec, tileId: number, col: number, row: number, count: number }[] = [];
346
+ const placeholderCalls: { col: number, row: number, count: number }[] = [];
347
+
348
+ // walk all cells in viewport and collect tiles found
379
349
  for (let row = start; row <= end; ++row) {
380
- const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt;
350
+ const line = buffer.lines.get(row + buffer.ydisp);
381
351
  if (!line) return;
382
352
  for (let col = 0; col < cols; ++col) {
383
- if (line.getBg(col) & BgFlags.HAS_EXTENDED) {
384
- let e: IExtendedAttrsImage = line._extendedAttrs[col] || EMPTY_ATTRS;
353
+ const e = line.getExtended(col)?.payload;
354
+ if (e instanceof ImageTileInfo) {
385
355
  const imageId = e.imageId;
386
356
  if (imageId === undefined || imageId === -1) {
387
357
  continue;
@@ -397,28 +367,39 @@ export class ImageStorage implements IDisposable {
397
367
  * - cell has same image id
398
368
  * - cell has consecutive tile id
399
369
  */
400
- while (
401
- ++col < cols
402
- && (line.getBg(col) & BgFlags.HAS_EXTENDED)
403
- && (e = line._extendedAttrs[col] || EMPTY_ATTRS)
404
- && (e.imageId === imageId)
405
- && (e.tileId === startTile + count)
406
- ) {
370
+ while (++col < cols) {
371
+ const nextE = line.getExtended(col)?.payload;
372
+ if (!(nextE instanceof ImageTileInfo) || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
373
+ break;
374
+ }
407
375
  count++;
408
376
  }
409
377
  col--;
410
378
  if (imgSpec) {
411
379
  if (imgSpec.actual) {
412
- this._renderer.draw(imgSpec, startTile, startCol, row, count);
380
+ drawCalls.push({ imgSpec, tileId: startTile, col: startCol, row, count });
413
381
  }
414
382
  } else if (this._opts.showPlaceholder) {
415
- this._renderer.drawPlaceholder(startCol, row, count);
383
+ placeholderCalls.push({ col: startCol, row, count });
416
384
  }
417
385
  this._fullyCleared = false;
418
386
  }
419
387
  }
420
388
  }
421
389
  }
390
+
391
+ // Sort by z-index so lower z draws first (higher z renders on top)
392
+ drawCalls.sort((a, b) => a.imgSpec.zIndex - b.imgSpec.zIndex);
393
+
394
+ // Draw placeholders first (lowest priority)
395
+ for (const call of placeholderCalls) {
396
+ this._renderer.drawPlaceholder(call.col, call.row, call.count);
397
+ }
398
+
399
+ // Draw images in z-index order
400
+ for (const call of drawCalls) {
401
+ this._renderer.draw(call.imgSpec, call.tileId, call.col, call.row, call.count);
402
+ }
422
403
  }
423
404
 
424
405
  public viewportResize(metrics: { cols: number, rows: number }): void {
@@ -440,9 +421,9 @@ export class ImageStorage implements IDisposable {
440
421
  const rows = buffer.lines.length;
441
422
  const oldCol = this._viewportMetrics.cols - 1;
442
423
  for (let row = 0; row < rows; ++row) {
443
- const line = buffer.lines.get(row) as IBufferLineExt;
444
- if (line.getBg(oldCol) & BgFlags.HAS_EXTENDED) {
445
- const e: IExtendedAttrsImage = line._extendedAttrs[oldCol] || EMPTY_ATTRS;
424
+ const line = buffer.lines.get(row)!;
425
+ const e = line.getExtended(oldCol)?.payload;
426
+ if (e instanceof ImageTileInfo) {
446
427
  const imageId = e.imageId;
447
428
  if (imageId === undefined || imageId === -1) {
448
429
  continue;
@@ -459,7 +440,7 @@ export class ImageStorage implements IDisposable {
459
440
  // expand only if right side is empty (nothing got wrapped from below)
460
441
  let hasData = false;
461
442
  for (let rightCol = oldCol + 1; rightCol > metrics.cols; ++rightCol) {
462
- if (line._data[rightCol * Cell.SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) {
443
+ if (line.hasContent(rightCol)) {
463
444
  hasData = true;
464
445
  break;
465
446
  }
@@ -471,7 +452,7 @@ export class ImageStorage implements IDisposable {
471
452
  const end = Math.min(metrics.cols, tilesPerRow - (e.tileId % tilesPerRow) + oldCol);
472
453
  let lastTile = e.tileId;
473
454
  for (let expandCol = oldCol + 1; expandCol < end; ++expandCol) {
474
- this._writeToCell(line as IBufferLineExt, expandCol, imageId, ++lastTile);
455
+ this._writeToCell(line, expandCol, imageId, ++lastTile);
475
456
  imgSpec.tileCount++;
476
457
  }
477
458
  }
@@ -485,10 +466,10 @@ export class ImageStorage implements IDisposable {
485
466
  */
486
467
  public getImageAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined {
487
468
  const buffer = this._terminal._core.buffer;
488
- const line = buffer.lines.get(y) as IBufferLineExt;
489
- if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {
490
- const e: IExtendedAttrsImage = line._extendedAttrs[x] || EMPTY_ATTRS;
491
- if (e.imageId && e.imageId !== -1) {
469
+ const line = buffer.lines.get(y);
470
+ if (line) {
471
+ const e = line.getExtended(x)?.payload;
472
+ if (e instanceof ImageTileInfo && e.imageId && e.imageId !== -1) {
492
473
  const orig = this._images.get(e.imageId)?.orig;
493
474
  if (window.ImageBitmap && orig instanceof ImageBitmap) {
494
475
  const canvas = ImageRenderer.createCanvas(window.document, orig.width, orig.height);
@@ -505,10 +486,10 @@ export class ImageStorage implements IDisposable {
505
486
  */
506
487
  public extractTileAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined {
507
488
  const buffer = this._terminal._core.buffer;
508
- const line = buffer.lines.get(y) as IBufferLineExt;
509
- if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {
510
- const e: IExtendedAttrsImage = line._extendedAttrs[x] || EMPTY_ATTRS;
511
- if (e.imageId && e.imageId !== -1 && e.tileId !== -1) {
489
+ const line = buffer.lines.get(y);
490
+ if (line) {
491
+ const e = line.getExtended(x)?.payload;
492
+ if (e instanceof ImageTileInfo && e.imageId && e.imageId !== -1 && e.tileId !== -1) {
512
493
  const spec = this._images.get(e.imageId);
513
494
  if (spec) {
514
495
  return this._renderer.extractTile(spec, e.tileId);
@@ -536,31 +517,31 @@ export class ImageStorage implements IDisposable {
536
517
  return used - current;
537
518
  }
538
519
 
539
- private _writeToCell(line: IBufferLineExt, x: number, imageId: number, tileId: number): void {
540
- if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
541
- const old = line._extendedAttrs[x];
542
- if (old) {
543
- if (old.imageId !== undefined) {
544
- // found an old ExtendedAttrsImage, since we know that
545
- // they are always isolated instances (single cell usage),
546
- // we can re-use it and just update their id entries
547
- const oldSpec = this._images.get(old.imageId);
548
- if (oldSpec) {
549
- // early eviction for in-viewport overwrites
550
- oldSpec.tileCount--;
551
- }
552
- old.imageId = imageId;
553
- old.tileId = tileId;
554
- return;
520
+ private _writeToCell(line: IBufferLine, x: number, imageId: number, tileId: number): void {
521
+ const workCell = this._workCell;
522
+ line.loadCell(x, workCell);
523
+ if (this._workCell.hasExtendedAttrs()) {
524
+ const old = workCell.extended.payload;
525
+ if (old instanceof ImageTileInfo) {
526
+ // found an old ExtendedAttrsImage, since we know that
527
+ // they are always isolated instances (single cell usage),
528
+ // we can re-use it and just update their id entries
529
+ const oldSpec = this._images.get(old.imageId);
530
+ if (oldSpec) {
531
+ // early eviction for in-viewport overwrites
532
+ oldSpec.tileCount--;
555
533
  }
556
- // found a plain ExtendedAttrs instance, clone it to new entry
557
- line._extendedAttrs[x] = new ExtendedAttrsImage(old.ext, old.urlId, imageId, tileId);
534
+ old.imageId = imageId;
535
+ old.tileId = tileId;
558
536
  return;
559
537
  }
560
538
  }
561
- // fall-through: always create new ExtendedAttrsImage entry
562
- line._data[x * Cell.SIZE + Cell.BG] |= BgFlags.HAS_EXTENDED;
563
- line._extendedAttrs[x] = new ExtendedAttrsImage(0, 0, imageId, tileId);
539
+ // fall-through: always create new ExtendedAttrs entry
540
+ const extattr = workCell.extended.clone();
541
+ extattr.payload = new ImageTileInfo(imageId, tileId);
542
+ workCell.extended = extattr;
543
+ workCell.updateExtended();
544
+ line.setCell(x, workCell);
564
545
  }
565
546
 
566
547
  private _evictOnAlternate(): void {
@@ -573,18 +554,16 @@ export class ImageStorage implements IDisposable {
573
554
  // re-count tiles on whole buffer
574
555
  const buffer = this._terminal._core.buffer;
575
556
  for (let y = 0; y < this._terminal.rows; ++y) {
576
- const line = buffer.lines.get(y) as IBufferLineExt;
557
+ const line = buffer.lines.get(y);
577
558
  if (!line) {
578
559
  continue;
579
560
  }
580
561
  for (let x = 0; x < this._terminal.cols; ++x) {
581
- if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
582
- const imgId = line._extendedAttrs[x]?.imageId;
583
- if (imgId) {
584
- const spec = this._images.get(imgId);
585
- if (spec) {
586
- spec.tileCount++;
587
- }
562
+ const payload = line.getExtended(x)?.payload;
563
+ if (payload instanceof ImageTileInfo) {
564
+ const spec = this._images.get(payload.imageId);
565
+ if (spec) {
566
+ spec.tileCount++;
588
567
  }
589
568
  }
590
569
  }
@@ -3,7 +3,7 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
- import { ImageStorage } from './ImageStorage';
6
+ import { SixelImageStorage } from './SixelImageStorage';
7
7
  import { IDcsHandler, IParams, IImageAddonOptions, ITerminalExt, AttributeData, IResetHandler, ReadonlyColorSet } from './Types';
8
8
  import { toRGBA8888, BIG_ENDIAN, PALETTE_ANSI_256, PALETTE_VT340_COLOR } from 'sixel/lib/Colors';
9
9
  import { RGBA8888 } from 'sixel/lib/Types';
@@ -26,7 +26,7 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
26
26
 
27
27
  constructor(
28
28
  private readonly _opts: IImageAddonOptions,
29
- private readonly _storage: ImageStorage,
29
+ private readonly _storage: SixelImageStorage,
30
30
  private readonly _coreTerminal: ITerminalExt
31
31
  ) {
32
32
  DecoderAsync({
@@ -91,7 +91,7 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
91
91
  const height = this._dec.height;
92
92
 
93
93
  // partial fix for https://github.com/jerch/xterm-addon-image/issues/37
94
- if (!width || ! height) {
94
+ if (!width || !height) {
95
95
  if (height) {
96
96
  this._storage.advanceCursor(height);
97
97
  }
@@ -99,7 +99,7 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
99
99
  }
100
100
 
101
101
  const canvas = ImageRenderer.createCanvas(undefined, width, height);
102
- canvas.getContext('2d')?.putImageData(new ImageData(this._dec.data8, width, height), 0, 0);
102
+ canvas.getContext('2d')?.putImageData(new ImageData(this._dec.data8 as Uint8ClampedArray<ArrayBuffer>, width, height), 0, 0);
103
103
  if (this._dec.memoryUsage > MEM_PERMA_LIMIT) {
104
104
  this._dec.release();
105
105
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Copyright (c) 2020 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
7
+ import { IImageAddonOptions, ITerminalExt, IAddImageOpts } from './Types';
8
+ import { ImageRenderer } from './ImageRenderer';
9
+
10
+ /**
11
+ * Sixel-specific image storage controller.
12
+ *
13
+ * Wraps the shared ImageStorage with sixel protocol semantics:
14
+ * - Cursor behavior governed by DECSET 80 (sixelScrolling option)
15
+ * - advanceCursor for empty sixels carrying only height
16
+ */
17
+ export class SixelImageStorage {
18
+ private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'vt340' };
19
+ constructor(
20
+ private readonly _storage: ImageStorage,
21
+ private readonly _opts: IImageAddonOptions,
22
+ private readonly _renderer: ImageRenderer,
23
+ private readonly _terminal: ITerminalExt
24
+ ) {}
25
+
26
+ /**
27
+ * Add a sixel image to storage.
28
+ * Cursor behavior depends on the sixelScrolling option (DECSET 80).
29
+ */
30
+ public addImage(img: HTMLCanvasElement | ImageBitmap): void {
31
+ this._addImageOpts.scrolling = this._opts.sixelScrolling;
32
+ this._storage.addImage(img, this._addImageOpts);
33
+ }
34
+
35
+ /**
36
+ * Only advance text cursor.
37
+ * This is an edge case from empty sixels carrying only a height but no pixels.
38
+ * Partially fixes https://github.com/jerch/xterm-addon-image/issues/37.
39
+ */
40
+ public advanceCursor(height: number): void {
41
+ if (this._opts.sixelScrolling) {
42
+ let cellSize = this._renderer.cellSize;
43
+ if (cellSize.width === -1 || cellSize.height === -1) {
44
+ cellSize = CELL_SIZE_DEFAULT;
45
+ }
46
+ const rows = Math.ceil(height / cellSize.height);
47
+ for (let i = 1; i < rows; ++i) {
48
+ this._terminal._core._inputHandler.lineFeed();
49
+ }
50
+ }
51
+ }
52
+ }