'use strict'; // Minimal RGB canvas + PNG encoder + 5x7 bitmap font. Only used to generate the demo library. const zlib = require('zlib'); // prettier-ignore const FONT = { A: '01110 10001 10001 11111 10001 10001 10001', B: '11110 10001 10001 11110 10001 10001 11110', C: '01110 10001 10000 10000 10000 10001 01110', D: '11110 10001 10001 10001 10001 10001 11110', E: '11111 10000 10000 11110 10000 10000 11111', F: '11111 10000 10000 11110 10000 10000 10000', G: '01110 10001 10000 10111 10001 10001 01111', H: '10001 10001 10001 11111 10001 10001 10001', I: '01110 00100 00100 00100 00100 00100 01110', J: '00111 00010 00010 00010 00010 10010 01100', K: '10001 10010 10100 11000 10100 10010 10001', L: '10000 10000 10000 10000 10000 10000 11111', M: '10001 11011 10101 10101 10001 10001 10001', N: '10001 10001 11001 10101 10011 10001 10001', O: '01110 10001 10001 10001 10001 10001 01110', P: '11110 10001 10001 11110 10000 10000 10000', Q: '01110 10001 10001 10001 10101 10010 01101', R: '11110 10001 10001 11110 10100 10010 10001', S: '01111 10000 10000 01110 00001 00001 11110', T: '11111 00100 00100 00100 00100 00100 00100', U: '10001 10001 10001 10001 10001 10001 01110', V: '10001 10001 10001 10001 10001 01010 00100', W: '10001 10001 10001 10101 10101 10101 01010', X: '10001 10001 01010 00100 01010 10001 10001', Y: '10001 10001 01010 00100 00100 00100 00100', Z: '11111 00001 00010 00100 01000 10000 11111', 0: '01110 10001 10011 10101 11001 10001 01110', 1: '00100 01100 00100 00100 00100 00100 01110', 2: '01110 10001 00001 00010 00100 01000 11111', 3: '11111 00010 00100 00010 00001 10001 01110', 4: '00010 00110 01010 10010 11111 00010 00010', 5: '11111 10000 11110 00001 00001 10001 01110', 6: '00110 01000 10000 11110 10001 10001 01110', 7: '11111 00001 00010 00100 01000 01000 01000', 8: '01110 10001 10001 01110 10001 10001 01110', 9: '01110 10001 10001 01111 00001 00010 01100', '.': '00000 00000 00000 00000 00000 01100 01100', '-': '00000 00000 00000 11111 00000 00000 00000', ':': '00000 01100 01100 00000 01100 01100 00000', '/': '00000 00001 00010 00100 01000 10000 00000', '#': '01010 01010 11111 01010 11111 01010 01010', '<': '00010 00100 01000 10000 01000 00100 00010', '>': '01000 00100 00010 00001 00010 00100 01000', '(': '00010 00100 01000 01000 01000 00100 00010', ')': '01000 00100 00010 00010 00010 00100 01000', '!': '00100 00100 00100 00100 00100 00000 00100', '?': '01110 10001 00001 00010 00100 00000 00100', ',': '00000 00000 00000 00000 01100 00100 01000', "'": '00100 00100 01000 00000 00000 00000 00000', '&': '01100 10010 10100 01000 10101 10010 01101', '+': '00000 00100 00100 11111 00100 00100 00000', '=': '00000 00000 11111 00000 11111 00000 00000', }; const CRC = new Int32Array(256).map((_, n) => { for (let k = 0; k < 8; k++) n = n & 1 ? 0xedb88320 ^ (n >>> 1) : n >>> 1; return n; }); function crc32(buf) { let c = -1; for (let i = 0; i < buf.length; i++) c = CRC[(c ^ buf[i]) & 255] ^ (c >>> 8); return (c ^ -1) >>> 0; } function chunk(type, data) { const head = Buffer.alloc(4), crc = Buffer.alloc(4), body = Buffer.concat([Buffer.from(type, 'ascii'), data]); head.writeUInt32BE(data.length); crc.writeUInt32BE(crc32(body)); return Buffer.concat([head, body, crc]); } function hsl(h, s, l) { h = (((h % 360) + 360) % 360) / 30; const a = s * Math.min(l, 1 - l); const f = n => { const k = (n + h) % 12; return Math.round(255 * (l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)))); }; return [f(0), f(8), f(4)]; } class Canvas { constructor(w, h, bg) { this.w = w; this.h = h; this.px = Buffer.alloc(w * h * 3); if (bg) this.rect(0, 0, w, h, bg); } rect(x, y, w, h, color) { const x0 = Math.max(0, Math.round(x)), y0 = Math.max(0, Math.round(y)); const x1 = Math.min(this.w, Math.round(x + w)), y1 = Math.min(this.h, Math.round(y + h)); if (x1 <= x0 || y1 <= y0) return; const pat = Buffer.from(color); for (let r = y0; r < y1; r++) this.px.fill(pat, (r * this.w + x0) * 3, (r * this.w + x1) * 3); } frame(x, y, w, h, t, color) { this.rect(x, y, w, t, color); this.rect(x, y + h - t, w, t, color); this.rect(x, y, t, h, color); this.rect(x + w - t, y, t, h, color); } textWidth(s, size) { return String(s).length * 6 * size - size; } text(s, x, y, size, color, align) { s = String(s).toUpperCase(); if (align === 'center') x -= this.textWidth(s, size) / 2; if (align === 'right') x -= this.textWidth(s, size); for (const ch of s) { const g = FONT[ch]; if (g) g.split(' ').forEach((row, ry) => { for (let rx = 0; rx < 5; rx++) if (row[rx] === '1') this.rect(x + rx * size, y + ry * size, size, size, color); }); x += 6 * size; } } // Text on a padded box, handy for labels that must stay readable on any background. label(s, x, y, size, color, box, align = 'center') { const w = this.textWidth(s, size), pad = size * 2; const left = align === 'center' ? x - w / 2 : align === 'right' ? x - w : x; this.rect(left - pad, y - pad, w + 2 * pad, 7 * size + 2 * pad, box); this.text(s, left, y, size, color); } crop(y, h) { const c = new Canvas(this.w, h); this.px.copy(c.px, 0, y * this.w * 3, (y + h) * this.w * 3); return c; } // Rotate 90° counter-clockwise (so a viewer must rotate it 90° clockwise to see it upright). rotateCCW() { const c = new Canvas(this.h, this.w); for (let y = 0; y < this.h; y++) for (let x = 0; x < this.w; x++) this.px.copy(c.px, ((this.w - 1 - x) * c.w + y) * 3, (y * this.w + x) * 3, (y * this.w + x) * 3 + 3); return c; } png() { const ihdr = Buffer.alloc(13), stride = this.w * 3, raw = Buffer.alloc((stride + 1) * this.h); ihdr.writeUInt32BE(this.w, 0); ihdr.writeUInt32BE(this.h, 4); ihdr[8] = 8; ihdr[9] = 2; // 8 bit RGB for (let y = 0; y < this.h; y++) this.px.copy(raw, y * (stride + 1) + 1, y * stride, (y + 1) * stride); return Buffer.concat([Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), chunk('IHDR', ihdr), chunk('IDAT', zlib.deflateSync(raw)), chunk('IEND', Buffer.alloc(0))]); } } module.exports = { Canvas, hsl };