From 4ff00404d4eac0954e5bc80c4a24647788f12a27 Mon Sep 17 00:00:00 2001 From: sfja Date: Fri, 30 Jan 2026 18:05:58 +0100 Subject: [PATCH] add new font --- font_maker/font_maker.js | 194 +++++++++++++++++++++++++++++++++++++++ font_maker/index.html | 52 +++++++++++ src/io_device.cpp | 60 +++++++++++- 3 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 font_maker/font_maker.js create mode 100644 font_maker/index.html diff --git a/font_maker/font_maker.js b/font_maker/font_maker.js new file mode 100644 index 0000000..0a933c6 --- /dev/null +++ b/font_maker/font_maker.js @@ -0,0 +1,194 @@ +const canvas = document.querySelector("#canvas"); +const selectedDiv = document.querySelector("#selected"); +const charlistTable = document.querySelector("#charlist"); +const exportButton = document.querySelector("#export"); +const exportAreaCode = document.querySelector("#export-area"); + +const [width, height] = [400, 400]; + +canvas.width = width; +canvas.height = height; + +canvas.style.width = width + "px"; +canvas.style.height = height + "px"; + +const g = canvas.getContext("2d"); +g.imageSmoothingEnabled = false; + +const state = new Array(64).fill(false); + +function render() { + g.fillStyle = "black"; + g.fillRect(0, 0, width, height); + g.fillStyle = "white"; + for (let y = 0; y < 8; ++y) { + for (let x = 0; x < 8; ++x) { + if (state[y * 8 + x]) { + g.fillRect( + x * (width / 8), + y * (height / 8), + width / 8, + height / 8, + ); + } + } + } + renderGrid(); +} + +function renderGrid() { + g.strokeStyle = "gray"; + g.lineWidth = 1; + g.beginPath(); + for (let y = 0; y < 8; ++y) { + for (let x = 0; x < 8; ++x) { + g.moveTo(x * (width / 8), 0); + g.lineTo(x * (width / 8), height); + g.moveTo(0, y * (height / 8)); + g.lineTo(width, y * (height / 8)); + } + } + g.moveTo(width - 1, 0); + g.lineTo(width - 1, height); + g.moveTo(0, height - 1); + g.lineTo(width, height - 1); + g.stroke(); +} + +const specialChars = Object.fromEntries( + Object.entries({ + "\0": "0", + "\n": "n", + "\r": "r", + "\t": "t", + }).map(([ch, r]) => [ch.charCodeAt(0), "\\" + r]), +); + +const chars = new Array(127).fill(0).map((_, i) => ({ + i, + ch: specialChars[i] ?? (i > 31 ? String.fromCharCode(i) : ""), + hex: new Array(8).fill(0), +})); + +let selected = { i: 0, ch: "\0", hex: new Array(8).fill(0) }; + +function stateToHex(state) { + let hex = new Array(8).fill(0); + for (let y = 0; y < 8; ++y) { + let byte = 0; + for (let x = 0; x < 8; ++x) { + if (state[y * 8 + x]) { + byte |= 1 << 7 - x; + } + } + hex[y] = byte; + } + return hex; +} + +function stateFromHex(state, hex) { + for (let y = 0; y < 8; ++y) { + for (let x = 0; x < 8; ++x) { + state[y * 8 + x] = (hex[y] >> 7 - x & 1) != 0; + } + } +} + +function html(strings, ...values) { + return strings[0] + + strings.slice(1).map((v, i) => values[i].toString() + v).join(""); +} + +function renderSelectedChar() { + const { i, ch, hex } = selected; + selectedDiv.innerHTML = html` + ${i} + ${ch} + 0x${hex.map((v) => v.toString(16).padStart(2, "0")).join( + "", + )} + + + `; + + document.querySelector("#selected-save") + .onclick = () => { + const overwrite = document.querySelector("#overwrite").value; + if (overwrite) { + const hex = new Array(8).fill(0); + for (let i = 0; i < 8; ++i) { + hex[i] = parseInt( + overwrite.slice(i * 2 + 2, i * 2 + 4), + 16, + ); + } + chars[selected.i].hex = hex; + selected.hex = hex.map((v) => v); + stateFromHex(state, hex); + renderCharlist(); + renderSelectedChar(); + render(); + } else { + chars[selected.i].hex = selected.hex.map((v) => v); + renderCharlist(); + } + }; +} + +function renderCharlist() { + charlistTable.innerHTML = html` + + int + char + hex + action + + ` + chars + .map(({ i, ch, hex }) => + html` + + ${i} + ${ch} + 0x${hex.map((v) => + v.toString(16).padStart(2, "0") + ).join("")} + + + ` + ).join(""); + + for (const char of chars) { + const { i, ch, hex } = char; + document.querySelector(`#char-select-${i}`) + .onclick = () => { + selected = { i, ch, hex: hex.map((v) => v) }; + renderSelectedChar(); + stateFromHex(state, selected.hex); + render(); + }; + } +} + +canvas.onclick = (ev) => { + const [x, y] = [ev.offsetX / width * 8, ev.offsetY / height * 8].map( + Math.floor, + ); + state[y * 8 + x] = !state[y * 8 + x]; + render(); + selected.hex = stateToHex(state); + renderSelectedChar(); +}; + +renderSelectedChar(); +render(); +renderCharlist(); + +exportButton.onclick = () => { + exportAreaCode.innerText = chars.map(({ i, ch, hex }) => + ` + case ${ch != "" ? `'${ch}'` : i}: return 0x${ + hex.map((v) => v.toString(16).padStart(2, "0")).join("") + }; + `.trim() + ).join("\n"); +}; diff --git a/font_maker/index.html b/font_maker/index.html new file mode 100644 index 0000000..9f7814f --- /dev/null +++ b/font_maker/index.html @@ -0,0 +1,52 @@ + + + + + + Document + + + + + + +
+
+ +
+ + diff --git a/src/io_device.cpp b/src/io_device.cpp index 37bf01d..fa149ea 100644 --- a/src/io_device.cpp +++ b/src/io_device.cpp @@ -20,6 +20,21 @@ constexpr uint64_t char_data(uint8_t ch) switch (ch) { // clang-format off case ' ': return 0x0000000000000000; + case '!': return 0x0018181818001818; + case '"': return 0x0066660000000000; + case '#': return 0x00247e24247e2400; + case '$': return 0x00187e6218467e18; + case '%': return 0x0022562c18346a44; + case '&': return 0x00784c38724a4638; + case '\'': return 0x0018180000000000; + case '(': return 0x000c18303030180c; + case ')': return 0x0030180c0c0c1830; + case '*': return 0x0000241818240000; + case '+': return 0x000018187e7e1818; + case ',': return 0x0000000000181830; + case '-': return 0x000000007e7e0000; + case '.': return 0x0000000000181800; + case '/': return 0x0002060c18306040; case '0': return 0x003C666E7666663C; case '1': return 0x001838181818183C; @@ -32,6 +47,14 @@ constexpr uint64_t char_data(uint8_t ch) case '8': return 0x003C66663C66663C; case '9': return 0x003C66663E06663C; + case ':': return 0x0018180000181800; + case ';': return 0x0000181800181830; + case '<': return 0x00060c1830180c06; + case '=': return 0x00007e7e007e7e00; + case '>': return 0x006030180c183060; + case '?': return 0x003e660c18001818; + case '@': return 0x003c664a564e603c; + case 'A': return 0x00187E66667E6666; case 'B': return 0x00787E667C667E7C; case 'C': return 0x003C7E6060607E3C; @@ -59,7 +82,42 @@ constexpr uint64_t char_data(uint8_t ch) case 'Y': return 0x0066663C18181818; case 'Z': return 0x007E7E0C18307E7E; - case ':': return 0x0018180000181800; + case '[': return 0x003c30303030303c; + case '\\': return 0x00406030180c0602; + case ']': return 0x003c0c0c0c0c0c3c; + case '^': return 0x0010386c44000000; + case '_': return 0x00000000000000ff; + case '`': return 0x0030181800000000; + case 'a': return 0x0000003c023e423e; + case 'b': return 0x004040407c42427c; + case 'c': return 0x000000003c40403c; + case 'd': return 0x00020202023e423e; + case 'e': return 0x0000003c427c403c; + case 'f': return 0x00000c103c101010; + case 'g': return 0x003e645840784438; + case 'h': return 0x0020202038242424; + case 'i': return 0x0000181800181818; + case 'j': return 0x000606000606361c; + case 'k': return 0x0020202c3830382c; + case 'l': return 0x003010101010100c; + case 'm': return 0x000000203e2a2a2a; + case 'n': return 0x0000000038242424; + case 'o': return 0x0000000018242418; + case 'p': return 0x0000001c223c2020; + case 'q': return 0x00000018241c0404; + case 'r': return 0x0000002038242020; + case 's': return 0x0000003840380438; + case 't': return 0x0000103c1010100c; + case 'u': return 0x000000002424241e; + case 'v': return 0x0000002424243c18; + case 'w': return 0x0000000063222a3e; + case 'x': return 0x000000663c183c66; + case 'y': return 0x00000024241c0438; + case 'z': return 0x0000003e060c183e; + case '{': return 0x000c18183018180c; + case '|': return 0x1818181818181818; + case '}': return 0x003018180c181830; + case '~': return 0x0000307a5e0c0000; default: return 0x55AA55AA55AA55AA; // clang-format on