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"); };