using System.Collections.Generic; using Godot; namespace IslaApocalypse.Tools { /// /// A 5x7 bitmap font, drawn straight into a . /// /// ═══ WHY A HAND-ROLLED FONT AND NOT GODOT'S ═══ /// /// Godot's Font renders through a CanvasItem, which means a viewport — the SubViewport /// capture path the renderer deliberately avoids, because it awaits render frames and is why /// `--headless` hangs. A legend is a handful of digits; paying the whole capture path for it /// would be a poor trade, and would drag a frame-loop dependency into a tool that currently /// needs no display at all. /// /// So: 5x7 glyphs, integer-scaled, drawn per-pixel. Digits, a few punctuation marks and A–Z. /// Unknown characters draw as blank rather than throwing — a legend with a missing letter is a /// cosmetic problem, not a reason to lose the render. /// public static class TinyFont { public const int GlyphW = 5; public const int GlyphH = 7; // Each glyph is 7 rows of 5 columns. '#' = ink. Written out rather than hex-packed so a // reader can verify a letter by looking at it. private static readonly Dictionary Glyphs = new() { [' '] = new[] { ".....", ".....", ".....", ".....", ".....", ".....", "....." }, ['.'] = new[] { ".....", ".....", ".....", ".....", ".....", "..##.", "..##." }, ['-'] = new[] { ".....", ".....", ".....", "#####", ".....", ".....", "....." }, [':'] = new[] { ".....", "..##.", "..##.", ".....", "..##.", "..##.", "....." }, ['/'] = new[] { "....#", "...#.", "...#.", "..#..", ".#...", ".#...", "#...." }, ['('] = new[] { "...#.", "..#..", ".#...", ".#...", ".#...", "..#..", "...#." }, [')'] = new[] { ".#...", "..#..", "...#.", "...#.", "...#.", "..#..", ".#..." }, ['0'] = new[] { ".###.", "#...#", "#..##", "#.#.#", "##..#", "#...#", ".###." }, ['1'] = new[] { "..#..", ".##..", "..#..", "..#..", "..#..", "..#..", ".###." }, ['2'] = new[] { ".###.", "#...#", "....#", "...#.", "..#..", ".#...", "#####" }, ['3'] = new[] { "#####", "...#.", "..#..", "...#.", "....#", "#...#", ".###." }, ['4'] = new[] { "...#.", "..##.", ".#.#.", "#..#.", "#####", "...#.", "...#." }, ['5'] = new[] { "#####", "#....", "####.", "....#", "....#", "#...#", ".###." }, ['6'] = new[] { "..##.", ".#...", "#....", "####.", "#...#", "#...#", ".###." }, ['7'] = new[] { "#####", "....#", "...#.", "..#..", ".#...", ".#...", ".#..." }, ['8'] = new[] { ".###.", "#...#", "#...#", ".###.", "#...#", "#...#", ".###." }, ['9'] = new[] { ".###.", "#...#", "#...#", ".####", "....#", "...#.", ".##.." }, ['A'] = new[] { ".###.", "#...#", "#...#", "#####", "#...#", "#...#", "#...#" }, ['B'] = new[] { "####.", "#...#", "#...#", "####.", "#...#", "#...#", "####." }, ['C'] = new[] { ".###.", "#...#", "#....", "#....", "#....", "#...#", ".###." }, ['D'] = new[] { "###..", "#..#.", "#...#", "#...#", "#...#", "#..#.", "###.." }, ['E'] = new[] { "#####", "#....", "#....", "####.", "#....", "#....", "#####" }, ['F'] = new[] { "#####", "#....", "#....", "####.", "#....", "#....", "#...." }, ['G'] = new[] { ".###.", "#...#", "#....", "#.###", "#...#", "#...#", ".####" }, ['H'] = new[] { "#...#", "#...#", "#...#", "#####", "#...#", "#...#", "#...#" }, ['I'] = new[] { ".###.", "..#..", "..#..", "..#..", "..#..", "..#..", ".###." }, ['J'] = new[] { "..###", "...#.", "...#.", "...#.", "...#.", "#..#.", ".##.." }, ['K'] = new[] { "#...#", "#..#.", "#.#..", "##...", "#.#..", "#..#.", "#...#" }, ['L'] = new[] { "#....", "#....", "#....", "#....", "#....", "#....", "#####" }, ['M'] = new[] { "#...#", "##.##", "#.#.#", "#.#.#", "#...#", "#...#", "#...#" }, ['N'] = new[] { "#...#", "##..#", "#.#.#", "#..##", "#...#", "#...#", "#...#" }, ['O'] = new[] { ".###.", "#...#", "#...#", "#...#", "#...#", "#...#", ".###." }, ['P'] = new[] { "####.", "#...#", "#...#", "####.", "#....", "#....", "#...." }, ['Q'] = new[] { ".###.", "#...#", "#...#", "#...#", "#.#.#", "#..#.", ".##.#" }, ['R'] = new[] { "####.", "#...#", "#...#", "####.", "#.#..", "#..#.", "#...#" }, ['S'] = new[] { ".####", "#....", "#....", ".###.", "....#", "....#", "####." }, ['T'] = new[] { "#####", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.." }, ['U'] = new[] { "#...#", "#...#", "#...#", "#...#", "#...#", "#...#", ".###." }, ['V'] = new[] { "#...#", "#...#", "#...#", "#...#", "#...#", ".#.#.", "..#.." }, ['W'] = new[] { "#...#", "#...#", "#...#", "#.#.#", "#.#.#", "##.##", "#...#" }, ['X'] = new[] { "#...#", "#...#", ".#.#.", "..#..", ".#.#.", "#...#", "#...#" }, ['Y'] = new[] { "#...#", "#...#", ".#.#.", "..#..", "..#..", "..#..", "..#.." }, ['Z'] = new[] { "#####", "....#", "...#.", "..#..", ".#...", "#....", "#####" }, }; /// Pixel width of at the given integer scale. public static int Width(string text, int scale) => text.Length * (GlyphW + 1) * scale; /// Pixel height of one line at the given integer scale. public static int Height(int scale) => GlyphH * scale; /// /// Draw with its top-left at (x, y). Uppercases automatically — /// the font has no lowercase, and silently dropping letters would be worse than shouting. /// public static void Draw(Image img, string text, int x, int y, int scale, Color color) { int cursor = x; foreach (char raw in text.ToUpperInvariant()) { if (Glyphs.TryGetValue(raw, out string[] g)) { for (int row = 0; row < GlyphH; row++) for (int col = 0; col < GlyphW; col++) if (g[row][col] == '#') FillCell(img, cursor + col * scale, y + row * scale, scale, color); } cursor += (GlyphW + 1) * scale; } } private static void FillCell(Image img, int px, int py, int scale, Color color) { for (int dy = 0; dy < scale; dy++) for (int dx = 0; dx < scale; dx++) { int tx = px + dx, ty = py + dy; if (tx >= 0 && ty >= 0 && tx < img.GetWidth() && ty < img.GetHeight()) img.SetPixel(tx, ty, color); } } } }