diff --git a/addons/addon-image/src/IIPHandler.ts b/addons/addon-image/src/IIPHandler.ts index 9662303f..76492c40 100644 --- a/addons/addon-image/src/IIPHandler.ts +++ b/addons/addon-image/src/IIPHandler.ts @@ -4,7 +4,8 @@ */ import { IImageAddonOptions, IOscHandler, IResetHandler, ITerminalExt } from './Types'; import { ImageRenderer } from './ImageRenderer'; -import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage'; +import { IIPImageStorage } from './IIPImageStorage'; +import { CELL_SIZE_DEFAULT } from './ImageStorage'; import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm'; import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser'; import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics'; @@ -40,7 +41,7 @@ export class IIPHandler implements IOscHandler, IResetHandler { constructor( private readonly _opts: IImageAddonOptions, private readonly _renderer: ImageRenderer, - private readonly _storage: ImageStorage, + private readonly _storage: IIPImageStorage, private readonly _coreTerminal: ITerminalExt ) { const maxEncodedBytes = Math.ceil(this._opts.iipSizeLimit * 4 / 3); diff --git a/addons/addon-image/src/IIPImageStorage.ts b/addons/addon-image/src/IIPImageStorage.ts new file mode 100644 index 00000000..0da91607 --- /dev/null +++ b/addons/addon-image/src/IIPImageStorage.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2023 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ImageStorage } from './ImageStorage'; + +/** + * IIP (iTerm Image Protocol) specific image storage controller. + * + * Wraps the shared ImageStorage with IIP protocol semantics: + * - Always uses scrolling mode (cursor advances with image) + */ +export class IIPImageStorage { + constructor( + private readonly _storage: ImageStorage + ) {} + + /** + * Add an IIP image to storage. + * Always uses scrolling mode — cursor advances past the image. + */ + public addImage(img: HTMLCanvasElement | ImageBitmap): void { + this._storage.addImage(img, true); + } +} diff --git a/addons/addon-image/src/ImageAddon.ts b/addons/addon-image/src/ImageAddon.ts index d18b59a0..4c30a104 100644 --- a/addons/addon-image/src/ImageAddon.ts +++ b/addons/addon-image/src/ImageAddon.ts @@ -9,6 +9,8 @@ import { IIPHandler } from './IIPHandler'; import { ImageRenderer } from './ImageRenderer'; import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage'; import { SixelHandler } from './SixelHandler'; +import { SixelImageStorage } from './SixelImageStorage'; +import { IIPImageStorage } from './IIPImageStorage'; import { ITerminalExt, IImageAddonOptions, IResetHandler } from './Types'; // default values of addon ctor options @@ -129,7 +131,8 @@ export class ImageAddon implements ITerminalAddon, IImageApi { // SIXEL handler if (this._opts.sixelSupport) { - const sixelHandler = new SixelHandler(this._opts, this._storage!, terminal); + const sixelStorage = new SixelImageStorage(this._storage!, this._opts, this._renderer!, terminal); + const sixelHandler = new SixelHandler(this._opts, sixelStorage, terminal); this._handlers.set('sixel', sixelHandler); this._disposeLater( terminal._core._inputHandler._parser.registerDcsHandler({ final: 'q' }, sixelHandler) @@ -138,7 +141,8 @@ export class ImageAddon implements ITerminalAddon, IImageApi { // iTerm IIP handler if (this._opts.iipSupport) { - const iipHandler = new IIPHandler(this._opts, this._renderer!, this._storage!, terminal); + const iipStorage = new IIPImageStorage(this._storage!); + const iipHandler = new IIPHandler(this._opts, this._renderer!, iipStorage, terminal); this._handlers.set('iip', iipHandler); this._disposeLater( terminal._core._inputHandler._parser.registerOscHandler(1337, iipHandler) diff --git a/addons/addon-image/src/ImageStorage.ts b/addons/addon-image/src/ImageStorage.ts index aea9b5e5..73a7cd69 100644 --- a/addons/addon-image/src/ImageStorage.ts +++ b/addons/addon-image/src/ImageStorage.ts @@ -216,28 +216,14 @@ export class ImageStorage implements IDisposable { this._fullyCleared = false; } - /** - * Only advance text cursor. - * This is an edge case from empty sixels carrying only a height but no pixels. - * Partially fixes https://github.com/jerch/xterm-addon-image/issues/37. - */ - public advanceCursor(height: number): void { - if (this._opts.sixelScrolling) { - let cellSize = this._renderer.cellSize; - if (cellSize.width === -1 || cellSize.height === -1) { - cellSize = CELL_SIZE_DEFAULT; - } - const rows = Math.ceil(height / cellSize.height); - for (let i = 1; i < rows; ++i) { - this._terminal._core._inputHandler.lineFeed(); - } - } - } - /** * Method to add an image to the storage. + * @param img - The image to add (canvas or bitmap). + * @param scrolling - When true, cursor advances with the image (lineFeed per row). + * When false, image is placed at (0,0) and cursor is restored (DECSET 80 / sixel origin mode). + * @returns The internal image ID assigned to the stored image. */ - public addImage(img: HTMLCanvasElement | ImageBitmap): void { + public addImage(img: HTMLCanvasElement | ImageBitmap, scrolling: boolean): number { // never allow storage to exceed memory limit this._evictOldest(img.width * img.height); @@ -259,7 +245,7 @@ export class ImageStorage implements IDisposable { let offset = originX; let tileCount = 0; - if (!this._opts.sixelScrolling) { + if (!scrolling) { buffer.x = 0; buffer.y = 0; offset = 0; @@ -273,7 +259,7 @@ export class ImageStorage implements IDisposable { this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col); tileCount++; } - if (this._opts.sixelScrolling) { + if (scrolling) { if (row < rows - 1) this._terminal._core._inputHandler.lineFeed(); } else { if (++buffer.y >= termRows) break; @@ -283,7 +269,7 @@ export class ImageStorage implements IDisposable { this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y); // cursor positioning modes - if (this._opts.sixelScrolling) { + if (scrolling) { buffer.x = offset; } else { buffer.x = originX; @@ -331,6 +317,7 @@ export class ImageStorage implements IDisposable { // finally add the image this._images.set(imageId, imgSpec); + return imageId; } diff --git a/addons/addon-image/src/SixelHandler.ts b/addons/addon-image/src/SixelHandler.ts index 07d90341..2f12fbe1 100644 --- a/addons/addon-image/src/SixelHandler.ts +++ b/addons/addon-image/src/SixelHandler.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ImageStorage } from './ImageStorage'; +import { SixelImageStorage } from './SixelImageStorage'; import { IDcsHandler, IParams, IImageAddonOptions, ITerminalExt, AttributeData, IResetHandler, ReadonlyColorSet } from './Types'; import { toRGBA8888, BIG_ENDIAN, PALETTE_ANSI_256, PALETTE_VT340_COLOR } from 'sixel/lib/Colors'; import { RGBA8888 } from 'sixel/lib/Types'; @@ -26,7 +26,7 @@ export class SixelHandler implements IDcsHandler, IResetHandler { constructor( private readonly _opts: IImageAddonOptions, - private readonly _storage: ImageStorage, + private readonly _storage: SixelImageStorage, private readonly _coreTerminal: ITerminalExt ) { DecoderAsync({ diff --git a/addons/addon-image/src/SixelImageStorage.ts b/addons/addon-image/src/SixelImageStorage.ts new file mode 100644 index 00000000..4c5f555b --- /dev/null +++ b/addons/addon-image/src/SixelImageStorage.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2020 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage'; +import { IImageAddonOptions, ITerminalExt } from './Types'; +import { ImageRenderer } from './ImageRenderer'; + +/** + * Sixel-specific image storage controller. + * + * Wraps the shared ImageStorage with sixel protocol semantics: + * - Cursor behavior governed by DECSET 80 (sixelScrolling option) + * - advanceCursor for empty sixels carrying only height + */ +export class SixelImageStorage { + constructor( + private readonly _storage: ImageStorage, + private readonly _opts: IImageAddonOptions, + private readonly _renderer: ImageRenderer, + private readonly _terminal: ITerminalExt + ) {} + + /** + * Add a sixel image to storage. + * Cursor behavior depends on the sixelScrolling option (DECSET 80). + */ + public addImage(img: HTMLCanvasElement | ImageBitmap): void { + this._storage.addImage(img, this._opts.sixelScrolling); + } + + /** + * Only advance text cursor. + * This is an edge case from empty sixels carrying only a height but no pixels. + * Partially fixes https://github.com/jerch/xterm-addon-image/issues/37. + */ + public advanceCursor(height: number): void { + if (this._opts.sixelScrolling) { + let cellSize = this._renderer.cellSize; + if (cellSize.width === -1 || cellSize.height === -1) { + cellSize = CELL_SIZE_DEFAULT; + } + const rows = Math.ceil(height / cellSize.height); + for (let i = 1; i < rows; ++i) { + this._terminal._core._inputHandler.lineFeed(); + } + } + } +}