function updateArea(context, line, col, lines, cols) {
    if (lines < 1 || cols < 1)
        return;
    //
    // set timing to TRUE to output timing details to the console
    const TIMING    = false
    const doNothing = (s) => {}
    const time      = TIMING ? (s) => {
                        console.time(`updateArea() ${s}`)
                      } : doNothing
    const timeEnd   = TIMING ? (s) => {
                        console.timeEnd(`updateArea() ${s}`)
                      } : doNothing
    //
    // create temporary structures
    time("TOTAL")
    time("init")
    const REALSIZE = CHARSIZE * SCALE
    const x = col  * REALSIZE  // left corner of the block to display
    const y = line * REALSIZE  // top   "  "
    const ctx = context || this.context()
    const img = ctx.createImageData(cols * REALSIZE, lines * REALSIZE)
    const BITS = new Uint8ClampedArray([128, 64, 32, 16, 8, 4, 2, 1])
    let paperPx = new Uint8ClampedArray(4 * SCALE)  // 4 bytes for RGBA
    let inkPx = new Uint8ClampedArray(4 * SCALE)
    let prevAttr = 0
    timeEnd("init")
    //
    // translate the data in displayColours and displayPixels
    // to RGBA 4-byte grid of pixels in ImageData (img).
    time("fill")
    for (let l = line; l < (line + lines) && l < LINES; l++)
        for (let c = col; c < (col + cols) && c < COLUMNS; c++) {
            //
            // if the attribute value has changed from the previous
            // block, update paperPx and inkPx content
            let a = displayColours[l * COLUMNS + c]
            if (a != prevAttr) {
                //
                // determine the ink and paper indexes
                let bright =  (a & 0b0_1_000_000) ? 8 : 0
                let ink    =  (a & 0b0_0_000_111) + bright
                let paper  = ((a & 0b0_0_111_000) >>> 3) + bright
                //
                // copy RGBA bytes into paperPx and inkPx (repeat to SCALE)
                let colour = COLOUR_PIXELS[paper]
                for (let i = 0; i < SCALE; i++)
                    paperPx.set(colour, i * 4)
                //
                colour = COLOUR_PIXELS[ink]
                for (let i = 0; i < SCALE; i++)
                    inkPx.set(colour, i * 4)
                //
                prevAttr = a
            }
            // draw the vertical lines of the character block
            for (let b = 0; b < CHARSIZE; b++) {
                const byte = displayPixels[(l * CHARSIZE + b) * COLUMNS + c]
                for (let bit = 7; bit >= 0; bit--) {
                    const isInk = byte & BITS[bit]
                    const N = CHARSIZE * SCALE
                    const i =
                          l   * 4 * COLUMNS * N * N +
                          b   * 4 * COLUMNS * SCALE * N +
                          c   * 4 * N +
                          bit * 4 * SCALE
                    const px = isInk ? inkPx : paperPx
                    img.data.set(px, i)
                }
            }
        }
    // copy the first line of physical pixels down so that each
    // virtual pixel is SCALE physical pixels high on the canvas.
    timeEnd("fill")
    //
    const BPL = 4 * XMAX * SCALE  // real bytes per line on canvas
    time("copy")
    const length = img.data.length
    for (let i = 0; i < length; i += BPL * SCALE) {
        const start = i
        const end   = i + BPL - 1
        for (let j = 1; j < SCALE; j++)
            img.data.copyWithin(i + BPL * j, start, end)
    }
    // finally, write the rendered image data to the canvas
    timeEnd("copy")
    //
    time("putd")
    ctx.putImageData(img, x, y)
    timeEnd("putd")
    timeEnd("TOTAL")
} //                                                                  updateArea

/** updateArea():
 *  Updates the specified area of the canvas
 *  with changes in the virtual display memory.
 *
 *  @param [context]  Optional context into which to draw.
 *
 *  @param [line]     Starting line number, a number from 0 to LINES-1.
 *                    The starting line is at the top of the display.
 *
 *  @param [col]      Starting column number, a number from 0 to COLUMNS-1.
 *                    The starting column is at the left of the display.
 *
 *  @param [line]     The height of the area to update, from 1 to LINES.
 *                    Any areas out of the display area will be ignored.
 *
 *  @param [col]      The width of the area to update, from 1 to COLUMNS.
 */
Updates the specified area of the canvas
with changes in the virtual display memory.

@param [context]  Optional context into which to draw.

@param [line]     Starting line number, a number from 0 to LINES-1.
The starting line is at the top of the display.

@param [col]      Starting column number, a number from 0 to COLUMNS-1.
The starting column is at the left of the display.

@param [line]     The height of the area to update, from 1 to LINES.
Any areas out of the display area will be ignored.

@param [col]      The width of the area to update, from 1 to COLUMNS.
Updates the specified area of the canvas with changes in the virtual display memory . @param [ context ] Optional context into which to draw . @param [ line ] Starting line number a number from 0 to LINES - 1 . The starting line is at the top of the display . @param [ col ] Starting column number a number from 0 to COLUMNS - 1 . The starting column is at the left of the display . @param [ line ] The height of the area to update from 1 to LINES . Any areas out of the display area will be ignored . @param [ col ] The width of the area to update from 1 to COLUMNS .
function updateArea(context, line, col, lines, cols) {
    if (lines < 1 || cols < 1)
        return;
    const TIMING    = false
    const doNothing = (s) => {}
    const time      = TIMING ? (s) => {
                        console.time(`updateArea() ${s}`)
                      } : doNothing
    const timeEnd   = TIMING ? (s) => {
                        console.timeEnd(`updateArea() ${s}`)
                      } : doNothing
    time("TOTAL")
    time("init")
    const REALSIZE = CHARSIZE * SCALE
    const x = col  * REALSIZE  
    const y = line * REALSIZE  
    const ctx = context || this.context()
    const img = ctx.createImageData(cols * REALSIZE, lines * REALSIZE)
    const BITS = new Uint8ClampedArray([128, 64, 32, 16, 8, 4, 2, 1])
    let paperPx = new Uint8ClampedArray(4 * SCALE)  
    let inkPx = new Uint8ClampedArray(4 * SCALE)
    let prevAttr = 0
    timeEnd("init")
    time("fill")
    for (let l = line; l < (line + lines) && l < LINES; l++)
        for (let c = col; c < (col + cols) && c < COLUMNS; c++) {
            let a = displayColours[l * COLUMNS + c]
            if (a != prevAttr) {
                let bright =  (a & 0b0_1_000_000) ? 8 : 0
                let ink    =  (a & 0b0_0_000_111) + bright
                let paper  = ((a & 0b0_0_111_000) >>> 3) + bright
                let colour = COLOUR_PIXELS[paper]
                for (let i = 0; i < SCALE; i++)
                    paperPx.set(colour, i * 4)
                colour = COLOUR_PIXELS[ink]
                for (let i = 0; i < SCALE; i++)
                    inkPx.set(colour, i * 4)
                prevAttr = a
            }
            for (let b = 0; b < CHARSIZE; b++) {
                const byte = displayPixels[(l * CHARSIZE + b) * COLUMNS + c]
                for (let bit = 7; bit >= 0; bit--) {
                    const isInk = byte & BITS[bit]
                    const N = CHARSIZE * SCALE
                    const i =
                          l   * 4 * COLUMNS * N * N +
                          b   * 4 * COLUMNS * SCALE * N +
                          c   * 4 * N +
                          bit * 4 * SCALE
                    const px = isInk ? inkPx : paperPx
                    img.data.set(px, i)
                }
            }
        }
    timeEnd("fill")
    const BPL = 4 * XMAX * SCALE  
    time("copy")
    const length = img.data.length
    for (let i = 0; i < length; i += BPL * SCALE) {
        const start = i
        const end   = i + BPL - 1
        for (let j = 1; j < SCALE; j++)
            img.data.copyWithin(i + BPL * j, start, end)
    }
    timeEnd("copy")
    time("putd")
    ctx.putImageData(img, x, y)
    timeEnd("putd")
    timeEnd("TOTAL")
} 
function updateArea ( context , line , col , lines , cols ) { if ( lines < 1 || cols < 1 ) return ; const TIMING = false const doNothing = ( s ) => { } const time = TIMING ? ( s ) => { console . time ( ` ${ s } ` ) } : doNothing const timeEnd = TIMING ? ( s ) => { console . timeEnd ( ` ${ s } ` ) } : doNothing time ( "TOTAL" ) time ( "init" ) const REALSIZE = CHARSIZE * SCALE const x = col * REALSIZE const y = line * REALSIZE const ctx = context || this . context ( ) const img = ctx . createImageData ( cols * REALSIZE , lines * REALSIZE ) const BITS = new Uint8ClampedArray ( [ 128 , 64 , 32 , 16 , 8 , 4 , 2 , 1 ] ) let paperPx = new Uint8ClampedArray ( 4 * SCALE ) let inkPx = new Uint8ClampedArray ( 4 * SCALE ) let prevAttr = 0 timeEnd ( "init" ) time ( "fill" ) for ( let l = line ; l < ( line + lines ) && l < LINES ; l ++ ) for ( let c = col ; c < ( col + cols ) && c < COLUMNS ; c ++ ) { let a = displayColours [ l * COLUMNS + c ] if ( a != prevAttr ) { let bright = ( a & 0b0_1_000_000 ) ? 8 : 0 let ink = ( a & 0b0_0_000_111 ) + bright let paper = ( ( a & 0b0_0_111_000 ) >>> 3 ) + bright let colour = COLOUR_PIXELS [ paper ] for ( let i = 0 ; i < SCALE ; i ++ ) paperPx . set ( colour , i * 4 ) colour = COLOUR_PIXELS [ ink ] for ( let i = 0 ; i < SCALE ; i ++ ) inkPx . set ( colour , i * 4 ) prevAttr = a } for ( let b = 0 ; b < CHARSIZE ; b ++ ) { const byte = displayPixels [ ( l * CHARSIZE + b ) * COLUMNS + c ] for ( let bit = 7 ; bit >= 0 ; bit -- ) { const isInk = byte & BITS [ bit ] const N = CHARSIZE * SCALE const i = l * 4 * COLUMNS * N * N + b * 4 * COLUMNS * SCALE * N + c * 4 * N + bit * 4 * SCALE const px = isInk ? inkPx : paperPx img . data . set ( px , i ) } } } timeEnd ( "fill" ) const BPL = 4 * XMAX * SCALE time ( "copy" ) const length = img . data . length for ( let i = 0 ; i < length ; i += BPL * SCALE ) { const start = i const end = i + BPL - 1 for ( let j = 1 ; j < SCALE ; j ++ ) img . data . copyWithin ( i + BPL * j , start , end ) } timeEnd ( "copy" ) time ( "putd" ) ctx . putImageData ( img , x , y ) timeEnd ( "putd" ) timeEnd ( "TOTAL" ) }
updateArea():
  Updates the specified area of the canvas
  with changes in the virtual display memory.
updateArea () : Updates the specified area of the canvas with changes in the virtual display memory .
//
 // set timing to TRUE to output timing details to the console
 //
 // create temporary structures
 // left corner of the block to display
 // top   "  "
 // 4 bytes for RGBA
 //
 // translate the data in displayColours and displayPixels
 // to RGBA 4-byte grid of pixels in ImageData (img).
 //
 // if the attribute value has changed from the previous
 // block, update paperPx and inkPx content
 //
 // determine the ink and paper indexes
 //
 // copy RGBA bytes into paperPx and inkPx (repeat to SCALE)
 //
 //
 // draw the vertical lines of the character block
 // copy the first line of physical pixels down so that each
 // virtual pixel is SCALE physical pixels high on the canvas.
 //
 // real bytes per line on canvas
 // finally, write the rendered image data to the canvas
 //
 //                                                                  updateArea
