Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430#include "font.h"#include "font-matrix-chunky8.h"#include "font-6x10.h"#include "font-unicode.h"#include <string.h>
// UTF-8 decode: reads one codepoint from *p, advances *p past it, returns// the codepoint. Returns -1 on invalid sequence (skips one byte).static int utf8_next(const char **p) { const unsigned char *s = (const unsigned char *)(*p); unsigned char c = *s; if (!c) return 0; int cp, extra; if ((c & 0x80) == 0) { cp = c; extra = 0; } else if ((c & 0xE0) == 0xC0){ cp = c & 0x1F; extra = 1; } else if ((c & 0xF0) == 0xE0){ cp = c & 0x0F; extra = 2; } else if ((c & 0xF8) == 0xF0){ cp = c & 0x07; extra = 3; } else { (*p)++; return -1; } s++; for (int i = 0; i < extra; i++) { if ((*s & 0xC0) != 0x80) { (*p) = (const char *)s; return -1; } cp = (cp << 6) | (*s & 0x3F); s++; } (*p) = (const char *)s; return cp;}
// Binary search the sorted Unicode glyph table for a codepoint.// Returns NULL if not found.static const UnicodeGlyph *unicode_lookup(uint32_t cp) { int lo = 0, hi = unicode_glyph_count - 1; while (lo <= hi) { int mid = (lo + hi) >> 1; uint32_t v = unicode_glyphs[mid].codepoint; if (v == cp) return &unicode_glyphs[mid]; if (v < cp) lo = mid + 1; else hi = mid - 1; } return NULL;}
// Render a Unicode glyph (unifont, 8x16) positioned so its baseline lines// up with the 6x10 font's baseline at y. Returns the advance width.static int draw_unicode_glyph(ACGraph *g, const UnicodeGlyph *u, int x, int y, int scale) { // 6x10 baseline is at y + 8 (top-aligned font). Unifont ascent is 14. // Position unifont so its ascent sits just above the 6x10 cap — that // places lowercase and caps at comparable heights in practice. int base_y = y - 2 * scale; for (int row = 0; row < u->height; row++) { uint16_t bits = u->rows[row]; for (int col = 0; col < u->width; col++) { if (bits & (0x8000 >> col)) { int px = x + (col + u->xoff) * scale; int py = base_y + (unicode_ascent - u->yoff - u->height + row) * scale; for (int sy = 0; sy < scale; sy++) for (int sx = 0; sx < scale; sx++) graph_plot(g, px + sx, py + sy); } } } return u->dwidth * scale;}
// Classic 8x8 bitmap font (CP437 style, ASCII 32-126)// Each character is 8 bytes, one byte per row, MSB = leftmost pixelstatic const unsigned char font_data[95][8] = { // Space (32) {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ! (33) {0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00}, // " (34) {0x6C, 0x6C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00}, // # (35) {0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00}, // $ (36) {0x18, 0x7E, 0xC0, 0x7C, 0x06, 0xFC, 0x18, 0x00}, // % (37) {0xC6, 0xCC, 0x18, 0x30, 0x60, 0xC6, 0x86, 0x00}, // & (38) {0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00}, // ' (39) {0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}, // ( (40) {0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00}, // ) (41) {0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00}, // * (42) {0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // + (43) {0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00}, // , (44) {0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30}, // - (45) {0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00}, // . (46) {0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00}, // / (47) {0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00}, // 0 (48) {0x7C, 0xCE, 0xDE, 0xF6, 0xE6, 0xC6, 0x7C, 0x00}, // 1 (49) {0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x7E, 0x00}, // 2 (50) {0x7C, 0xC6, 0x06, 0x1C, 0x70, 0xC0, 0xFE, 0x00}, // 3 (51) {0x7C, 0xC6, 0x06, 0x3C, 0x06, 0xC6, 0x7C, 0x00}, // 4 (52) {0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x00}, // 5 (53) {0xFE, 0xC0, 0xFC, 0x06, 0x06, 0xC6, 0x7C, 0x00}, // 6 (54) {0x3C, 0x60, 0xC0, 0xFC, 0xC6, 0xC6, 0x7C, 0x00}, // 7 (55) {0xFE, 0xC6, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00}, // 8 (56) {0x7C, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0x7C, 0x00}, // 9 (57) {0x7C, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0x78, 0x00}, // : (58) {0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00}, // ; (59) {0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30}, // < (60) {0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x00}, // = (61) {0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00}, // > (62) {0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00}, // ? (63) {0x7C, 0xC6, 0x0C, 0x18, 0x18, 0x00, 0x18, 0x00}, // @ (64) {0x7C, 0xC6, 0xDE, 0xDE, 0xDE, 0xC0, 0x78, 0x00}, // A-Z (65-90) {0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x00}, {0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xFC, 0x00}, {0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x3C, 0x00}, {0xF8, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00}, {0xFE, 0x62, 0x68, 0x78, 0x68, 0x62, 0xFE, 0x00}, {0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0xF0, 0x00}, {0x3C, 0x66, 0xC0, 0xC0, 0xCE, 0x66, 0x3E, 0x00}, {0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00}, {0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00}, {0x1E, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00}, {0xE6, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE6, 0x00}, {0xF0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00}, {0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0x00}, {0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00}, {0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00}, {0xFC, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00}, {0x7C, 0xC6, 0xC6, 0xC6, 0xD6, 0xDE, 0x7C, 0x06}, {0xFC, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0xE6, 0x00}, {0x7C, 0xC6, 0xE0, 0x7C, 0x0E, 0xC6, 0x7C, 0x00}, {0x7E, 0x5A, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00}, {0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00}, {0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00}, {0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00}, {0xC6, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0xC6, 0x00}, {0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x3C, 0x00}, {0xFE, 0xC6, 0x8C, 0x18, 0x32, 0x66, 0xFE, 0x00}, // [ (91) {0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00}, // backslash (92) {0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00}, // ] (93) {0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00}, // ^ (94) {0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00}, // _ (95) {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // ` (96) {0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00}, // a-z (97-122) {0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00}, {0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0xDC, 0x00}, {0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC6, 0x7C, 0x00}, {0x1C, 0x0C, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00}, {0x00, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0x7C, 0x00}, {0x1C, 0x36, 0x30, 0x78, 0x30, 0x30, 0x78, 0x00}, {0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0x78}, {0xE0, 0x60, 0x6C, 0x76, 0x66, 0x66, 0xE6, 0x00}, {0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00}, {0x06, 0x00, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3C}, {0xE0, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0xE6, 0x00}, {0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00}, {0x00, 0x00, 0xCC, 0xFE, 0xD6, 0xD6, 0xC6, 0x00}, {0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x00}, {0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00}, {0x00, 0x00, 0xDC, 0x66, 0x66, 0x7C, 0x60, 0xF0}, {0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0x1E}, {0x00, 0x00, 0xDC, 0x76, 0x60, 0x60, 0xF0, 0x00}, {0x00, 0x00, 0x7C, 0xC0, 0x7C, 0x06, 0xFC, 0x00}, {0x10, 0x30, 0x7C, 0x30, 0x30, 0x34, 0x18, 0x00}, {0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00}, {0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00}, {0x00, 0x00, 0xC6, 0xD6, 0xD6, 0xFE, 0x6C, 0x00}, {0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00}, {0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0xFC}, {0x00, 0x00, 0xFE, 0x8C, 0x18, 0x32, 0xFE, 0x00}, // { (123) {0x0E, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0E, 0x00}, // | (124) {0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // } (125) {0x70, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x70, 0x00}, // ~ (126) {0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},};
void font_init(void) { // Font data is static, nothing to init}
int font_draw(ACGraph *g, const char *text, int x, int y, int scale) { if (!text) return x; if (scale < 1) scale = 1;
int start_x = x; for (const char *p = text; *p; p++) { unsigned char ch = (unsigned char)*p; if (ch == '\n') { x = start_x; y += FONT_CHAR_H * scale; continue; } if (ch < 32 || ch > 126) ch = '?'; int idx = ch - 32; const unsigned char *glyph = font_data[idx];
for (int row = 0; row < FONT_CHAR_H; row++) { unsigned char bits = glyph[row]; for (int col = 0; col < FONT_CHAR_W; col++) { if (bits & (0x80 >> col)) { // Draw scaled pixel for (int sy = 0; sy < scale; sy++) { for (int sx = 0; sx < scale; sx++) { graph_plot(g, x + col * scale + sx, y + row * scale + sy); } } } } } x += FONT_CHAR_W * scale; } return x;}
int font_measure(const char *text, int scale) { if (!text) return 0; if (scale < 1) scale = 1; int len = 0; for (const char *p = text; *p && *p != '\n'; p++) len++; return len * FONT_CHAR_W * scale;}
// MatrixChunky8 BDF font rendering (variable-width, 8px tall)int font_draw_matrix(ACGraph *g, const char *text, int x, int y, int scale) { if (!text) return x; if (scale < 1) scale = 1;
int start_x = x; for (const char *p = text; *p; p++) { unsigned char ch = (unsigned char)*p; if (ch == '\n') { x = start_x; y += matrix_chunky8_ascent * scale; continue; } if (ch < 32 || ch > 126) ch = '?'; const BDFGlyph *gl = &matrix_chunky8_glyphs[ch - 32];
// Render glyph: yoff is distance from baseline to bottom of glyph int gy = y + (matrix_chunky8_ascent - gl->yoff - gl->height) * scale; int gx = x + gl->xoff * scale;
for (int row = 0; row < gl->height && row < MATRIX_CHUNKY8_MAX_H; row++) { uint8_t bits = gl->rows[row]; for (int col = 0; col < gl->width; col++) { if (bits & (0x80 >> col)) { for (int sy = 0; sy < scale; sy++) for (int sx = 0; sx < scale; sx++) graph_plot(g, gx + col * scale + sx, gy + row * scale + sy); } } } x += gl->dwidth * scale; } return x;}
int font_measure_matrix(const char *text, int scale) { if (!text) return 0; if (scale < 1) scale = 1; int w = 0; for (const char *p = text; *p && *p != '\n'; p++) { unsigned char ch = (unsigned char)*p; if (ch < 32 || ch > 126) ch = '?'; w += matrix_chunky8_glyphs[ch - 32].dwidth; } return w * scale;}
// 6x10 fixed-width BDF font renderingint font_draw_6x10(ACGraph *g, const char *text, int x, int y, int scale) { if (!text) return x; if (scale < 1) scale = 1;
int start_x = x; const char *p = text; while (*p) { if (*p == '\n') { x = start_x; y += FONT_6X10_H * scale; p++; continue; } int cp = utf8_next(&p); if (cp < 0) continue; if (cp >= 32 && cp <= 126) { // ASCII — render from the 6x10 bitmap int idx = cp - 32; const uint8_t *glyph = font_6x10_data[idx]; for (int row = 0; row < FONT_6X10_H; row++) { uint8_t bits = glyph[row]; for (int col = 0; col < FONT_6X10_W; col++) { if (bits & (0x80 >> col)) { for (int sy = 0; sy < scale; sy++) for (int sx = 0; sx < scale; sx++) graph_plot(g, x + col * scale + sx, y + row * scale + sy); } } } x += FONT_6X10_W * scale; } else if (cp > 126) { // Non-ASCII — try Unicode fallback (unifont subset) const UnicodeGlyph *u = unicode_lookup((uint32_t)cp); if (u) { draw_unicode_glyph(g, u, x, y, scale); x += FONT_6X10_W * scale; // Keep monospace grid } else { // Missing glyph — draw ? as a placeholder const uint8_t *glyph = font_6x10_data['?' - 32]; for (int row = 0; row < FONT_6X10_H; row++) { uint8_t bits = glyph[row]; for (int col = 0; col < FONT_6X10_W; col++) { if (bits & (0x80 >> col)) { for (int sy = 0; sy < scale; sy++) for (int sx = 0; sx < scale; sx++) graph_plot(g, x + col * scale + sx, y + row * scale + sy); } } } x += FONT_6X10_W * scale; } } else { // Control chars — skip silently } } return x;}
int font_measure_6x10(const char *text, int scale) { if (!text) return 0; if (scale < 1) scale = 1; int len = 0; const char *p = text; while (*p && *p != '\n') { int cp = utf8_next(&p); if (cp > 0) len++; } return len * FONT_6X10_W * scale;}
// GNU unifont (8x16 cell, fixed 8px advance for the subset we carry). The// glyph table now includes ASCII, so this renders entire strings — full// parity with the web's "unifont" face (nom word cells, edition subtitles).// Unlike draw_unicode_glyph (baseline-matched into 6x10 text), this places// the 16px glyph cell's top-left at (x, y), like the web write() does.int font_draw_unifont(ACGraph *g, const char *text, int x, int y, int scale) { if (!text) return x; if (scale < 1) scale = 1;
int start_x = x; const char *p = text; while (*p) { if (*p == '\n') { x = start_x; y += FONT_UNIFONT_H * scale; p++; continue; } int cp = utf8_next(&p); if (cp <= 0) { if (cp == 0) break; continue; } const UnicodeGlyph *u = unicode_lookup((uint32_t)cp); if (!u) u = unicode_lookup((uint32_t)'?'); if (!u) { x += FONT_UNIFONT_W * scale; continue; } // BDF cell top = baseline − ascent; for unifont (ascent 14, yoff −2) // a full glyph spans rows 0..15 of the cell, so the cell top is y. for (int row = 0; row < u->height && row < 16; row++) { uint16_t bits = u->rows[row]; for (int col = 0; col < u->width; col++) { if (bits & (0x8000 >> col)) { int px = x + (col + u->xoff) * scale; int py = y + (unicode_ascent - u->yoff - u->height + row) * scale; for (int sy = 0; sy < scale; sy++) for (int sx = 0; sx < scale; sx++) graph_plot(g, px + sx, py + sy); } } } x += u->dwidth * scale; } return x;}
int font_measure_unifont(const char *text, int scale) { if (!text) return 0; if (scale < 1) scale = 1; int w = 0; const char *p = text; while (*p && *p != '\n') { int cp = utf8_next(&p); if (cp <= 0) { if (cp == 0) break; continue; } const UnicodeGlyph *u = unicode_lookup((uint32_t)cp); w += u ? u->dwidth : FONT_UNIFONT_W; } return w * scale;}