@xterm/addon-image 0.10.0-beta.3 → 0.10.0-beta.300
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/README.md +40 -42
- package/lib/addon-image.js +1 -1
- package/lib/addon-image.js.map +1 -1
- package/lib/addon-image.mjs +1 -22
- package/lib/addon-image.mjs.map +4 -4
- package/package.json +5 -5
- package/src/IIPHandler.ts +124 -47
- package/src/IIPHeaderParser.ts +90 -14
- package/src/IIPImageStorage.ts +28 -0
- package/src/IIPMetrics.ts +71 -5
- package/src/ImageAddon.ts +74 -13
- package/src/ImageRenderer.ts +99 -53
- package/src/ImageStorage.ts +103 -39
- package/src/SixelHandler.ts +4 -4
- package/src/SixelImageStorage.ts +52 -0
- package/src/Types.ts +18 -3
- package/src/kitty/KittyGraphicsHandler.ts +819 -0
- package/src/kitty/KittyGraphicsTypes.ts +195 -0
- package/src/kitty/KittyImageStorage.ts +155 -0
- package/typings/addon-image.d.ts +15 -1
package/src/ImageAddon.ts
CHANGED
|
@@ -5,24 +5,72 @@
|
|
|
5
5
|
|
|
6
6
|
import type { ITerminalAddon, IDisposable } from '@xterm/xterm';
|
|
7
7
|
import type { ImageAddon as IImageApi } from '@xterm/addon-image';
|
|
8
|
+
import { Emitter, type IEvent } from 'common/Event';
|
|
8
9
|
import { IIPHandler } from './IIPHandler';
|
|
9
10
|
import { ImageRenderer } from './ImageRenderer';
|
|
10
11
|
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
|
|
12
|
+
import { KittyGraphicsHandler } from './kitty/KittyGraphicsHandler';
|
|
13
|
+
import { KittyImageStorage } from './kitty/KittyImageStorage';
|
|
11
14
|
import { SixelHandler } from './SixelHandler';
|
|
15
|
+
import { SixelImageStorage } from './SixelImageStorage';
|
|
16
|
+
import { IIPImageStorage } from './IIPImageStorage';
|
|
12
17
|
import { ITerminalExt, IImageAddonOptions, IResetHandler } from './Types';
|
|
13
18
|
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Document VT features provided by this addon.
|
|
22
|
+
*
|
|
23
|
+
* @vt: #E[Supported via @xterm/addon-image.] DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image."
|
|
24
|
+
*
|
|
25
|
+
* Sixel support is provided by the addon @xterm/addon-image with these limitations:
|
|
26
|
+
* - immediate coloring (no shared palette, allows high color settings of `img2sixel`)
|
|
27
|
+
* - max. palette size of 4096 colors
|
|
28
|
+
* - max. pixel width of 16K
|
|
29
|
+
* - max. 25 MB per sixel sequence
|
|
30
|
+
* - VT340 cursor positioning (begin of last sixel data row)
|
|
31
|
+
*
|
|
32
|
+
* See [addon readme](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image) for more details.
|
|
33
|
+
*
|
|
34
|
+
*
|
|
35
|
+
* @vt: #E[Supported via @xterm/addon-image.] OSC 1337 "iTerm2 Commands" "OSC 1337 ; Pt BEL" "Custom iTerm2 commands."
|
|
36
|
+
*
|
|
37
|
+
* Only the inline image protocol (IIP) is supported by the addon @xterm/addon-image with
|
|
38
|
+
* the following limitations:
|
|
39
|
+
* - sequence:
|
|
40
|
+
* - format: `OSC 1337 ; File=inline=1 ; size=<unencoded size> ; ... : <base64 payload> BEL`
|
|
41
|
+
* - size param must be set and payload may not exceed CEIL(size * 4 / 3)
|
|
42
|
+
* - strict base64 handling as of RFC4648 §4 (standard alphabet, optional padding,
|
|
43
|
+
* no separator bytes allowed)
|
|
44
|
+
* - supported params: size, name, width, height, preserveAspectRatio
|
|
45
|
+
* - image formats: PNG, JPEG and GIF
|
|
46
|
+
* - no animation support (renders first image of a GIF)
|
|
47
|
+
* - no multipart support
|
|
48
|
+
* - VT340 cursor positioning (begin of last sixel data row)
|
|
49
|
+
*
|
|
50
|
+
* See [addon readme](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image)
|
|
51
|
+
* and [iTerm2 IIP docs](https://iterm2.com/documentation-images.html) for more details.
|
|
52
|
+
*
|
|
53
|
+
*
|
|
54
|
+
* @vt: #E[Supported via @xterm/addon-image.] APC KITTY_GRAPHICS "Kitty Graphics" "APC G Pt ST" "Kitty Graphics Protocol."
|
|
55
|
+
*
|
|
56
|
+
* Kitty graphics support is provided by the addon @xterm/addon-image.
|
|
57
|
+
* Note that while basic image output already works, this is still work in progress.
|
|
58
|
+
*/
|
|
59
|
+
|
|
14
60
|
// default values of addon ctor options
|
|
15
61
|
const DEFAULT_OPTIONS: IImageAddonOptions = {
|
|
16
62
|
enableSizeReports: true,
|
|
17
63
|
pixelLimit: 16777216, // limit to 4096 * 4096 pixels
|
|
18
64
|
sixelSupport: true,
|
|
19
65
|
sixelScrolling: true,
|
|
20
|
-
sixelPaletteLimit:
|
|
21
|
-
sixelSizeLimit:
|
|
66
|
+
sixelPaletteLimit: 4096,
|
|
67
|
+
sixelSizeLimit: 33554432,
|
|
22
68
|
storageLimit: 128,
|
|
23
69
|
showPlaceholder: true,
|
|
24
70
|
iipSupport: true,
|
|
25
|
-
iipSizeLimit:
|
|
71
|
+
iipSizeLimit: 33554432,
|
|
72
|
+
kittySupport: true,
|
|
73
|
+
kittySizeLimit: 33554432
|
|
26
74
|
};
|
|
27
75
|
|
|
28
76
|
// max palette size supported by the sixel lib (compile time setting)
|
|
@@ -48,7 +96,7 @@ const enum GaStatus {
|
|
|
48
96
|
}
|
|
49
97
|
|
|
50
98
|
|
|
51
|
-
export class ImageAddon implements ITerminalAddon
|
|
99
|
+
export class ImageAddon implements ITerminalAddon, IImageApi {
|
|
52
100
|
private _opts: IImageAddonOptions;
|
|
53
101
|
private _defaultOpts: IImageAddonOptions;
|
|
54
102
|
private _storage: ImageStorage | undefined;
|
|
@@ -56,6 +104,8 @@ export class ImageAddon implements ITerminalAddon , IImageApi {
|
|
|
56
104
|
private _disposables: IDisposable[] = [];
|
|
57
105
|
private _terminal: ITerminalExt | undefined;
|
|
58
106
|
private _handlers: Map<String, IResetHandler> = new Map();
|
|
107
|
+
private readonly _onImageAdded = new Emitter<void>();
|
|
108
|
+
public readonly onImageAdded: IEvent<void> = this._onImageAdded.event;
|
|
59
109
|
|
|
60
110
|
constructor(opts?: Partial<IImageAddonOptions>) {
|
|
61
111
|
this._opts = Object.assign({}, DEFAULT_OPTIONS, opts);
|
|
@@ -68,6 +118,7 @@ export class ImageAddon implements ITerminalAddon , IImageApi {
|
|
|
68
118
|
}
|
|
69
119
|
this._disposables.length = 0;
|
|
70
120
|
this._handlers.clear();
|
|
121
|
+
this._onImageAdded.dispose();
|
|
71
122
|
}
|
|
72
123
|
|
|
73
124
|
private _disposeLater(...args: IDisposable[]): void {
|
|
@@ -82,15 +133,11 @@ export class ImageAddon implements ITerminalAddon , IImageApi {
|
|
|
82
133
|
// internal data structures
|
|
83
134
|
this._renderer = new ImageRenderer(terminal);
|
|
84
135
|
this._storage = new ImageStorage(terminal, this._renderer, this._opts);
|
|
136
|
+
this._storage.onImageAdded = () => this._onImageAdded.fire();
|
|
85
137
|
|
|
86
138
|
// enable size reports
|
|
87
139
|
if (this._opts.enableSizeReports) {
|
|
88
|
-
|
|
89
|
-
// windowOptions.getWinSizePixels = true;
|
|
90
|
-
// windowOptions.getCellSizePixels = true;
|
|
91
|
-
// windowOptions.getWinSizeChars = true;
|
|
92
|
-
// terminal.setOption('windowOptions', windowOptions);
|
|
93
|
-
const windowOps = terminal.options.windowOptions || {};
|
|
140
|
+
const windowOps = terminal.options.windowOptions ?? {};
|
|
94
141
|
windowOps.getWinSizePixels = true;
|
|
95
142
|
windowOps.getCellSizePixels = true;
|
|
96
143
|
windowOps.getWinSizeChars = true;
|
|
@@ -129,7 +176,8 @@ export class ImageAddon implements ITerminalAddon , IImageApi {
|
|
|
129
176
|
|
|
130
177
|
// SIXEL handler
|
|
131
178
|
if (this._opts.sixelSupport) {
|
|
132
|
-
const
|
|
179
|
+
const sixelStorage = new SixelImageStorage(this._storage!, this._opts, this._renderer!, terminal);
|
|
180
|
+
const sixelHandler = new SixelHandler(this._opts, sixelStorage, terminal);
|
|
133
181
|
this._handlers.set('sixel', sixelHandler);
|
|
134
182
|
this._disposeLater(
|
|
135
183
|
terminal._core._inputHandler._parser.registerDcsHandler({ final: 'q' }, sixelHandler)
|
|
@@ -138,12 +186,25 @@ export class ImageAddon implements ITerminalAddon , IImageApi {
|
|
|
138
186
|
|
|
139
187
|
// iTerm IIP handler
|
|
140
188
|
if (this._opts.iipSupport) {
|
|
141
|
-
const
|
|
189
|
+
const iipStorage = new IIPImageStorage(this._storage!);
|
|
190
|
+
const iipHandler = new IIPHandler(this._opts, this._renderer!, iipStorage, terminal);
|
|
142
191
|
this._handlers.set('iip', iipHandler);
|
|
143
192
|
this._disposeLater(
|
|
144
193
|
terminal._core._inputHandler._parser.registerOscHandler(1337, iipHandler)
|
|
145
194
|
);
|
|
146
195
|
}
|
|
196
|
+
|
|
197
|
+
// Kitty graphics handler
|
|
198
|
+
if (this._opts.kittySupport) {
|
|
199
|
+
const kittyStorage = new KittyImageStorage(this._storage!);
|
|
200
|
+
const kittyHandler = new KittyGraphicsHandler(this._opts, this._renderer!, kittyStorage, terminal);
|
|
201
|
+
this._handlers.set('kitty', kittyHandler);
|
|
202
|
+
this._disposeLater(
|
|
203
|
+
kittyStorage,
|
|
204
|
+
kittyHandler,
|
|
205
|
+
terminal._core._inputHandler._parser.registerApcHandler({ final: 'G' }, kittyHandler)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
147
208
|
}
|
|
148
209
|
|
|
149
210
|
// Note: storageLimit is skipped here to not intoduce a surprising side effect.
|
|
@@ -194,7 +255,7 @@ export class ImageAddon implements ITerminalAddon , IImageApi {
|
|
|
194
255
|
}
|
|
195
256
|
|
|
196
257
|
private _report(s: string): void {
|
|
197
|
-
this._terminal?._core.
|
|
258
|
+
this._terminal?._core.input(s, false);
|
|
198
259
|
}
|
|
199
260
|
|
|
200
261
|
private _decset(params: (number | number[])[]): boolean {
|
package/src/ImageRenderer.ts
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
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';
|
|
9
|
-
import { Disposable, MutableDisposable, toDisposable } from '
|
|
8
|
+
import { ICellSize, ImageLayer, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
|
|
9
|
+
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
|
|
11
|
+
const enum Constants {
|
|
12
|
+
PLACEHOLDER_LENGTH = 4096,
|
|
13
|
+
PLACEHOLDER_HEIGHT = 24
|
|
14
|
+
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* ImageRenderer - terminal frontend extension:
|
|
@@ -18,8 +20,9 @@ const PLACEHOLDER_HEIGHT = 24;
|
|
|
18
20
|
* - draw image tiles onRender
|
|
19
21
|
*/
|
|
20
22
|
export class ImageRenderer extends Disposable implements IDisposable {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
/** @deprecated Kept for backward compat — points to top layer canvas. */
|
|
24
|
+
public get canvas(): HTMLCanvasElement | undefined { return this._layers.get('top')?.canvas; }
|
|
25
|
+
private _layers = new Map<ImageLayer, CanvasRenderingContext2D>();
|
|
23
26
|
private _placeholder: HTMLCanvasElement | undefined;
|
|
24
27
|
private _placeholderBitmap: ImageBitmap | undefined;
|
|
25
28
|
private _optionsRefresh = this._register(new MutableDisposable());
|
|
@@ -38,7 +41,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
38
41
|
* Only the DOM output canvas should be on the terminal's document,
|
|
39
42
|
* which gets explicitly checked in `insertLayerToDom`.
|
|
40
43
|
*/
|
|
41
|
-
const canvas = (localDocument
|
|
44
|
+
const canvas = (localDocument ?? document).createElement('canvas');
|
|
42
45
|
canvas.width = width | 0;
|
|
43
46
|
canvas.height = height | 0;
|
|
44
47
|
return canvas;
|
|
@@ -86,6 +89,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
86
89
|
});
|
|
87
90
|
this._register(toDisposable(() => {
|
|
88
91
|
this.removeLayerFromDom();
|
|
92
|
+
this.removeLayerFromDom('bottom');
|
|
89
93
|
if (this._terminal._core && this._oldOpen) {
|
|
90
94
|
this._terminal._core.open = this._oldOpen;
|
|
91
95
|
this._oldOpen = undefined;
|
|
@@ -95,8 +99,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
95
99
|
this._oldSetRenderer = undefined;
|
|
96
100
|
}
|
|
97
101
|
this._renderService = undefined;
|
|
98
|
-
this.
|
|
99
|
-
this._ctx = undefined;
|
|
102
|
+
this._layers.clear();
|
|
100
103
|
this._placeholderBitmap?.close();
|
|
101
104
|
this._placeholderBitmap = undefined;
|
|
102
105
|
this._placeholder = undefined;
|
|
@@ -109,7 +112,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
109
112
|
public showPlaceholder(value: boolean): void {
|
|
110
113
|
if (value) {
|
|
111
114
|
if (!this._placeholder && this.cellSize.height !== -1) {
|
|
112
|
-
this._createPlaceHolder(Math.max(this.cellSize.height + 1, PLACEHOLDER_HEIGHT));
|
|
115
|
+
this._createPlaceHolder(Math.max(this.cellSize.height + 1, Constants.PLACEHOLDER_HEIGHT));
|
|
113
116
|
}
|
|
114
117
|
} else {
|
|
115
118
|
this._placeholderBitmap?.close();
|
|
@@ -124,7 +127,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
124
127
|
* Forwarded from internal render service.
|
|
125
128
|
*/
|
|
126
129
|
public get dimensions(): IRenderDimensions | undefined {
|
|
127
|
-
return this.
|
|
130
|
+
return this._terminal.dimensions;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
/**
|
|
@@ -140,27 +143,38 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
140
143
|
/**
|
|
141
144
|
* Clear a region of the image layer canvas.
|
|
142
145
|
*/
|
|
143
|
-
public clearLines(start: number, end: number): void {
|
|
144
|
-
this.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
(
|
|
149
|
-
|
|
146
|
+
public clearLines(start: number, end: number, layer?: ImageLayer): void {
|
|
147
|
+
const y = start * (this.dimensions?.css.cell.height || 0);
|
|
148
|
+
const w = this.dimensions?.css.canvas.width || 0;
|
|
149
|
+
const h = (end + 1 - start) * (this.dimensions?.css.cell.height || 0);
|
|
150
|
+
if (!layer || layer === 'top') {
|
|
151
|
+
this._layers.get('top')?.clearRect(0, y, w, h);
|
|
152
|
+
}
|
|
153
|
+
if (!layer || layer === 'bottom') {
|
|
154
|
+
this._layers.get('bottom')?.clearRect(0, y, w, h);
|
|
155
|
+
}
|
|
150
156
|
}
|
|
151
157
|
|
|
152
158
|
/**
|
|
153
159
|
* Clear whole image canvas.
|
|
154
160
|
*/
|
|
155
|
-
public clearAll(): void {
|
|
156
|
-
|
|
161
|
+
public clearAll(layer?: ImageLayer): void {
|
|
162
|
+
if (!layer || layer === 'top') {
|
|
163
|
+
const ctx = this._layers.get('top');
|
|
164
|
+
ctx?.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
165
|
+
}
|
|
166
|
+
if (!layer || layer === 'bottom') {
|
|
167
|
+
const ctx = this._layers.get('bottom');
|
|
168
|
+
ctx?.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
169
|
+
}
|
|
157
170
|
}
|
|
158
171
|
|
|
159
172
|
/**
|
|
160
173
|
* Draw neighboring tiles on the image layer canvas.
|
|
161
174
|
*/
|
|
162
175
|
public draw(imgSpec: IImageSpec, tileId: number, col: number, row: number, count: number = 1): void {
|
|
163
|
-
|
|
176
|
+
const ctx = this._layers.get(imgSpec.layer);
|
|
177
|
+
if (!ctx) {
|
|
164
178
|
return;
|
|
165
179
|
}
|
|
166
180
|
const { width, height } = this.cellSize;
|
|
@@ -187,7 +201,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
187
201
|
// Note: For not pixel perfect aligned cells like in the DOM renderer
|
|
188
202
|
// this will move a tile slightly to the top/left (subpixel range, thus ignore it).
|
|
189
203
|
// FIX #34: avoid striping on displays with pixelDeviceRatio != 1 by ceiling height and width
|
|
190
|
-
|
|
204
|
+
ctx.drawImage(
|
|
191
205
|
img,
|
|
192
206
|
Math.floor(sx), Math.floor(sy), Math.ceil(finalWidth), Math.ceil(finalHeight),
|
|
193
207
|
Math.floor(dx), Math.floor(dy), Math.ceil(finalWidth), Math.ceil(finalHeight)
|
|
@@ -227,7 +241,8 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
227
241
|
* Draw a line with placeholder on the image layer canvas.
|
|
228
242
|
*/
|
|
229
243
|
public drawPlaceholder(col: number, row: number, count: number = 1): void {
|
|
230
|
-
|
|
244
|
+
const ctx = this._layers.get('top');
|
|
245
|
+
if (ctx) {
|
|
231
246
|
const { width, height } = this.cellSize;
|
|
232
247
|
|
|
233
248
|
// Don't try to draw anything, if we cannot get valid renderer metrics.
|
|
@@ -236,13 +251,13 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
236
251
|
}
|
|
237
252
|
|
|
238
253
|
if (!this._placeholder) {
|
|
239
|
-
this._createPlaceHolder(Math.max(height + 1, PLACEHOLDER_HEIGHT));
|
|
254
|
+
this._createPlaceHolder(Math.max(height + 1, Constants.PLACEHOLDER_HEIGHT));
|
|
240
255
|
} else if (height >= this._placeholder!.height) {
|
|
241
256
|
this._createPlaceHolder(height + 1);
|
|
242
257
|
}
|
|
243
258
|
if (!this._placeholder) return;
|
|
244
|
-
|
|
245
|
-
this._placeholderBitmap
|
|
259
|
+
ctx.drawImage(
|
|
260
|
+
this._placeholderBitmap ?? this._placeholder!,
|
|
246
261
|
col * width,
|
|
247
262
|
(row * height) % 2 ? 0 : 1, // needs %2 offset correction
|
|
248
263
|
width * count,
|
|
@@ -260,12 +275,13 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
260
275
|
* Checked once from `ImageStorage.render`.
|
|
261
276
|
*/
|
|
262
277
|
public rescaleCanvas(): void {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
278
|
+
const w = this.dimensions?.css.canvas.width || 0;
|
|
279
|
+
const h = this.dimensions?.css.canvas.height || 0;
|
|
280
|
+
for (const ctx of this._layers.values()) {
|
|
281
|
+
if (ctx.canvas.width !== w || ctx.canvas.height !== h) {
|
|
282
|
+
ctx.canvas.width = w;
|
|
283
|
+
ctx.canvas.height = h;
|
|
284
|
+
}
|
|
269
285
|
}
|
|
270
286
|
}
|
|
271
287
|
|
|
@@ -304,38 +320,68 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
304
320
|
this._renderService = this._terminal._core._renderService;
|
|
305
321
|
this._oldSetRenderer = this._renderService.setRenderer.bind(this._renderService);
|
|
306
322
|
this._renderService.setRenderer = (renderer: any) => {
|
|
307
|
-
this.
|
|
323
|
+
for (const key of [...this._layers.keys()]) {
|
|
324
|
+
this.removeLayerFromDom(key);
|
|
325
|
+
}
|
|
308
326
|
this._oldSetRenderer?.call(this._renderService, renderer);
|
|
309
327
|
};
|
|
310
328
|
}
|
|
311
329
|
|
|
312
|
-
public insertLayerToDom(): void {
|
|
330
|
+
public insertLayerToDom(layer: ImageLayer = 'top'): void {
|
|
313
331
|
// make sure that the terminal is attached to a document and to DOM
|
|
314
|
-
if (this.document
|
|
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 {
|
|
332
|
+
if (!this.document || !this._terminal._core.screenElement) {
|
|
326
333
|
console.warn('image addon: cannot insert output canvas to DOM, missing document or screenElement');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (this._layers.has(layer)) {
|
|
337
|
+
return;
|
|
327
338
|
}
|
|
339
|
+
const canvas = ImageRenderer.createCanvas(
|
|
340
|
+
this.document, this.dimensions?.css.canvas.width || 0,
|
|
341
|
+
this.dimensions?.css.canvas.height || 0
|
|
342
|
+
);
|
|
343
|
+
canvas.classList.add(`xterm-image-layer-${layer}`);
|
|
344
|
+
const screenElement = this._terminal._core.screenElement;
|
|
345
|
+
// Use isolation to create a stacking context without overriding z-index,
|
|
346
|
+
// which would conflict with integrators (e.g. VS Code) that set their
|
|
347
|
+
// own z-index on the screen element.
|
|
348
|
+
screenElement.style.isolation = 'isolate';
|
|
349
|
+
if (layer === 'bottom') {
|
|
350
|
+
// Use z-index:-1 so it paints behind non-positioned text elements.
|
|
351
|
+
// The screen element needs to be a stacking context (via isolation)
|
|
352
|
+
// to contain the negative z-index, otherwise it would go behind the
|
|
353
|
+
// entire terminal.
|
|
354
|
+
canvas.style.zIndex = '-1';
|
|
355
|
+
screenElement.insertBefore(canvas, screenElement.firstChild);
|
|
356
|
+
} else {
|
|
357
|
+
// Explicit z-index ensures the image canvas reliably stacks above
|
|
358
|
+
// the text layer (DOM renderer rows). z-index: 0 is below the
|
|
359
|
+
// selection overlay (z-index: 1).
|
|
360
|
+
canvas.style.zIndex = '0';
|
|
361
|
+
screenElement.appendChild(canvas);
|
|
362
|
+
}
|
|
363
|
+
const ctx = canvas.getContext('2d', { alpha: true });
|
|
364
|
+
if (!ctx) {
|
|
365
|
+
canvas.remove();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
this._layers.set(layer, ctx);
|
|
369
|
+
this.clearAll(layer);
|
|
328
370
|
}
|
|
329
371
|
|
|
330
|
-
public removeLayerFromDom(): void {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
this.
|
|
372
|
+
public removeLayerFromDom(layer: ImageLayer = 'top'): void {
|
|
373
|
+
const ctx = this._layers.get(layer);
|
|
374
|
+
if (ctx) {
|
|
375
|
+
ctx.canvas.remove();
|
|
376
|
+
this._layers.delete(layer);
|
|
335
377
|
}
|
|
336
378
|
}
|
|
337
379
|
|
|
338
|
-
|
|
380
|
+
public hasLayer(layer: ImageLayer): boolean {
|
|
381
|
+
return this._layers.has(layer);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private _createPlaceHolder(height: number = Constants.PLACEHOLDER_HEIGHT): void {
|
|
339
385
|
this._placeholderBitmap?.close();
|
|
340
386
|
this._placeholderBitmap = undefined;
|
|
341
387
|
|
|
@@ -359,7 +405,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|
|
359
405
|
ctx.putImageData(imgData, 0, 0);
|
|
360
406
|
|
|
361
407
|
// create placeholder line, width aligned to blueprint width
|
|
362
|
-
const width = (screen.width + bWidth - 1) & ~(bWidth - 1) || PLACEHOLDER_LENGTH;
|
|
408
|
+
const width = (screen.width + bWidth - 1) & ~(bWidth - 1) || Constants.PLACEHOLDER_LENGTH;
|
|
363
409
|
this._placeholder = ImageRenderer.createCanvas(this.document, width, height);
|
|
364
410
|
const ctx2 = this._placeholder.getContext('2d', { alpha: false });
|
|
365
411
|
if (!ctx2) {
|