/* 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, }, ])