@ -60,6 +60,7 @@ module.exports = class CanvasRenderer extends EventEmitter {
this . screenBG = [ ]
this . screenBG = [ ]
this . screenAttrs = [ ]
this . screenAttrs = [ ]
this . screenSelection = [ ]
this . screenSelection = [ ]
this . screenLines = [ ]
this . cursor = { }
this . cursor = { }
this . reverseVideo = false
this . reverseVideo = false
this . hasBlinkingCells = false
this . hasBlinkingCells = false
@ -96,6 +97,7 @@ module.exports = class CanvasRenderer extends EventEmitter {
this . drawnScreenFG = [ ]
this . drawnScreenFG = [ ]
this . drawnScreenBG = [ ]
this . drawnScreenBG = [ ]
this . drawnScreenAttrs = [ ]
this . drawnScreenAttrs = [ ]
this . drawnScreenLines = [ ]
this . drawnCursor = [ - 1 , - 1 , '' , false ]
this . drawnCursor = [ - 1 , - 1 , '' , false ]
}
}
@ -209,6 +211,9 @@ module.exports = class CanvasRenderer extends EventEmitter {
drawBackground ( { x , y , cellWidth , cellHeight , bg , isDefaultBG } ) {
drawBackground ( { x , y , cellWidth , cellHeight , bg , isDefaultBG } ) {
const { ctx , width , height , padding } = this
const { ctx , width , height , padding } = this
// is a double-width/double-height line
if ( this . screenLines [ y ] & 0b001 ) cellWidth *= 2
ctx . fillStyle = this . getColor ( bg )
ctx . fillStyle = this . getColor ( bg )
let screenX = x * cellWidth + padding
let screenX = x * cellWidth + padding
let screenY = y * cellHeight + padding
let screenY = y * cellHeight + padding
@ -276,6 +281,39 @@ module.exports = class CanvasRenderer extends EventEmitter {
let screenX = x * cellWidth + padding
let screenX = x * cellWidth + padding
let screenY = y * cellHeight + padding
let screenY = y * cellHeight + padding
const dblWidth = this . screenLines [ y ] & 0b001
const dblHeightTop = this . screenLines [ y ] & 0b010
const dblHeightBot = this . screenLines [ y ] & 0b100
if ( this . screenLines [ y ] ) {
// is a double-width/double-height line
if ( dblWidth ) cellWidth *= 2
ctx . save ( )
ctx . translate ( padding , screenY + 0.5 * cellHeight )
if ( dblWidth ) ctx . scale ( 2 , 1 )
if ( dblHeightTop ) {
// top half
ctx . scale ( 1 , 2 )
ctx . translate ( 0 , cellHeight / 4 )
} else if ( dblHeightBot ) {
// bottom half
ctx . scale ( 1 , 2 )
ctx . translate ( 0 , - cellHeight / 4 )
}
ctx . translate ( - padding , - screenY - 0.5 * cellHeight )
if ( dblWidth ) ctx . translate ( - cellWidth / 4 , 0 )
if ( dblHeightBot || dblHeightTop ) {
// characters overflow -- needs clipping
// TODO: clipping is really expensive
ctx . beginPath ( )
if ( dblHeightTop ) ctx . rect ( screenX , screenY , cellWidth , cellHeight / 2 )
else ctx . rect ( screenX , screenY + cellHeight / 2 , cellWidth , cellHeight / 2 )
ctx . clip ( )
}
}
let codePoint = text . codePointAt ( 0 )
let codePoint = text . codePointAt ( 0 )
if ( codePoint >= 0x2580 && codePoint <= 0x259F ) {
if ( codePoint >= 0x2580 && codePoint <= 0x259F ) {
// block elements
// block elements
@ -450,6 +488,8 @@ module.exports = class CanvasRenderer extends EventEmitter {
ctx . stroke ( )
ctx . stroke ( )
}
}
if ( this . screenLines [ y ] ) ctx . restore ( )
ctx . globalAlpha = 1
ctx . globalAlpha = 1
}
}
@ -558,6 +598,7 @@ module.exports = class CanvasRenderer extends EventEmitter {
fg !== this . drawnScreenFG [ cell ] || // foreground updated, and this cell has text
fg !== this . drawnScreenFG [ cell ] || // foreground updated, and this cell has text
bg !== this . drawnScreenBG [ cell ] || // background updated
bg !== this . drawnScreenBG [ cell ] || // background updated
attrs !== this . drawnScreenAttrs [ cell ] || // attributes updated
attrs !== this . drawnScreenAttrs [ cell ] || // attributes updated
this . screenLines [ y ] !== this . drawnScreenLines [ y ] || // line updated
// TODO: fix artifacts or keep this hack:
// TODO: fix artifacts or keep this hack:
isCursor || wasCursor || // cursor blink/position updated
isCursor || wasCursor || // cursor blink/position updated
( isCursor && this . cursor . style !== this . drawnCursor [ 2 ] ) || // cursor style updated
( isCursor && this . cursor . style !== this . drawnCursor [ 2 ] ) || // cursor style updated
@ -570,6 +611,28 @@ module.exports = class CanvasRenderer extends EventEmitter {
updateMap . set ( cell , didUpdate )
updateMap . set ( cell , didUpdate )
}
}
// set drawn screen lines
this . drawnScreenLines = this . screenLines . slice ( )
let debugFilledUpdates = [ ]
if ( this . graphics >= 1 ) {
// fancy graphics gets really slow when there's a lot of masks
// so here's an algorithm that fills in holes in the update map
for ( let cell of updateMap . keys ( ) ) {
if ( updateMap . get ( cell ) ) continue
let previous = updateMap . get ( cell - 1 ) || false
let next = updateMap . get ( cell + 1 ) || false
if ( previous && next ) {
// set cell to true of horizontally adjacent updated
updateMap . set ( cell , true )
if ( this . debug && this . _debug ) debugFilledUpdates . push ( cell )
}
}
}
// Map of (cell index) -> boolean, whether or not a cell should be redrawn
// Map of (cell index) -> boolean, whether or not a cell should be redrawn
const redrawMap = new Map ( )
const redrawMap = new Map ( )
const maskedCells = new Map ( )
const maskedCells = new Map ( )
@ -594,7 +657,10 @@ module.exports = class CanvasRenderer extends EventEmitter {
// update this cell if:
// update this cell if:
// - the adjacent cell updated (For now, this'll always be true because characters can be slightly larger than they say they are)
// - the adjacent cell updated (For now, this'll always be true because characters can be slightly larger than they say they are)
// - the adjacent cell updated and this cell or the adjacent cell is wide
// - the adjacent cell updated and this cell or the adjacent cell is wide
if ( updateMap . get ( adjacentCell ) && ( this . graphics < 2 || isWideCell || isTextWide ( this . screen [ adjacentCell ] ) ) ) {
// - this or the adjacent cell is not double-sized
if ( updateMap . get ( adjacentCell ) &&
( this . graphics < 2 || isWideCell || isTextWide ( this . screen [ adjacentCell ] ) ) &&
( ! this . screenLines [ Math . floor ( cell / this . width ) ] && ! this . screenLines [ Math . floor ( adjacentCell / this . width ) ] ) ) {
adjacentDidUpdate = true
adjacentDidUpdate = true
if ( this . getAdjacentCells ( cell , 1 ) . includes ( adjacentCell ) ) {
if ( this . getAdjacentCells ( cell , 1 ) . includes ( adjacentCell ) ) {
@ -621,11 +687,54 @@ module.exports = class CanvasRenderer extends EventEmitter {
// TODO: include padding in border cells
// TODO: include padding in border cells
const padding = this . padding
const padding = this . padding
let clipRegion = ( regionStart , y , endX ) => {
let regions = [ ]
for ( let y = 0 ; y < height ; y ++ ) {
let regionStart = null
for ( let x = 0 ; x < width ; x ++ ) {
let cell = y * width + x
let masked = maskedCells . get ( cell )
if ( masked && regionStart === null ) regionStart = x
if ( ! masked && regionStart !== null ) {
regions . push ( [ regionStart , y , x , y + 1 ] )
regionStart = null
}
}
if ( regionStart !== null ) {
regions . push ( [ regionStart , y , width , y + 1 ] )
}
}
// join regions if possible (O(n^2-1), sorry)
let i = 0
while ( i < regions . length ) {
let region = regions [ i ]
let j = 0
while ( j < regions . length ) {
let other = regions [ j ]
if ( other === region ) {
j ++
continue
}
if ( other [ 0 ] === region [ 0 ] && other [ 2 ] === region [ 2 ] && other [ 3 ] === region [ 1 ] ) {
region [ 1 ] = other [ 1 ]
regions . splice ( j , 1 )
if ( i > j ) i --
j --
}
j ++
}
i ++
}
ctx . save ( )
ctx . beginPath ( )
for ( let region of regions ) {
let [ regionStart , y , endX , endY ] = region
let rectX = padding + regionStart * cellWidth
let rectX = padding + regionStart * cellWidth
let rectY = padding + y * cellHeight
let rectY = padding + y * cellHeight
let rectWidth = ( endX - regionStart ) * cellWidth
let rectWidth = ( endX - regionStart ) * cellWidth
let rectHeight = cellHeight
let rectHeight = ( endY - y ) * cellHeight
// compensate for padding
// compensate for padding
if ( regionStart === 0 ) {
if ( regionStart === 0 ) {
@ -641,24 +750,6 @@ module.exports = class CanvasRenderer extends EventEmitter {
ctx . rect ( rectX , rectY , rectWidth , rectHeight )
ctx . rect ( rectX , rectY , rectWidth , rectHeight )
}
}
ctx . save ( )
ctx . beginPath ( )
for ( let y = 0 ; y < height ; y ++ ) {
let regionStart = null
for ( let x = 0 ; x < width ; x ++ ) {
let cell = y * width + x
let masked = maskedCells . get ( cell )
if ( masked && regionStart === null ) regionStart = x
if ( ! masked && regionStart !== null ) {
clipRegion ( regionStart , y , x )
regionStart = null
}
}
if ( regionStart !== null ) {
clipRegion ( regionStart , y , width )
}
}
ctx . clip ( )
ctx . clip ( )
}
}
@ -676,6 +767,7 @@ module.exports = class CanvasRenderer extends EventEmitter {
flags |= ( + updateMap . get ( cell ) ) << 1
flags |= ( + updateMap . get ( cell ) ) << 1
flags |= ( + maskedCells . get ( cell ) ) << 2
flags |= ( + maskedCells . get ( cell ) ) << 2
flags |= ( + isTextWide ( text ) ) << 3
flags |= ( + isTextWide ( text ) ) << 3
flags |= ( + debugFilledUpdates . includes ( cell ) ) << 4
this . _debug . setCell ( cell , flags )
this . _debug . setCell ( cell , flags )
}
}
}
}
@ -716,17 +808,22 @@ module.exports = class CanvasRenderer extends EventEmitter {
let cursorX = x
let cursorX = x
let cursorY = y
let cursorY = y
let cursorWidth = cellWidth // JS doesn't allow same-name assignment
if ( this . cursor . hanging ) {
if ( this . cursor . hanging ) {
// draw hanging cursor in the margin
// draw hanging cursor in the margin
cursorX += 1
cursorX += 1
}
}
let screenX = cursorX * cellWidth + this . padding
// double-width lines
if ( this . screenLines [ cursorY ] & 0b001 ) cursorWidth *= 2
let screenX = cursorX * cursorWidth + this . padding
let screenY = cursorY * cellHeight + this . padding
let screenY = cursorY * cellHeight + this . padding
if ( this . cursor . style === 'block' ) {
if ( this . cursor . style === 'block' ) {
// block
// block
ctx . rect ( screenX , screenY , cell Width , cellHeight )
ctx . rect ( screenX , screenY , cursor Width , cellHeight )
} else if ( this . cursor . style === 'bar' ) {
} else if ( this . cursor . style === 'bar' ) {
// vertical bar
// vertical bar
let barWidth = 2
let barWidth = 2
@ -734,7 +831,7 @@ module.exports = class CanvasRenderer extends EventEmitter {
} else if ( this . cursor . style === 'line' ) {
} else if ( this . cursor . style === 'line' ) {
// underline
// underline
let lineHeight = 2
let lineHeight = 2
ctx . rect ( screenX , screenY + charSize . height , cell Width , lineHeight )
ctx . rect ( screenX , screenY + charSize . height , cursor Width , lineHeight )
}
}
ctx . clip ( )
ctx . clip ( )