dow/server/static/dow-emulator.js

172 lines
3.0 KiB
JavaScript

/* eslint-disable @typescript-eslint/no-unused-vars */
/** @type {HTMLCanvasElement} */
const $canvas = document.getElementById('canvas')
const TYPES = {
TEXT: 0,
LINE: 1,
CIRCLE: 2,
CIRCLE_FILL: 3,
RECT: 4,
RECT_FILL: 5,
}
const ALIGN = {
START: 0,
CENTER: 1,
END: 2,
}
const FONTS = {
0: '24px FreeSans',
1: '48px FreeSans',
2: 'bold 124px "Fira Code"',
3: '18px FreeSans',
}
/**
* @param {{ x: number; y: number; t: number; c?: number; va?: number; ha?: number; f?: number; x2?: number; y2?: number; w?: number; h?: number; r?: number }[]} data
*/
function render(data) {
const ctx = $canvas.getContext('2d')
if (!ctx) {
throw new Error('Canvas 2D context is not available')
}
ctx.imageSmoothingEnabled = false
ctx.clearRect(0, 0, $canvas.width * 2, $canvas.height * 2)
ctx.textBaseline = 'alphabetic'
ctx.fillStyle = '#222'
ctx.strokeStyle = '#222'
for (const item of data) {
switch (item.t) {
case TYPES.TEXT: {
const content = item.c
const x = item.x
const y = item.y
const verticalAlign = item.va ?? 0
const horizontalAlign = item.ha ?? 0
const font = item.f ?? 0
ctx.font = FONTS[font]
const measured = ctx.measureText(content)
let xx = x
let yy = y
switch (verticalAlign) {
case ALIGN.START:
yy = y + measured.actualBoundingBoxAscent
break
case ALIGN.CENTER:
yy = y + measured.actualBoundingBoxAscent / 2
break
case ALIGN.END:
yy = y
break
}
switch (horizontalAlign) {
case ALIGN.START:
xx = x
break
case ALIGN.CENTER:
xx = x - measured.width / 2
break
case ALIGN.END:
xx = x - measured.width
break
}
ctx.fillText(content, Math.round(xx), Math.round(yy))
break
}
case TYPES.LINE: {
const x = item.x
const y = item.y
const x2 = item.x2
const y2 = item.y2
ctx.moveTo(x, y)
ctx.lineTo(x2, y2)
ctx.stroke()
break
}
case TYPES.CIRCLE: {
const x = item.x
const y = item.y
const radius = item.r
ctx.beginPath()
ctx.arc(x, y, radius, 0, 2 * Math.PI)
ctx.stroke()
break
}
case TYPES.CIRCLE_FILL: {
const x = item.x
const y = item.y
const radius = item.r
ctx.beginPath()
ctx.arc(x, y, radius, 0, 2 * Math.PI)
ctx.fill()
break
}
case TYPES.RECT: {
const x = item.x
const y = item.y
const w = item.x2
const h = item.y2
ctx.strokeRect(x, y, w, h)
break
}
case TYPES.RECT_FILL: {
const x = item.x
const y = item.y
const w = item.x2
const h = item.y2
ctx.fillRect(x, y, w, h)
break
}
}
}
}
const WIDTH = 648
const HEIGHT = 480
const hours = new Date().getHours().toString().padStart(2, '0')
const minutes = new Date().getMinutes().toString().padStart(2, '0')
setInterval(() => {
fetch('/api/dow').then((r) => r.json().then((data) => render(data)))
}, 1000)
render([
{
x: Math.round(WIDTH / 2),
y: 20,
t: TYPES.TEXT,
c: `...`,
va: ALIGN.START,
ha: ALIGN.CENTER,
f: 2,
},
])