|
|
|
@ -206,6 +206,72 @@ module.exports = class ScreenRenderer { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
drawBoxLine (x, y, dx, dy, type, normalType) { |
|
|
|
|
const ctx = this.ctx |
|
|
|
|
|
|
|
|
|
let normalOffset = 0 |
|
|
|
|
if (normalType === 1) { |
|
|
|
|
// thin
|
|
|
|
|
normalOffset = 1 / 2 |
|
|
|
|
} else if (normalType === 2) { |
|
|
|
|
// thick
|
|
|
|
|
normalOffset = 3 / 2 |
|
|
|
|
} else if (normalType === 3) { |
|
|
|
|
// double
|
|
|
|
|
normalOffset = -1 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (type === 1 || type === 2) { |
|
|
|
|
// thin or thick line
|
|
|
|
|
let lineWidth = type === 2 ? 3 : 1 |
|
|
|
|
let startX = x - Math.sign(dx) * normalOffset |
|
|
|
|
let startY = y - Math.sign(dy) * normalOffset |
|
|
|
|
let width = dx |
|
|
|
|
let height = dy |
|
|
|
|
|
|
|
|
|
if (Math.abs(dx) > 0) { |
|
|
|
|
// horizontal line
|
|
|
|
|
startY -= lineWidth / 2 |
|
|
|
|
startY = Math.round(startY) |
|
|
|
|
height = lineWidth |
|
|
|
|
} else { |
|
|
|
|
// vertical line
|
|
|
|
|
startX -= lineWidth / 2 |
|
|
|
|
startX = Math.round(startX) |
|
|
|
|
width = lineWidth |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ctx.fillRect(startX, startY, width, height) |
|
|
|
|
} else if (type === 3) { |
|
|
|
|
// double-struck line
|
|
|
|
|
ctx.lineWidth = 1 |
|
|
|
|
ctx.lineCap = 'butt' |
|
|
|
|
ctx.beginPath() |
|
|
|
|
let nx = ctx.lineWidth * Math.sign(dy) // normal x
|
|
|
|
|
let ny = ctx.lineWidth * Math.sign(dx) // normal y
|
|
|
|
|
ctx.moveTo(x + nx - Math.sign(dx) * normalOffset, y + ny - Math.sign(dy) * normalOffset) |
|
|
|
|
ctx.lineTo(x + dx + nx, y + dy + ny) |
|
|
|
|
ctx.moveTo(x - nx - Math.sign(dx) * normalOffset, y - ny - Math.sign(dy) * normalOffset) |
|
|
|
|
ctx.lineTo(x + dx - nx, y + dy - ny) |
|
|
|
|
ctx.stroke() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
drawBoxLines ({ x, y, cellWidth, cellHeight, up, left, right, down }) { |
|
|
|
|
const padding = Math.round(this.screen._padding) |
|
|
|
|
|
|
|
|
|
let centerX = (x + 0.5) * cellWidth + padding |
|
|
|
|
let centerY = (y + 0.5) * cellHeight + padding |
|
|
|
|
|
|
|
|
|
let verticalType = Math.max(up, down) |
|
|
|
|
let horizontalType = Math.max(left, right) |
|
|
|
|
|
|
|
|
|
if (up) this.drawBoxLine(centerX, centerY, 0, -cellHeight / 2, up, horizontalType) |
|
|
|
|
if (left) this.drawBoxLine(centerX, centerY, -cellWidth / 2, 0, left, verticalType) |
|
|
|
|
if (right) this.drawBoxLine(centerX, centerY, cellWidth / 2, 0, right, verticalType) |
|
|
|
|
if (down) this.drawBoxLine(centerX, centerY, 0, cellHeight / 2, down, horizontalType) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Draws a cell's character with the given parameters. Won't do anything if |
|
|
|
|
* text is an empty string. |
|
|
|
@ -241,7 +307,202 @@ module.exports = class ScreenRenderer { |
|
|
|
|
let screenY = y * cellHeight + padding |
|
|
|
|
|
|
|
|
|
let codePoint = text.codePointAt(0) |
|
|
|
|
if (codePoint >= 0x2580 && codePoint <= 0x259F) { |
|
|
|
|
if (codePoint >= 0x2500 && codePoint <= 0x257F) { |
|
|
|
|
// box drawing
|
|
|
|
|
// 0 1 2 3 4 5 6 7 8 9 a b c d e f
|
|
|
|
|
// 250 ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
|
|
|
|
|
// 251 ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
|
|
|
|
|
// 252 ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
|
|
|
|
|
// 253 ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
|
|
|
|
|
// 254 ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
|
|
|
|
|
// 255 ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
|
|
|
|
|
// 256 ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯
|
|
|
|
|
// 257 ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
|
|
|
|
|
|
|
|
|
|
ctx.strokeStyle = ctx.fillStyle |
|
|
|
|
|
|
|
|
|
if (codePoint <= 0x250B || (codePoint >= 0x254C && codePoint <= 0x254F)) { |
|
|
|
|
// single long line
|
|
|
|
|
|
|
|
|
|
// direction. 0 for horizontal, 1 for vertical
|
|
|
|
|
let direction, thick, dashes |
|
|
|
|
if (codePoint <= 0x250B) { |
|
|
|
|
direction = Math.floor((codePoint - 0x2500) / 2) % 2 |
|
|
|
|
thick = codePoint % 2 === 1 |
|
|
|
|
dashes = codePoint < 0x2504 ? 0 : codePoint < 0x2508 ? 3 : 4 |
|
|
|
|
} else { |
|
|
|
|
direction = (codePoint - 0x254C) < 3 ? 0 : 1 |
|
|
|
|
thick = codePoint % 2 === 1 |
|
|
|
|
dashes = 2 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ctx.lineWidth = thick ? 3 : 1 |
|
|
|
|
if (dashes) { |
|
|
|
|
let length = direction === 0 ? cellWidth : cellHeight |
|
|
|
|
ctx.setLineDash([(length / dashes) - 1, 1]) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ctx.beginPath() |
|
|
|
|
if (direction === 0) { |
|
|
|
|
ctx.moveTo(screenX, screenY + 0.5 * cellHeight) |
|
|
|
|
ctx.lineTo(screenX + cellWidth, screenY + 0.5 * cellHeight) |
|
|
|
|
} else { |
|
|
|
|
ctx.moveTo(screenX + 0.5 * cellWidth, screenY) |
|
|
|
|
ctx.lineTo(screenX + 0.5 * cellWidth, screenY + cellHeight) |
|
|
|
|
} |
|
|
|
|
ctx.stroke() |
|
|
|
|
if (dashes) ctx.setLineDash([]) |
|
|
|
|
} else if (codePoint <= 0x251B) { |
|
|
|
|
// two lines
|
|
|
|
|
|
|
|
|
|
// horizontal line direction
|
|
|
|
|
let directionX = (codePoint - 0x250B - 1) % 8 < 4 ? 1 : -1 |
|
|
|
|
// vertical line direction
|
|
|
|
|
let directionY = codePoint < 0x2514 ? 1 : -1 |
|
|
|
|
let typeX = (1 - (codePoint - 0x250B) % 2) + 1 |
|
|
|
|
let typeY = Math.floor((codePoint - 0x250B - 1) / 2) % 2 + 1 |
|
|
|
|
|
|
|
|
|
this.drawBoxLines({ |
|
|
|
|
x, |
|
|
|
|
y, |
|
|
|
|
cellWidth, |
|
|
|
|
cellHeight, |
|
|
|
|
up: directionY === -1 ? typeY : 0, |
|
|
|
|
down: directionY === 1 ? typeY : 0, |
|
|
|
|
left: directionX === -1 ? typeX : 0, |
|
|
|
|
right: directionX === 1 ? typeX : 0 |
|
|
|
|
}) |
|
|
|
|
} else if (codePoint <= 0x253B) { |
|
|
|
|
// three lines
|
|
|
|
|
|
|
|
|
|
// TODO: figure out the pattern
|
|
|
|
|
let up = [1, 1, 2, 1, 2, 2, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2] |
|
|
|
|
let left = [0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 1, 1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2] |
|
|
|
|
let right = [1, 2, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2] |
|
|
|
|
let down = [1, 1, 1, 2, 2, 1, 2, 2, 1, 1, 1, 2, 2, 1, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0] |
|
|
|
|
let index = codePoint - 0x251C |
|
|
|
|
|
|
|
|
|
this.drawBoxLines({ |
|
|
|
|
x, |
|
|
|
|
y, |
|
|
|
|
cellWidth, |
|
|
|
|
cellHeight, |
|
|
|
|
up: up[index], |
|
|
|
|
left: left[index], |
|
|
|
|
right: right[index], |
|
|
|
|
down: down[index] |
|
|
|
|
}) |
|
|
|
|
} else if (codePoint <= 0x254B) { |
|
|
|
|
// four lines
|
|
|
|
|
let up = [1, 1, 1, 1, 2, 1, 2, 2, 2, 1, 1, 2, 1, 2, 2, 2] |
|
|
|
|
let left = [1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 2, 2, 1, 2] |
|
|
|
|
let right = [1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 2, 2, 2, 1, 2, 2] |
|
|
|
|
let down = [1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2, 2] |
|
|
|
|
let index = codePoint - 0x253C |
|
|
|
|
|
|
|
|
|
this.drawBoxLines({ |
|
|
|
|
x, |
|
|
|
|
y, |
|
|
|
|
cellWidth, |
|
|
|
|
cellHeight, |
|
|
|
|
up: up[index], |
|
|
|
|
left: left[index], |
|
|
|
|
right: right[index], |
|
|
|
|
down: down[index] |
|
|
|
|
}) |
|
|
|
|
} else if (codePoint <= 0x2551) { |
|
|
|
|
// double struck line
|
|
|
|
|
this.drawBoxLines({ |
|
|
|
|
x, |
|
|
|
|
y, |
|
|
|
|
cellWidth, |
|
|
|
|
cellHeight, |
|
|
|
|
up: codePoint === 0x2551 ? 3 : 0, |
|
|
|
|
down: codePoint === 0x2551 ? 3 : 0, |
|
|
|
|
left: codePoint === 0x2550 ? 3 : 0, |
|
|
|
|
right: codePoint === 0x2550 ? 3 : 0 |
|
|
|
|
}) |
|
|
|
|
} else if (codePoint <= 0x256C) { |
|
|
|
|
// double struck
|
|
|
|
|
|
|
|
|
|
// TODO: figure out the pattern
|
|
|
|
|
let up = [0, 0, 0, 0, 0, 0, 1, 3, 3, 1, 3, 3, 1, 3, 3, 1, 3, 3, 0, 0, 0, 1, 3, 3, 1, 3, 3] |
|
|
|
|
let left = [0, 0, 0, 3, 1, 3, 0, 0, 0, 3, 1, 3, 0, 0, 0, 3, 1, 3, 3, 1, 3, 3, 1, 3, 3, 1, 3] |
|
|
|
|
let right = [3, 1, 3, 0, 0, 0, 3, 1, 3, 0, 0, 0, 3, 1, 3, 0, 0, 0, 3, 1, 3, 3, 1, 3, 3, 1, 3] |
|
|
|
|
let down = [1, 3, 3, 1, 3, 3, 0, 0, 0, 0, 0, 0, 1, 3, 3, 1, 3, 3, 1, 3, 3, 0, 0, 0, 1, 3, 3] |
|
|
|
|
let index = codePoint - 0x2552 |
|
|
|
|
|
|
|
|
|
this.drawBoxLines({ |
|
|
|
|
x, |
|
|
|
|
y, |
|
|
|
|
cellWidth, |
|
|
|
|
cellHeight, |
|
|
|
|
up: up[index], |
|
|
|
|
left: left[index], |
|
|
|
|
right: right[index], |
|
|
|
|
down: down[index] |
|
|
|
|
}) |
|
|
|
|
} else if (codePoint <= 0x2570) { |
|
|
|
|
// arcs
|
|
|
|
|
let centerX = screenX + 0.5 * cellWidth |
|
|
|
|
let centerY = screenY + 0.5 * cellHeight |
|
|
|
|
let radius = Math.min(cellWidth, cellHeight) / 2 |
|
|
|
|
|
|
|
|
|
let endX = (codePoint - 0x256D) % 3 === 0 ? 1 : -1 |
|
|
|
|
let startY = (codePoint - 0x256D) < 2 ? 1 : -1 |
|
|
|
|
|
|
|
|
|
ctx.lineWidth = 1 |
|
|
|
|
ctx.beginPath() |
|
|
|
|
ctx.moveTo(centerX, centerY + startY * cellHeight / 2) |
|
|
|
|
ctx.arcTo(centerX, centerY, centerX + endX * radius, centerY, radius) |
|
|
|
|
ctx.lineTo(centerX + endX * cellWidth / 2, centerY) |
|
|
|
|
ctx.stroke() |
|
|
|
|
} else if (codePoint <= 0x2573) { |
|
|
|
|
// diagonals
|
|
|
|
|
ctx.lineWidth = 1 |
|
|
|
|
ctx.beginPath() |
|
|
|
|
if (codePoint === 0x2571 || codePoint === 0x2573) { |
|
|
|
|
// diagonal /
|
|
|
|
|
ctx.moveTo(screenX, screenY + cellHeight) |
|
|
|
|
ctx.lineTo(screenX + cellWidth, screenY) |
|
|
|
|
} |
|
|
|
|
if (codePoint === 0x2572 || codePoint === 0x2573) { |
|
|
|
|
// diagonal \
|
|
|
|
|
ctx.moveTo(screenX, screenY) |
|
|
|
|
ctx.lineTo(screenX + cellWidth, screenY + cellHeight) |
|
|
|
|
} |
|
|
|
|
ctx.stroke() |
|
|
|
|
} else if (codePoint <= 0x257B) { |
|
|
|
|
// single lines
|
|
|
|
|
|
|
|
|
|
// 0: left, 1: up, 2: right, 3: down
|
|
|
|
|
let direction = (codePoint - 0x2574) % 4 |
|
|
|
|
let type = codePoint < 0x2578 ? 1 : 2 |
|
|
|
|
|
|
|
|
|
this.drawBoxLines({ |
|
|
|
|
x, |
|
|
|
|
y, |
|
|
|
|
cellWidth, |
|
|
|
|
cellHeight, |
|
|
|
|
left: direction === 0 ? type : 0, |
|
|
|
|
up: direction === 1 ? type : 0, |
|
|
|
|
right: direction === 2 ? type : 0, |
|
|
|
|
down: direction === 3 ? type : 0 |
|
|
|
|
}) |
|
|
|
|
} else { |
|
|
|
|
let index = codePoint - 0x257C |
|
|
|
|
this.drawBoxLines({ |
|
|
|
|
x, |
|
|
|
|
y, |
|
|
|
|
cellWidth, |
|
|
|
|
cellHeight, |
|
|
|
|
left: index === 0 ? 1 : index === 2 ? 2 : 0, |
|
|
|
|
up: index === 1 ? 1 : index === 3 ? 2 : 0, |
|
|
|
|
right: index === 0 ? 2 : index === 2 ? 1 : 0, |
|
|
|
|
down: index === 1 ? 2 : index === 3 ? 1 : 0 |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} else if (codePoint >= 0x2580 && codePoint <= 0x259F) { |
|
|
|
|
// block elements
|
|
|
|
|
ctx.beginPath() |
|
|
|
|
const left = screenX |
|
|
|
@ -375,18 +636,6 @@ module.exports = class ScreenRenderer { |
|
|
|
|
ctx.fillText(text, screenX + 0.5 * cellWidth, screenY + 0.5 * cellHeight) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// -- line drawing - a reference for a possible future rect/line implementation ---
|
|
|
|
|
// http://www.fileformat.info/info/unicode/block/box_drawing/utf8test.htm
|
|
|
|
|
// 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F
|
|
|
|
|
// 0x2500 ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
|
|
|
|
|
// 0x2510 ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
|
|
|
|
|
// 0x2520 ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
|
|
|
|
|
// 0x2530 ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
|
|
|
|
|
// 0x2540 ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
|
|
|
|
|
// 0x2550 ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
|
|
|
|
|
// 0x2560 ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯
|
|
|
|
|
// 0x2570 ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
|
|
|
|
|
|
|
|
|
|
if (underline || strike || overline) { |
|
|
|
|
ctx.strokeStyle = this.getColor(fg) |
|
|
|
|
ctx.lineWidth = 1 |
|
|
|
|