import { Remote } from '../remotes/remotebase' ; import * as util from 'util' ; import { ZxNextSpritePatternsView } from './zxnextspritepatternsview' ; import { ImageConvert } from '../misc/imageconvert' ; import { WebviewPanel } from 'vscode' ; import { Utility } from '../misc/utility' ; const MAX_COUNT_SPRITES = 128 ; class SpriteData { public x = 0 ; public y = 0 ; public xMirrored = 0 ; public yMirrored = 0 ; public rotated = 0 ; public paletteOffset = 0 ; public patternIndex = 0 ; public visible = false ; public PR : number = 0 ; public N6 : number | undefined = undefined ; public T : number | undefined = undefined ; public PO : number | undefined = undefined ; public anchorSprite ? : SpriteData ; public anchorSpriteIndex = - 1 ; public xMagnification = 1 ; public yMagnification = 1 ; public image : Array < number > ; param attributes param anchorSprite param anchorSpriteIndex constructor ( attributes , anchorSprite , anchorSpriteIndex ) { this . x = attributes [ 0 ] ; this . y = attributes [ 1 ] ; this . xMirrored = ( attributes [ 2 ] & 8 ) ? 1 : 0 ; this . yMirrored = ( attributes [ 2 ] & 4 ) ? 1 : 0 ; this . rotated = ( attributes [ 2 ] & 2 ) ? 1 : 0 ; this . paletteOffset = attributes [ 2 ] & 240 ; this . PR = attributes [ 2 ] & 1 ; this . patternIndex = attributes [ 3 ] & 63 ; this . visible = ( ( attributes [ 3 ] & 128 ) != 0 ) ; if ( attributes . length > 4 ) { const attr4 = attributes [ 4 ] ; const xPow = ( attr4 & 24 ) >>> 3 ; this . xMagnification = 2 ** xPow ; const yPow = ( attr4 & 6 ) >>> 1 ; this . yMagnification = 2 ** yPow ; const relativeSprite = ( ( attr4 & 192 ) == 64 ) ; if ( relativeSprite ) { Utility . assert ( anchorSprite ) ; this . anchorSprite = anchorSprite ; this . anchorSpriteIndex = anchorSpriteIndex ; if ( this . anchorSprite . N6 != undefined ) this . N6 = ( attr4 & 32 ) >>> 5 ; this . PO = attr4 & 1 ; if ( this . x >= 128 ) this . x -= 256 ; if ( this . y >= 128 ) this . y -= 256 ; } else { if ( attr4 & 128 ) this . N6 = ( attr4 & 64 ) >>> 6 ; this . T = ( attr4 & 32 ) >>> 5 ; this . x += ( attributes [ 2 ] & 0x01 ) * 256 ; this . y += ( attr4 & 0x01 ) * 256 ; } } } /**
     * Returns the absolute pattern index.
     * I.e. relative sprites will add the anchor's sprite index
     * to the pattern index.
     * Other sprites just return the pattern index.
     */ public getAbsPatternIndex ( ) { let patternIndex = this . patternIndex ; if ( this . PO == 1 ) { const anchor = this . anchorSprite ! ; patternIndex += anchor . patternIndex ; let N6 = anchor . N6 ; if ( N6 ) { N6 += this . N6 ! ; if ( N6 > 1 ) patternIndex ++ ; } patternIndex &= 0x3F ; } return patternIndex ; } returns public getAbsN6 ( ) { let N6 = this . N6 ! ; if ( this . PO == 1 ) { const anchor = this . anchorSprite ! ; if ( anchor . N6 ) { N6 += anchor . N6 ; } } return N6 ; } /**
     * Returns the absolute palette index.
     */ public getAbsPaletteOffset ( ) { let paletteOffset = this . paletteOffset ; if ( this . anchorSprite && this . PR != 0 ) { paletteOffset += this . anchorSprite ! . paletteOffset ; paletteOffset &= 0xFF ; } return paletteOffset ; } /**
     * Returns the absolute x/y value and the scale.
     * Takes the anchor into account for composite/unified sprites.
     */ public getAbsXYScale ( ) { let x = this . x ; let y = this . y ; let scaleX = this . xMagnification ; let scaleY = this . yMagnification ; const anchorSprite = this . anchorSprite ; if ( anchorSprite ) { if ( anchorSprite . T == 1 ) { scaleX = anchorSprite . xMagnification ; scaleY = anchorSprite . yMagnification ; if ( anchorSprite . rotated ) { const old_x = x ; x = - y ; y = old_x ; } if ( anchorSprite . xMirrored ) { x = - x ; } if ( anchorSprite . yMirrored ) { y = - y ; } x *= scaleX ; y *= scaleY ; } x += anchorSprite . x ; y += anchorSprite . y ; } if ( 512 - 128 < x ) x -= 512 ; if ( 512 - 128 < y ) y -= 512 ; return { x , y , scaleX , scaleY } ; } /**
     * Returns the type as string.
     */ public getTypeString ( ) { if ( this . T == undefined ) { return "Relative" ; } else { if ( this . T == 0 ) return "Composite" ; else return "Unified" ; } } /**
     * Returns the type as string.
     */ public getAnchorIndexString ( ) { if ( this . anchorSpriteIndex < 0 ) { return "-" ; } return this . anchorSpriteIndex . toString ( ) ; } /**
     * Returns the X-magnification as string.
     */ public getXScaleString ( ) { if ( this . T == undefined ) { if ( this . anchorSprite ) { if ( this . anchorSprite . T == 1 ) return "-" ; } else return "?" ; } return this . xMagnification . toString ( ) + 'x' ; } /**
     * Returns the X-magnification as string.
     */ public getYScaleString ( ) { if ( this . T == undefined ) { if ( this . anchorSprite ) { if ( this . anchorSprite . T == 1 ) return "-" ; } else return "?" ; } return this . yMagnification . toString ( ) + 'x' ; } /**
     * Returns the palette offset in hex.
     */ public getPaletteOffsetString ( ) { const hex = Utility . getHexString ( this . paletteOffset , 2 ) + 'h' ; return hex ; } /**
     * Returns if a relative palette index should be used.
     */ public getPoString ( ) { if ( this . T == undefined ) { return this . PO ! . toString ( ) ; } return "-" ; } /**
     * Returns the value of N6.
     * Undefined for 8bit patterns
     */ public getN6String ( ) { if ( this . N6 == undefined ) { return "-" ; } return this . N6 ! . toString ( ) ; } param pattern param palette param transparentIndex public createImageFromPattern ( pattern , palette , transparentIndex ) { let usedPattern = pattern ; const N6 = this . getAbsN6 ( ) ; if ( N6 != undefined ) { transparentIndex &= 0x0F ; const offset = ( N6 & 0x01 ) * 128 ; const np = new Array < number > ( 256 ) ; for ( let i = 0 ; i < 128 ; i ++ ) { const val = usedPattern [ i + offset ] ; np [ 2 * i ] = val >>> 4 ; np [ 2 * i + 1 ] = val & 0x0F ; } usedPattern = np ; } let rotated = this . rotated ; let xMirrored = this . xMirrored ; let yMirrored = this . yMirrored ; const anchorSprite = this . anchorSprite ; if ( anchorSprite ?. T == 1 ) { if ( anchorSprite . rotated ) { const oldX = xMirrored ; xMirrored = rotated ^ yMirrored ; yMirrored = rotated ^ oldX ; rotated ^= 0x01 ; } if ( anchorSprite . xMirrored ) { xMirrored ^= 0x01 ; } if ( anchorSprite . yMirrored ) { yMirrored ^= 0x01 ; } } if ( rotated ) { const np = new Array < number > ( 256 ) ; let k = 0 ; for ( let y = 0 ; y < 16 ; y ++ ) { for ( let x = 0 ; x < 16 ; x ++ ) np [ x * 16 + 15 - y ] = usedPattern [ k ++ ] ; } usedPattern = np ; } if ( xMirrored ) { const np = new Array < number > ( 256 ) ; let k = 0 ; for ( let y = 0 ; y < 16 ; y ++ ) { for ( let x = 0 ; x < 16 ; x ++ ) np [ k ++ ] = usedPattern [ y * 16 + 15 - x ] ; } usedPattern = np ; } if ( yMirrored ) { const np = new Array < number > ( 256 ) ; let k = 0 ; for ( let y = 0 ; y < 16 ; y ++ ) { for ( let x = 0 ; x < 16 ; x ++ ) np [ k ++ ] = usedPattern [ ( 15 - y ) * 16 + x ] ; } usedPattern = np ; } this . image = ImageConvert . createGifFromArray ( 16 , 16 , usedPattern , palette , transparentIndex ) ; } } /**
 * A Webview that shows the ZX Next sprite slots and the associated pattern with it's palette.
 * The view cannot be edited.
 *
 * The display consists of:
 * - x/y position
 * - Palette offset, mirroring, rotation.
 * - Visibility
 * - Pattern index
 * - Pattern as image
 *
 * The range of the slot indices can be chosen. Eg. "5 10" or "5 10, 17 2".
 * There exist a checkbox that allows for live update of the patterns and palettes.
 * The sprite values themselves are always updated live.
 *
 */ export class ZxNextSpritesView extends ZxNextSpritePatternsView { protected slotIndices : Array < number > ; protected orderedSlotIndices : Array < number > ; protected sprites = Array < SpriteData | undefined > ( MAX_COUNT_SPRITES ) ; protected spriteLastIndex : number ; protected previousSprites = Array < SpriteData | undefined > ( MAX_COUNT_SPRITES ) ; protected clipXl : number ; protected clipXr : number ; protected clipYt : number ; protected clipYb : number ; protected control : number ; protected showOnlyVisible : boolean ; param title param slotRanges constructor ( title , slotRanges ) { super ( title , [ ] ) ; this . control = 0 ; if ( slotRanges ) { this . showOnlyVisible = false ; this . slotIndices = new Array < number > ( ) ; while ( true ) { const start = slotRanges . shift ( ) ; if ( start == undefined ) break ; let end = slotRanges . shift ( ) || 0 ; Utility . assert ( end > 0 ) ; end += start ; for ( let k = start ; k < end ; k ++ ) { if ( k >= MAX_COUNT_SPRITES ) break ; this . slotIndices . push ( k ) ; } } let max = - 1 ; for ( const k of this . slotIndices ) { if ( k > max ) max = k ; } this . spriteLastIndex = max ; this . orderedSlotIndices = this . slotIndices . sort ( ( n1 , n2 ) => n1 - n2 ) ; } else { this . showOnlyVisible = true ; this . spriteLastIndex = MAX_COUNT_SPRITES - 1 ; this . slotIndices = new Array < number > ( MAX_COUNT_SPRITES ) ; this . orderedSlotIndices = new Array < number > ( MAX_COUNT_SPRITES ) ; for ( let i = 0 ; i < MAX_COUNT_SPRITES ; i ++ ) { this . slotIndices [ i ] = i ; this . orderedSlotIndices [ i ] = i ; } } Utility . assert ( this . vscodePanel ) ; ( this . vscodePanel as WebviewPanel ) . title = title ; } param slotIndices protected async getSprites ( ) { let index = 0 ; const spriteCount = this . spriteLastIndex + 1 - index ; const sprites = await Remote . getTbblueSprites ( index , spriteCount ) ; let lastAnchorSprite = new SpriteData ( new Uint8Array ( 5 ) , undefined as any , - 1 ) ; let lastAnchorSpriteIndex = - 1 ; for ( const attrs of sprites ) { const sprite = new SpriteData ( attrs , lastAnchorSprite , lastAnchorSpriteIndex ) ; this . sprites [ index ] = sprite ; if ( sprite . T != undefined ) { lastAnchorSprite = sprite ; lastAnchorSpriteIndex = index ; } index ++ ; } } /**
     * Check if clipping window is set.
     * If YES it also retrieves the sprite clipping coordinates.
     */ protected async getSpritesClippingWindow ( ) { const clip = await Remote . getTbblueSpritesClippingWindow ( ) ; this . clipXl = clip . xl ; this . clipXr = clip . xr ; this . clipYt = clip . yt ; this . clipYb = clip . yb ; this . control = clip . control ; if ( this . control & 2 ) { this . clipXl = 2 * this . clipXl - 32 ; this . clipXr = 2 * this . clipXr - 32 ; this . clipYt -= 32 ; this . clipYb -= 32 ; } } /**
     * Retrieves the sprite patterns from the emulator.
     * It knows which patterns to request from the loaded sprites.
     * And it requests only that data that has not been requested before.
     */ protected async getSpritePatterns ( ) { const onlyVisible = this . showOnlyVisible ; let patternSet = new Set < number > ( ) ; for ( const sprite of this . sprites ) { if ( ! sprite ) continue ; if ( onlyVisible && ! sprite . visible ) continue ; const index = sprite . getAbsPatternIndex ( ) ; patternSet . add ( index ) ; } this . patternIds = Array . from ( patternSet ) ; await super . getSpritePatterns ( ) ; const palette = ZxNextSpritePatternsView . staticGetPaletteForSelectedIndex ( this . usedPalette ) ; Utility . assert ( palette ) ; for ( const sprite of this . sprites ) { if ( ! sprite ) continue ; if ( onlyVisible && ! sprite . visible ) continue ; const pattern = ZxNextSpritePatternsView . spritePatterns . get ( sprite . getAbsPatternIndex ( ) ) ! ; Utility . assert ( pattern ) ; const offs = sprite . getAbsPaletteOffset ( ) ; let usedPalette ; if ( offs == 0 ) usedPalette = palette ; else { const index = 3 * offs ; const firstPart = palette . slice ( index ) ; const secondPart = palette . slice ( 0 , index ) ; usedPalette = firstPart ; usedPalette . push ( ... secondPart ) ; } sprite . createImageFromPattern ( pattern , usedPalette , ZxNextSpritePatternsView . spritesPaletteTransparentIndex ) ; } } param reason public async update ( reason ? ) { try { this . previousSprites = this . sprites ; this . sprites = new Array < SpriteData | undefined > ( MAX_COUNT_SPRITES ) ; try { await this . getSprites ( ) ; } catch ( e ) { this . retrievingError = e . message ; } ; await this . getSpritesClippingWindow ( ) ; await super . update ( reason ) ; } catch ( e ) { Remote . emit ( 'warning' , e . message ) ; } ; } /**
     * Creates the js scripts and the UI elements.
     */ protected createScriptsAndButtons ( ) { let html = super . createScriptsAndButtons ( ) ; html += `
		<script>
			let zxBorderColor;
			let zxScreenBckgColor;
			let zxScreenFgColor;

			//----- To change also the background color of the screen -----
			function spriteBckgSelected() {
				// First call general function
				bckgSelected();

				// Set colors in canvas
				let selectedId = bckgSelector.selectedIndex;
				let color = bckgSelector.options[selectedId].value;
				zxScreenBckgColor = color;
				if(color == "black") {
					zxBorderColor = "gray";
					zxScreenFgColor = "white";
				}
				else if(color == "white") {
					zxBorderColor = "gray";
					zxScreenFgColor = "black";
				}
				else if(color == "gray") {
					zxBorderColor = "lightgray";
					zxScreenFgColor = "black";
				}
				drawScreen();
			}


			// Change the function called when the background dropdown is chosen.
			bckgSelector.onchange = spriteBckgSelected;
		</script>
		` ; return html ; } param currentValue param prevValue protected getTableTdWithBold ( currentValue , prevValue ) { let convCurrentValue = currentValue ; if ( convCurrentValue == undefined ) convCurrentValue = '-' ; let td = ' <td>' ; td += ( currentValue == prevValue ) ? convCurrentValue : '<b>' + convCurrentValue + '</b>' ; td += '</td>\n' ; return td ; } /**
     * Creates one html table out of the sprites data.
     */ protected createHtmlTable ( ) { if ( this . retrievingError ) { return '<div>' + this . retrievingError + '</div>' ; } const format = `
		<style>
			.classPattern {
				width:auto;
				height:2em;
			}
			.classImg {
				image-rendering:pixelated;
				width:auto;
				height:2em;
			}
			.classRow0 {
				//background:#2C3E50;
				background:var(--vscode-panel-background);
				//background:var(--vscode-titleBar-activeBackground);
			}
			.classRow1 {
				//background:#566573;
				background:var(--vscode-panel-dropBackground);
				//background:var(--vscode-titleBar-inactiveBackground);
			}
		</style>
		<table  style="text-align: center" border="1" cellpadding="0">
			<colgroup>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
				<col>
			</colgroup>

          <tr>
			<th>Slot</th>
			<th><span title="The image takes all mirroring and rotation into account. In case of unified (relative) sprites also the mirror/rotation of the anchor.">Image</span></th>

			<th><span title="For anchor sprites the absolute 9bit value.\nFor relative sprite the signed 8bit relative value.">X</span></th>
			<th><span title="For anchor sprites the absolute 9bit value.\nFor relative sprite the signed 8bit relative value.">Y</span></th>

			<th><span title="XM bit. 1 = mirror horizontally.">X-Mir.</span></th>
			<th><span title="YM bit. 1 = mirror vertically.">Y-Mir.</span></th>
			<th><span title="R bit. 1 = rotate 90 degrees clockwise.">Rot.</span></th>
			<th><span title="P bit. 4 bit palette offset.">Pal.Offs.</span></th>
			<th><span title="PR bit (Palette relative).\n Only for relative sprites:\n1 = Palette offset is relative to anchor's palette index.\n0 = Palette offset is absolute.">PR</span></th>
			<th><span title="Relative sprites only.\nPO bit (Pattern offset).\n1 = pattern number is relative to anchor's pattern index.">PO</span></th>
			<th><span title="N5-N0 bits. Pattern index.">Pattern</span></th>
			<th><span title="7th pattern bit if sprite uses 4-bit color pattern. '-' if sprite uses 8-bit color pattern.">N6</span></th>
			<th><span title="XX bits. I.e. the sale factor in X direction.">X-Scale</span></th>
			<th><span title="YY bits. I.e. the sale factor in Y direction.">Y-Scale</span></th>
			<th><span title="V bit. 1 = visible.">Visibility</span></th>
			<th><span title="The sprite type.\nAnchor sprite or relative sprite.\nAn anchor sprite can be either Composite or Unified.\nThe anchor sprite determines the Composite/Unified type of the following relative sprites.">T (Type)<span></th>
			<th><span title="For a relative sprite this is the index of it's anchor sprite.">Anchor</span></th>
		  </tr>

%s

		</table>

		` ; const onlyVisible = this . showOnlyVisible ; let table = '' ; let classRowIndex = 1 ; let lastAnchorspriteIndex = - 1 ; for ( const k of this . slotIndices ) { const sprite = this . sprites [ k ] ; if ( ! sprite ) continue ; if ( onlyVisible ) { if ( ! sprite . visible ) continue ; if ( sprite . anchorSprite && ! sprite . anchorSprite . visible ) continue ; } const prevSprite = this . previousSprites [ k ] ; let index = k ; if ( sprite . T == undefined ) { index = sprite . anchorSpriteIndex ; } if ( lastAnchorspriteIndex != index ) { classRowIndex = ( classRowIndex + 1 ) % 2 ; lastAnchorspriteIndex = index ; } table += '<tr class="classRow' + classRowIndex + '">\n' ; table += ' <td>' + k + '</td>\n' ; const buf = Buffer . from ( sprite . image ) ; const base64String = buf . toString ( 'base64' ) ; table += ' <td class="classPattern"><img class="classImg" src="data:image/gif;base64,' + base64String + '"></td>\n' ; table += this . getTableTdWithBold ( sprite . x , ( prevSprite ) ? prevSprite . x : - 1 ) ; table += this . getTableTdWithBold ( sprite . y , ( prevSprite ) ? prevSprite . y : - 1 ) ; table += this . getTableTdWithBold ( sprite . xMirrored , ( prevSprite ) ? prevSprite . xMirrored : - 1 ) ; table += this . getTableTdWithBold ( sprite . yMirrored , ( prevSprite ) ? prevSprite . yMirrored : - 1 ) ; table += this . getTableTdWithBold ( sprite . rotated , ( prevSprite ) ? prevSprite . rotated : - 1 ) ; table += this . getTableTdWithBold ( sprite . getPaletteOffsetString ( ) , prevSprite ?. getPaletteOffsetString ( ) ) ; table += this . getTableTdWithBold ( sprite . PR , ( prevSprite ) ? prevSprite . PR : - 1 ) ; table += this . getTableTdWithBold ( sprite . getPoString ( ) , prevSprite ?. getPoString ( ) ) ; table += this . getTableTdWithBold ( sprite . patternIndex , ( prevSprite ) ? prevSprite . patternIndex : - 1 ) ; table += this . getTableTdWithBold ( sprite . getN6String ( ) , prevSprite ?. getN6String ( ) ) ; table += this . getTableTdWithBold ( sprite . getXScaleString ( ) , prevSprite ?. getXScaleString ( ) ) ; table += this . getTableTdWithBold ( sprite . getYScaleString ( ) , prevSprite ?. getYScaleString ( ) ) ; table += this . getTableTdWithBold ( sprite . visible , prevSprite ?. visible ) ; table += this . getTableTdWithBold ( sprite . getTypeString ( ) , prevSprite ?. getTypeString ( ) ) ; table += this . getTableTdWithBold ( sprite . getAnchorIndexString ( ) , prevSprite ?. getAnchorIndexString ( ) ) ; table += '</tr>\n\n' ; } const html = util . format ( format , table ) ; return html ; } /**
     * Creates one html canvas to display the sprites on the "screen".
     * The screen also shows the border and the clipping rectangle.
     * Additionally all sprites are drawn into together with their slot index.
     */ protected createHtmlCanvas ( ) { const format = `
		<canvas id="screen" width="640px" height="512px" style="border:1px solid #c3c3c3;">

		<script>
			let canvas = document.getElementById("screen");
			let ctx = canvas.getContext("2d");
			ctx.scale(2, 2);

				function drawScreen() {
				ctx.clearRect(0, 0, canvas.width, canvas.height);

				ctx.imageSmoothingEnabled = false;
				ctx.lineWidth=1;
				ctx.translate(0.5, 0.5);

				ctx.fillStyle = zxBorderColor;
				ctx.fillRect(0,0,320,256);
				ctx.fillStyle = zxScreenBckgColor;
				ctx.fillRect(32,32,320-2*32,256-2*32);

%s

				ctx.strokeStyle = zxScreenFgColor;
				ctx.fillStyle = zxScreenFgColor;

%s

			}
		</script>
` ; let clipHtml = '' ; { clipHtml += 'ctx.beginPath();\n' ; clipHtml += 'ctx.strokeStyle = "red";\n' ; clipHtml += util . format ( 'ctx.rect(%d,%d,%d,%d);\n' , this . clipXl + 32 , this . clipYt + 32 , this . clipXr - this . clipXl + 1 , this . clipYb - this . clipYt + 1 ) ; clipHtml += 'ctx.closePath();\n' ; clipHtml += 'ctx.stroke();\n\n' ; } let spritesHtml = 'ctx.beginPath();\n' ; const lastItem = this . orderedSlotIndices . length - 1 ; const priorityNormal = ( ( this . control & 0x40 ) == 0 ) ; for ( let i = 0 ; i <= lastItem ; i ++ ) { const j = ( priorityNormal ) ? i : lastItem - i ; const k = this . orderedSlotIndices [ j ] ; const sprite = this . sprites [ k ] ; if ( ! sprite ) continue ; if ( ! sprite . visible ) continue ; if ( sprite . anchorSprite && ! sprite . anchorSprite . visible ) continue ; let pos = sprite . getAbsXYScale ( ) ; const width = 16 * pos . scaleX ; const height = 16 * pos . scaleY ; spritesHtml += util . format ( "ctx.rect(%d,%d,%d,%d);\n" , pos . x , pos . y , width , height ) ; spritesHtml += util . format ( 'ctx.fillText("%d",%d,%d);\n' , k , pos . x + width + 2 , pos . y + height ) ; const buf = Buffer . from ( sprite . image ) ; const base64String = buf . toString ( 'base64' ) ; spritesHtml += util . format ( 'let img%d = new Image();\n' , k ) ; spritesHtml += util . format ( 'img%d.onload = function() { ctx.drawImage(img%d,%d,%d,%d,%d); };\n' , k , k , pos . x , pos . y , width , height ) ; spritesHtml += util . format ( 'img%d.src = "data:image/gif;base64,%s";\n' , k , base64String ) ; } spritesHtml += 'ctx.closePath();\n' ; spritesHtml += 'ctx.stroke();\n\n' ; const html = util . format ( format , clipHtml , spritesHtml ) ; return html ; } /**
     * Sets the html code to display the sprites.
     */ protected setHtml ( ) { const format = this . createHtmlSkeleton ( ) ; const ui = this . createScriptsAndButtons ( ) ; const table = this . createHtmlTable ( ) ; const canvas = this . createHtmlCanvas ( ) ; const content = ui + table + '\n<p style="margin-bottom:3em;"></p>\n\n' + canvas ; const html = util . format ( format , content ) ; this . vscodePanel . webview . html = html ; } }