Skip to content

Commit

Permalink
add jgs version of editor
Browse files Browse the repository at this point in the history
  • Loading branch information
felixroos committed Nov 22, 2024
1 parent 3996476 commit ebe0c68
Show file tree
Hide file tree
Showing 4 changed files with 596 additions and 0 deletions.
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
</head>
<body>
<h2>🌱 garten.salat.dev</h2>
<p>day 15:<br/>
<a href="pixelfonts/pixelfont-editor2.html">pixelfont editor: jgs7</a>
</p>
<p>day 14:<br/>
<a href="pixelfonts/pixelfont-editor.html">pixelfont editor</a>
</p>
Expand Down
4 changes: 4 additions & 0 deletions pixelfonts/pixelfont-editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
</head>
<body>
<h2>🌱 pixelfont editor</h2>
<p>
<a>PixeloidMono</a>
<a href="pixelfont-editor2.html">jgs7</a>
</p>
<canvas
id="canvas"
style="background: #003333"
Expand Down
335 changes: 335 additions & 0 deletions pixelfonts/pixelfont-editor2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>🌱 pixelfont editor: jgs7</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
body {
background-color: #222;
max-width: 512px;
margin: auto;
font-family: monospace;
font-size: 1.2em;
color: #edd;
text-align: left;
padding: 20px 0;
}
@font-face {
font-family: "FontWithASyntaxHighlighter";
src: url("/fonts/FontWithASyntaxHighlighter-Regular.woff2")
format("woff2");
}
pre {
font-family: "FontWithASyntaxHighlighter", monospace;
font-size: 12px;
padding: 8px;
border: 1px solid white;
overflow: auto;
width: 100%;
margin: 0px 0;
color: white;
box-sizing: border-box;
}
a {
color: cyan;
font-size: 1em;
}
</style>
</head>
<body>
<h2>🌱 pixelfont editor: jgs7</h2>
<p>
<a href="pixelfont-editor.html">PixeloidMono</a>
<a>jgs7</a>
</p>
<canvas
id="canvas"
style="background: #003333"
width="480"
height="480"
></canvas>
<p>
Font:
<a href="https://adelfaure.net/tools/jgs/" target="_blank"
>jgs7 by Adel Faure</a
>
</p>
<details>
<summary>show page source</summary>
<pre id="pre"></pre>
</details>
<br />
<a href="/">back to garten.salat</a>
<br />
<script>
class PixelFont {
constructor(font) {
const { base64, chars, cols, rows } = font;
this.base64 = base64;
this.chars = chars;
this.perChar = base64.length / chars.length;
const maxBits = this.perChar * 6; // 1 base64 char = 6 bit
this.cols = cols;
this.rows = rows;
this.bitsPerChar = this.cols * this.rows;
const totalPixels = this.cols * this.rows * this.chars.length;
this.bits = new Uint8Array(totalPixels);
this.decode();
}
getChar(char) {
const i = this.chars.indexOf(char);
const [from, to] = [i * this.perChar, (i + 1) * this.perChar];
const chunk = this.base64.slice(from, to);
const bin = this.base64ToBinary(chunk);
return bin.slice(0, this.bitsPerChar);
}
base64ToBinary(base64String) {
const binaryString = atob(base64String);
let str = "";
for (let i = 0; i < binaryString.length; i++) {
str += binaryString.charCodeAt(i).toString(2).padStart(8, "0");
}
return str;
}
decode() {
for (let i = 0; i < this.chars.length; i++) {
const blockOffset = i * this.bitsPerChar;
const bits = this.getChar(this.chars[i]);
for (let j = 0; j < bits.length; j++) {
const blockStart = (blockOffset + j) * 4;
const v = bits[j] === "1" ? 255 : 0;
this.bits[blockOffset + j] = v;
}
}
}

insertChar(char, imageData, x, y, gap = 1, a = 1) {
const { data } = imageData;
const charIndex = this.chars.indexOf(char);
const readStart = charIndex * this.cols * this.rows;
const fw = this.cols + gap;
const fh = this.rows + gap;
if (x >= imageData.width) {
console.log("overflow..");
return;
}
const topLeft = y * imageData.width + x;
const ia = 1 - a;
for (let row = 0; row < this.rows; row++) {
const writeStart = topLeft + row * imageData.width;
for (let col = 0; col < this.cols; col++) {
const i = (writeStart + col) * 4;
const readBlock = readStart + (row * this.cols + col);
const v = this.bits[readBlock] * a;
data[i] = v + ia * data[i];
data[i + 1] = v + ia * data[i + 1];
data[i + 2] = v + ia * data[i + 2];
data[i + 3] = v ? 255 : 0;
}
}
}
insertText(text, imageData, x, y, gap = 1, a = 1) {
const lines = text.split("\n");
lines.forEach((line, l) => {
line.split("").forEach((char, i) => {
const offsetX = x + i * (this.cols + gap);
const offsetY = y + l * (this.rows + gap);
this.insertChar(char, imageData, offsetX, offsetY, gap, a);
});
});
}
}

const _mod = (n, m) => ((n % m) + m) % m;
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
class Editor {
constructor(canvas, font, text) {
this.canvas = canvas;
this.scale = 2;
this.col = 0; // caret column
this.maxCol = 0; // max caret column
this.row = 0; // caret row
this.cols = 46; // number of columns
this.rows = 20; // number of rows
this.gap = 0; // gap between chars in px
this.ctx = canvas.getContext("2d");
this.setBuffer(text);
this.font = font;
const [x, y, w, h] = this.getCaretRect();

canvas.width = w * this.cols;
canvas.height = h * this.rows;
canvas.style.width = canvas.width * this.scale + "px";
canvas.style.height = canvas.height * this.scale + "px";
canvas.style.imageRendering = "pixelated";
this.image = new ImageData(canvas.width, canvas.height);
this.onChange();
this.paint();
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowRight") {
this.moveRight();
} else if (e.key === "ArrowLeft") {
this.moveLeft();
} else if (e.key === "ArrowUp") {
this.moveUp();
} else if (e.key === "ArrowDown") {
this.moveDown();
} else if (e.key === "Enter") {
this.writeChar("\n");
this.col = 0;
this.maxCol = 0;
this.row++;
} else if (e.key === "Backspace") {
this.deleteChar();
} else if (e.key === " " || this.font.chars.includes(e.key)) {
this.writeChar(e.key);
this.col++;
this.maxCol = this.col;
} else {
return;
}
this.onChange();
this.paint();
});
}
getLines() {
return this.buffer.join("").split("\n");
}
setBuffer(text) {
this.buffer = text.split("");
}
getPaddedText() {
const padded = [];
const lines = this.getLines();
for (let r = 0; r < this.rows; r++) {
padded[r] =
lines[r]?.padEnd(this.cols).slice(0, this.cols) ||
Array(this.cols).fill(" ").join("");
}
return padded.join("\n");
}
onChange() {
const paddedText = this.getPaddedText();
this.font.insertText(paddedText, this.image, 0, 0, this.gap);
}
paint() {
this.ctx.imageSmoothingEnabled = false;
this.drawCaret();
this.ctx.putImageData(this.image, 0, 0);
}

moveRight() {
const lines = this.getLines();
const line = lines[this.row];
this.col++;
if (this.col > line.length && this.row < lines.length - 1) {
this.row++;
this.col = 0;
} else if (this.col > line.length) {
this.col = line.length;
}
this.maxCol = this.col;
}
moveLeft() {
const lines = this.getLines();
const line = lines[this.row];
this.col--;
if (this.col < 0 && this.row > 0) {
this.row--;
this.col = lines[this.row].length;
} else if (this.col < 0) {
this.col = 0;
}
this.maxCol = this.col;
}
moveUp() {
const lines = this.getLines();
const line = lines[this.row];
if (this.row > 0) {
this.row--;
this.col = Math.min(this.maxCol, lines[this.row].length);
} else {
this.col = 0;
}
}
moveDown() {
const lines = this.getLines();
const line = lines[this.row];
if (this.row < lines.length - 1) {
this.row++;
this.col = Math.min(this.maxCol, lines[this.row].length);
} else {
this.col = lines[this.row].length;
}
}
getCaretIndex() {
const chars = this.getLines()
.slice(0, this.row)
.reduce((sum, line) => sum + line.length + 1, 0);
return chars + this.col;
}
deleteChar() {
if (this.col > 0 || this.row > 0) {
this.moveLeft();
this.buffer.splice(this.getCaretIndex(), 1);
}
}
writeChar(char) {
this.buffer.splice(this.getCaretIndex(), 0, char);
}
drawCaret() {
const [x, y] = this.getCaretRect();
const char = this.buffer[this.row * this.cols + this.col];
this.font.insertChar("§", this.image, x, y, this.gap, 0.4);
}
getCaretRect() {
const w = this.font.cols + this.gap;
const h = this.font.rows + this.gap;
const x = this.col * w;
const y = this.row * h;
return [x, y, w, h];
}
}
const canvas = document.querySelector("#canvas");
/* const font = new PixelFont({
base64:
"////////AAAAAAAAIQhCAIAAUoAAAAAAV9SlfUAAdWji1cQAzkRETmAARSiKyaAAIQAAAAAAOiEIQQcA4IIQhFwAURQAAAAAAQnyEAAAAAAAAIQAAAHwAAAAAAAAAIAACEREQgAAdGNYxcAA4QhCE+AAdEIiI+AAdEJgxcAAEZUviEAA/CHgxcAAdGHoxcAA+EIiIgAAdGLoxcAAdGLwxcAAAAgAAIAAAAgAAIQAGREEEGAAAD4PgAAAwQQREwAAdEIiAIAA/Gt4Q+AAdGP4xiAA9GPox8AAdGEIRcAA5KMYy4AA/CHoQ+AA/CHoQgAAdCF4xcAAjGP4xiAA+QhCE+AAGEIYxcAAjKmKSiAAhCEIQ+AAjvdaxiAAzmtazmAAdGMYxcAA9GPoQgAAdGMayaAA9GPqSiAAdGDgxcAA+QhCEIAAjGMYxcAAjGKlEIAArWtaqUAAjFRFRiAAjGLiEIAA+EREQ+AA/CEIQh8AhBBBBCAA+EIQhD8AIqIAAAAAAAAAAB8AAAAAAAAAABs4zaAAhC2Y5sAAAB8IQeAACFs4zaAAAB0fweAAMT5CEIAAAB0YxeFwhC2YxiAAAD5CE+AAAAIYxcAAhCMuSiAAACEIQ+AAACO61iAAAC2YxiAAAB0YxcAAAB0Yx9CAAB0YxeEIACdMQgAAAB8HB8AAIT5CEMAAACMYxcAAACMYqIAAACMa1UAAACKiKiAAACMVKITAAD4XQ+AAOhGMIQcAIQhCEIQA4IQxiFwAABFRAAAA",
chars:
"§ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
cols: 5,
}); */
const font = new PixelFont({
base64:
"////////////////////ECBAgQIECAAAQIAAAA==AAChQoAAAAAAAAAAAA==RIkX9EiRIkSL+iRIgA==AABBxUoOChSo4IAAAA==AkVRIIEECCBBIqiQAA==AADCRIopSokR0AAAAA==AAAgggAAAAAAAAAAAA==AgQQIIECBAgQECAgQA==gQECAgQIECBBAggQAA==AAAAhUc/nFQgAAAAAA==ECBAgQI/iBAgQIECAA==AAAAAAAAAAAQQQAAAA==AAAAAAA/gAAAAAAAAA==AAAAAAAAAAAAQIAAAA==AgQQIIEECCBBAggQAA==AAABxEiVKkSI4AAAAA==AAAAQYUCBAgQIAAAAA==AAABxECCCCCB8AAAAA==AAABxECGAgSI4AAAAA==AAAAwoUSJHwQIAAAAA==AAAD5AgeAgSI4AAAAA==AAABxEgeIkSI4AAAAA==AAAD4ECCCCCBAAAAAA==AAABxEiOIkSI4AAAAA==AAABxEiRHgSI4AAAAA==AAAAgQAAAAAAQIAAAA==AAAAgQAAAAAQQQAAAA==AAAAMYwgQGAwGAAAAA==AAAH8AAAAAAD+AAAAA==AAAGAwGAgQxjAAAAAA==AHESIEEECAAAQIAAAA==AAABxFOpUpiA4AAAAA==AAABxEiRPkSJEAAAAA==AAADxEieIkSJ4AAAAA==AAABxEgQIECI4AAAAA==AAADxEiRIkSJ4AAAAA==AAAD5AgeIECB8AAAAA==AAAD5AgeIECBAAAAAA==AAABxEgQLkSI4AAAAA==AAACJEifIkSJEAAAAA==AAAD4QIECBAh8AAAAA==AAAAIECBAkSI4AAAAA==AAACJIoYMFCREAAAAA==AAACBAgQIECB8AAAAA==AAACJsqRIkSJEAAAAA==AAACJkyVKkyZEAAAAA==AAABxEiRIkSI4AAAAA==AAADxEiRPECBAAAAAA==AAABxEiRIlSQ0AAAAA==AAADxEiRPFCREAAAAA==AAABxEgOAgSI4AAAAA==AAAD4QIECBAgQAAAAA==AAACJEiRIkSI4AAAAA==AAACJEiKFCggQAAAAA==AAACJEiRIlTZEAAAAA==AAACJEUECCiJEAAAAA==AAACJEUKCBAgQAAAAA==AAAD4EEECCCB8AAAAA==AAAA8QIECBAgeAAAAA==gQECAgQECAgQECAgQA==AAAHgQIECBAjwAAAAA==EFCiJFBggAAAAAAAAA==AAAAAAAAAAAAAAAfwA==AACAgIAAAAAAAAAAAA==AAAAAAcRHkSI4AAAAA==AAACBA8RIkSI4AAAAA==AAAAAAcRIECI4AAAAA==AAAAIEeRIkSI4AAAAA==AAAAAAcRPkCI4AAAAA==AAABxEgeIECBAAAAAA==AAAAAAcRIkR4EiOAAA==AAACBA8RIkSJEAAAAA==AAAAgQAACBAgQAAAAA==AAAAIEAAAgQIEiOAAA==AAACBAiWMGCxEAAAAA==AAAAgQIECBAgQAAAAA==AAAAAAUVKlSJEAAAAA==AAAAAAcRIkSJEAAAAA==AAAAAAcRIkSI4AAAAA==AAAAAAcRIkSJ4gQAAA==AAAAAAcRIkSI8CBAAA==AAAAAAcRIECBAAAAAA==AAAAAAcRHASI4AAAAA==AAACBAgeIECI4AAAAA==AAAAAAiRIkSI4AAAAA==AAAAAAiRFCggQAAAAA==AAAAAAiRKlSooAAAAA==AAAAAAiKCBBREAAAAA==AAAAAAiRIkR4EiOAAA==AAAAAA+CCBBB8AAAAA==AAwgQIYQIDAQIEBgAA==ECBAgQIECBAgQIECAA==AYCBAgMBAhhAgQwAAA==YSBIYAAAAAAAAAAAAA==",
chars:
"§!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
cols: 7,
rows: 14,
});

const initialCode = `
This is a beautiful font called jgs7
made by Adel Faure. Check out adelfaure.net
The quick brown fox jumps over the lazy dog
as a bonus, here are two dogs:
/)-_-(\\ /)-_-(\\
(o o) (o o)
.-----__/\\o/ \\o/\\__-----.
/ __ / \\ __ \\
\\__/\\ / \\_\\ |/ \\| /_/ \\ /\\__/
\\\\ || || \\\\
// || || //
|\\ |\\ /| /|
`;
const editor = new Editor(canvas, font, initialCode);

// render code
document.querySelector("#pre").textContent =
document.querySelector("html").outerHTML;
</script>
</body>
</html>
Loading

0 comments on commit ebe0c68

Please sign in to comment.