@@ -18,12 +18,9 @@ const PLACEHOLDER_HEIGHT = 24;
1818 * - draw image tiles onRender
1919 */
2020export class ImageRenderer extends Disposable implements IDisposable {
21- /** @deprecated Use canvasTop instead. Kept for backward compat — points to canvasTop. */
22- public get canvas ( ) : HTMLCanvasElement | undefined { return this . _canvasTop ; }
23- private _canvasTop : HTMLCanvasElement | undefined ;
24- private _canvasBottom : HTMLCanvasElement | undefined ;
25- private _ctxTop : CanvasRenderingContext2D | null | undefined ;
26- private _ctxBottom : 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 > ( ) ;
2724 private _placeholder : HTMLCanvasElement | undefined ;
2825 private _placeholderBitmap : ImageBitmap | undefined ;
2926 private _optionsRefresh = this . _register ( new MutableDisposable ( ) ) ;
@@ -100,10 +97,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
10097 this . _oldSetRenderer = undefined ;
10198 }
10299 this . _renderService = undefined ;
103- this . _canvasTop = undefined ;
104- this . _canvasBottom = undefined ;
105- this . _ctxTop = undefined ;
106- this . _ctxBottom = undefined ;
100+ this . _layers . clear ( ) ;
107101 this . _placeholderBitmap ?. close ( ) ;
108102 this . _placeholderBitmap = undefined ;
109103 this . _placeholder = undefined ;
@@ -152,10 +146,10 @@ export class ImageRenderer extends Disposable implements IDisposable {
152146 const w = this . dimensions ?. css . canvas . width || 0 ;
153147 const h = ( ++ end - start ) * ( this . dimensions ?. css . cell . height || 0 ) ;
154148 if ( ! layer || layer === 'top' ) {
155- this . _ctxTop ?. clearRect ( 0 , y , w , h ) ;
149+ this . _layers . get ( 'top' ) ?. clearRect ( 0 , y , w , h ) ;
156150 }
157151 if ( ! layer || layer === 'bottom' ) {
158- this . _ctxBottom ?. clearRect ( 0 , y , w , h ) ;
152+ this . _layers . get ( 'bottom' ) ?. clearRect ( 0 , y , w , h ) ;
159153 }
160154 }
161155
@@ -164,18 +158,20 @@ export class ImageRenderer extends Disposable implements IDisposable {
164158 */
165159 public clearAll ( layer ?: ImageLayer ) : void {
166160 if ( ! layer || layer === 'top' ) {
167- this . _ctxTop ?. clearRect ( 0 , 0 , this . _canvasTop ?. width || 0 , this . _canvasTop ?. height || 0 ) ;
161+ const ctx = this . _layers . get ( 'top' ) ;
162+ ctx ?. clearRect ( 0 , 0 , ctx . canvas . width , ctx . canvas . height ) ;
168163 }
169164 if ( ! layer || layer === 'bottom' ) {
170- this . _ctxBottom ?. clearRect ( 0 , 0 , this . _canvasBottom ?. width || 0 , this . _canvasBottom ?. height || 0 ) ;
165+ const ctx = this . _layers . get ( 'bottom' ) ;
166+ ctx ?. clearRect ( 0 , 0 , ctx . canvas . width , ctx . canvas . height ) ;
171167 }
172168 }
173169
174170 /**
175171 * Draw neighboring tiles on the image layer canvas.
176172 */
177173 public draw ( imgSpec : IImageSpec , tileId : number , col : number , row : number , count : number = 1 ) : void {
178- const ctx = imgSpec . layer === 'bottom' ? this . _ctxBottom : this . _ctxTop ;
174+ const ctx = this . _layers . get ( imgSpec . layer ) ;
179175 if ( ! ctx ) {
180176 return ;
181177 }
@@ -243,7 +239,8 @@ export class ImageRenderer extends Disposable implements IDisposable {
243239 * Draw a line with placeholder on the image layer canvas.
244240 */
245241 public drawPlaceholder ( col : number , row : number , count : number = 1 ) : void {
246- if ( this . _ctxTop ) {
242+ const ctx = this . _layers . get ( 'top' ) ;
243+ if ( ctx ) {
247244 const { width, height } = this . cellSize ;
248245
249246 // Don't try to draw anything, if we cannot get valid renderer metrics.
@@ -257,7 +254,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
257254 this . _createPlaceHolder ( height + 1 ) ;
258255 }
259256 if ( ! this . _placeholder ) return ;
260- this . _ctxTop . drawImage (
257+ ctx . drawImage (
261258 this . _placeholderBitmap ?? this . _placeholder ! ,
262259 col * width ,
263260 ( row * height ) % 2 ? 0 : 1 , // needs %2 offset correction
@@ -278,13 +275,11 @@ export class ImageRenderer extends Disposable implements IDisposable {
278275 public rescaleCanvas ( ) : void {
279276 const w = this . dimensions ?. css . canvas . width || 0 ;
280277 const h = this . dimensions ?. css . canvas . height || 0 ;
281- if ( this . _canvasTop && ( this . _canvasTop . width !== w || this . _canvasTop . height !== h ) ) {
282- this . _canvasTop . width = w ;
283- this . _canvasTop . height = h ;
284- }
285- if ( this . _canvasBottom && ( this . _canvasBottom . width !== w || this . _canvasBottom . height !== h ) ) {
286- this . _canvasBottom . width = w ;
287- this . _canvasBottom . height = h ;
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+ }
288283 }
289284 }
290285
@@ -323,61 +318,57 @@ export class ImageRenderer extends Disposable implements IDisposable {
323318 this . _renderService = this . _terminal . _core . _renderService ;
324319 this . _oldSetRenderer = this . _renderService . setRenderer . bind ( this . _renderService ) ;
325320 this . _renderService . setRenderer = ( renderer : any ) => {
326- this . removeLayerFromDom ( ) ;
327- this . removeLayerFromDom ( 'bottom' ) ;
321+ for ( const key of [ ...this . _layers . keys ( ) ] ) {
322+ this . removeLayerFromDom ( key ) ;
323+ }
328324 this . _oldSetRenderer ?. call ( this . _renderService , renderer ) ;
329325 } ;
330326 }
331327
332328 public insertLayerToDom ( layer : ImageLayer = 'top' ) : void {
333329 // make sure that the terminal is attached to a document and to DOM
334- if ( this . document && this . _terminal . _core . screenElement ) {
335- if ( layer === 'top' && ! this . _canvasTop ) {
336- this . _canvasTop = ImageRenderer . createCanvas (
337- this . document , this . dimensions ?. css . canvas . width || 0 ,
338- this . dimensions ?. css . canvas . height || 0
339- ) ;
340- this . _canvasTop . classList . add ( 'xterm-image-layer-top' ) ;
341- this . _terminal . _core . screenElement . appendChild ( this . _canvasTop ) ;
342- this . _ctxTop = this . _canvasTop . getContext ( '2d' , { alpha : true , desynchronized : true } ) ;
343- this . clearAll ( 'top' ) ;
344- }
345- if ( layer === 'bottom' && ! this . _canvasBottom ) {
346- this . _canvasBottom = ImageRenderer . createCanvas (
347- this . document , this . dimensions ?. css . canvas . width || 0 ,
348- this . dimensions ?. css . canvas . height || 0
349- ) ;
350- this . _canvasBottom . classList . add ( 'xterm-image-layer-bottom' ) ;
351- // Use z-index:-1 so it paints behind non-positioned text elements.
352- // The screen element needs to be a stacking context to contain the
353- // negative z-index, otherwise it would go behind the entire terminal.
354- this . _canvasBottom . style . zIndex = '-1' ;
355- const screenElement = this . _terminal . _core . screenElement ;
356- screenElement . style . zIndex = '0' ;
357- screenElement . insertBefore ( this . _canvasBottom , screenElement . firstChild ) ;
358- this . _ctxBottom = this . _canvasBottom . getContext ( '2d' , { alpha : true , desynchronized : true } ) ;
359- this . clearAll ( 'bottom' ) ;
360- }
361- } else {
330+ if ( ! this . document || ! this . _terminal . _core . screenElement ) {
362331 console . warn ( 'image addon: cannot insert output canvas to DOM, missing document or screenElement' ) ;
332+ return ;
363333 }
334+ if ( this . _layers . has ( layer ) ) {
335+ return ;
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+ screenElement . appendChild ( canvas ) ;
352+ }
353+ const ctx = canvas . getContext ( '2d' , { alpha : true , desynchronized : true } ) ;
354+ if ( ! ctx ) {
355+ canvas . remove ( ) ;
356+ return ;
357+ }
358+ this . _layers . set ( layer , ctx ) ;
359+ this . clearAll ( layer ) ;
364360 }
365361
366362 public removeLayerFromDom ( layer : ImageLayer = 'top' ) : void {
367- if ( layer === 'top' && this . _canvasTop ) {
368- this . _ctxTop = undefined ;
369- this . _canvasTop . remove ( ) ;
370- this . _canvasTop = undefined ;
371- }
372- if ( layer === 'bottom' && this . _canvasBottom ) {
373- this . _ctxBottom = undefined ;
374- this . _canvasBottom . remove ( ) ;
375- this . _canvasBottom = undefined ;
363+ const ctx = this . _layers . get ( layer ) ;
364+ if ( ctx ) {
365+ ctx . canvas . remove ( ) ;
366+ this . _layers . delete ( layer ) ;
376367 }
377368 }
378369
379370 public hasLayer ( layer : ImageLayer ) : boolean {
380- return layer === 'top' ? ! ! this . _canvasTop : ! ! this . _canvasBottom ;
371+ return this . _layers . has ( layer ) ;
381372 }
382373
383374 private _createPlaceHolder ( height : number = PLACEHOLDER_HEIGHT ) : void {
0 commit comments