55
66import { toRGBA8888 } from 'sixel/lib/Colors' ;
77import { IDisposable } from '@xterm/xterm' ;
8- import { ICellSize , ITerminalExt , IImageSpec , IRenderDimensions , IRenderService } from './Types' ;
8+ import { ICellSize , ImageLayer , ITerminalExt , IImageSpec , IRenderDimensions , IRenderService } from './Types' ;
99import { Disposable , MutableDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
1010
1111const PLACEHOLDER_LENGTH = 4096 ;
@@ -18,8 +18,12 @@ const PLACEHOLDER_HEIGHT = 24;
1818 * - draw image tiles onRender
1919 */
2020export class ImageRenderer extends Disposable implements IDisposable {
21- public canvas : HTMLCanvasElement | undefined ;
22- private _ctx : CanvasRenderingContext2D | null | undefined ;
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 ;
2327 private _placeholder : HTMLCanvasElement | undefined ;
2428 private _placeholderBitmap : ImageBitmap | undefined ;
2529 private _optionsRefresh = this . _register ( new MutableDisposable ( ) ) ;
@@ -86,6 +90,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
8690 } ) ;
8791 this . _register ( toDisposable ( ( ) => {
8892 this . removeLayerFromDom ( ) ;
93+ this . removeLayerFromDom ( 'bottom' ) ;
8994 if ( this . _terminal . _core && this . _oldOpen ) {
9095 this . _terminal . _core . open = this . _oldOpen ;
9196 this . _oldOpen = undefined ;
@@ -95,8 +100,10 @@ export class ImageRenderer extends Disposable implements IDisposable {
95100 this . _oldSetRenderer = undefined ;
96101 }
97102 this . _renderService = undefined ;
98- this . canvas = undefined ;
99- this . _ctx = undefined ;
103+ this . _canvasTop = undefined ;
104+ this . _canvasBottom = undefined ;
105+ this . _ctxTop = undefined ;
106+ this . _ctxBottom = undefined ;
100107 this . _placeholderBitmap ?. close ( ) ;
101108 this . _placeholderBitmap = undefined ;
102109 this . _placeholder = undefined ;
@@ -140,27 +147,36 @@ export class ImageRenderer extends Disposable implements IDisposable {
140147 /**
141148 * Clear a region of the image layer canvas.
142149 */
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- ) ;
150+ public clearLines ( start : number , end : number , layer ?: ImageLayer ) : void {
151+ const y = start * ( this . dimensions ?. css . cell . height || 0 ) ;
152+ const w = this . dimensions ?. css . canvas . width || 0 ;
153+ const h = ( ++ end - start ) * ( this . dimensions ?. css . cell . height || 0 ) ;
154+ if ( ! layer || layer === 'top' ) {
155+ this . _ctxTop ?. clearRect ( 0 , y , w , h ) ;
156+ }
157+ if ( ! layer || layer === 'bottom' ) {
158+ this . _ctxBottom ?. clearRect ( 0 , y , w , h ) ;
159+ }
150160 }
151161
152162 /**
153163 * Clear whole image canvas.
154164 */
155- public clearAll ( ) : void {
156- this . _ctx ?. clearRect ( 0 , 0 , this . canvas ?. width || 0 , this . canvas ?. height || 0 ) ;
165+ public clearAll ( layer ?: ImageLayer ) : void {
166+ if ( ! layer || layer === 'top' ) {
167+ this . _ctxTop ?. clearRect ( 0 , 0 , this . _canvasTop ?. width || 0 , this . _canvasTop ?. height || 0 ) ;
168+ }
169+ if ( ! layer || layer === 'bottom' ) {
170+ this . _ctxBottom ?. clearRect ( 0 , 0 , this . _canvasBottom ?. width || 0 , this . _canvasBottom ?. height || 0 ) ;
171+ }
157172 }
158173
159174 /**
160175 * Draw neighboring tiles on the image layer canvas.
161176 */
162177 public draw ( imgSpec : IImageSpec , tileId : number , col : number , row : number , count : number = 1 ) : void {
163- if ( ! this . _ctx ) {
178+ const ctx = imgSpec . layer === 'bottom' ? this . _ctxBottom : this . _ctxTop ;
179+ if ( ! ctx ) {
164180 return ;
165181 }
166182 const { width, height } = this . cellSize ;
@@ -187,7 +203,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
187203 // Note: For not pixel perfect aligned cells like in the DOM renderer
188204 // this will move a tile slightly to the top/left (subpixel range, thus ignore it).
189205 // FIX #34: avoid striping on displays with pixelDeviceRatio != 1 by ceiling height and width
190- this . _ctx . drawImage (
206+ ctx . drawImage (
191207 img ,
192208 Math . floor ( sx ) , Math . floor ( sy ) , Math . ceil ( finalWidth ) , Math . ceil ( finalHeight ) ,
193209 Math . floor ( dx ) , Math . floor ( dy ) , Math . ceil ( finalWidth ) , Math . ceil ( finalHeight )
@@ -227,7 +243,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
227243 * Draw a line with placeholder on the image layer canvas.
228244 */
229245 public drawPlaceholder ( col : number , row : number , count : number = 1 ) : void {
230- if ( this . _ctx ) {
246+ if ( this . _ctxTop ) {
231247 const { width, height } = this . cellSize ;
232248
233249 // Don't try to draw anything, if we cannot get valid renderer metrics.
@@ -241,7 +257,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
241257 this . _createPlaceHolder ( height + 1 ) ;
242258 }
243259 if ( ! this . _placeholder ) return ;
244- this . _ctx . drawImage (
260+ this . _ctxTop . drawImage (
245261 this . _placeholderBitmap ?? this . _placeholder ! ,
246262 col * width ,
247263 ( row * height ) % 2 ? 0 : 1 , // needs %2 offset correction
@@ -260,12 +276,15 @@ export class ImageRenderer extends Disposable implements IDisposable {
260276 * Checked once from `ImageStorage.render`.
261277 */
262278 public rescaleCanvas ( ) : void {
263- if ( ! this . canvas ) {
264- return ;
279+ const w = this . dimensions ?. css . canvas . width || 0 ;
280+ 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 ;
265284 }
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 ;
285+ if ( this . _canvasBottom && ( this . _canvasBottom . width !== w || this . _canvasBottom . height !== h ) ) {
286+ this . _canvasBottom . width = w ;
287+ this . _canvasBottom . height = h ;
269288 }
270289 }
271290
@@ -305,34 +324,60 @@ export class ImageRenderer extends Disposable implements IDisposable {
305324 this . _oldSetRenderer = this . _renderService . setRenderer . bind ( this . _renderService ) ;
306325 this . _renderService . setRenderer = ( renderer : any ) => {
307326 this . removeLayerFromDom ( ) ;
327+ this . removeLayerFromDom ( 'bottom' ) ;
308328 this . _oldSetRenderer ?. call ( this . _renderService , renderer ) ;
309329 } ;
310330 }
311331
312- public insertLayerToDom ( ) : void {
332+ public insertLayerToDom ( layer : ImageLayer = 'top' ) : void {
313333 // make sure that the terminal is attached to a document and to DOM
314334 if ( this . document && this . _terminal . _core . screenElement ) {
315- if ( ! this . canvas ) {
316- this . canvas = ImageRenderer . createCanvas (
335+ if ( layer === 'top' && ! this . _canvasTop ) {
336+ this . _canvasTop = ImageRenderer . createCanvas (
317337 this . document , this . dimensions ?. css . canvas . width || 0 ,
318338 this . dimensions ?. css . canvas . height || 0
319339 ) ;
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 ( ) ;
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' ) ;
324360 }
325361 } else {
326362 console . warn ( 'image addon: cannot insert output canvas to DOM, missing document or screenElement' ) ;
327363 }
328364 }
329365
330- public removeLayerFromDom ( ) : void {
331- if ( this . canvas ) {
332- this . _ctx = undefined ;
333- this . canvas . remove ( ) ;
334- this . canvas = undefined ;
366+ public removeLayerFromDom ( layer : ImageLayer = 'top' ) : void {
367+ if ( layer === 'top' && this . _canvasTop ) {
368+ this . _ctxTop = undefined ;
369+ this . _canvasTop . remove ( ) ;
370+ this . _canvasTop = undefined ;
335371 }
372+ if ( layer === 'bottom' && this . _canvasBottom ) {
373+ this . _ctxBottom = undefined ;
374+ this . _canvasBottom . remove ( ) ;
375+ this . _canvasBottom = undefined ;
376+ }
377+ }
378+
379+ public hasLayer ( layer : ImageLayer ) : boolean {
380+ return layer === 'top' ? ! ! this . _canvasTop : ! ! this . _canvasBottom ;
336381 }
337382
338383 private _createPlaceHolder ( height : number = PLACEHOLDER_HEIGHT ) : void {
0 commit comments