#include "font.h" #include "font-matrix-chunky8.h" #include "font-6x10.h" #include "font-unicode.h" #include // 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 pixel static 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 rendering int 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; }