#include "BitmapFont.h" #include "../RHI/RHIDevice.h" #include #include namespace grove { // ============================================================================ // Embedded 8x8 Monospace Font Data // Classic CP437 style bitmap font covering ASCII 32-126 // Each character is 8x8 pixels, stored as 8 bytes (1 bit per pixel) // ============================================================================ // clang-format off static const uint8_t g_fontData8x8[] = { // 32 SPACE 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 33 ! 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00, // 34 " 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 35 # 0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, // 36 $ 0x18, 0x7E, 0xC0, 0x7C, 0x06, 0xFC, 0x18, 0x00, // 37 % 0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00, // 38 & 0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00, // 39 ' 0x30, 0x30, 0x60, 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, // 48 0 0x7C, 0xCE, 0xDE, 0xF6, 0xE6, 0xC6, 0x7C, 0x00, // 49 1 0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, // 50 2 0x7C, 0xC6, 0x06, 0x7C, 0xC0, 0xC0, 0xFE, 0x00, // 51 3 0xFC, 0x06, 0x06, 0x3C, 0x06, 0x06, 0xFC, 0x00, // 52 4 0x0C, 0xCC, 0xCC, 0xCC, 0xFE, 0x0C, 0x0C, 0x00, // 53 5 0xFE, 0xC0, 0xFC, 0x06, 0x06, 0xC6, 0x7C, 0x00, // 54 6 0x7C, 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0x7C, 0x00, // 55 7 0xFE, 0x06, 0x06, 0x0C, 0x18, 0x18, 0x18, 0x00, // 56 8 0x7C, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0x7C, 0x00, // 57 9 0x7C, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x7C, 0x00, // 58 : 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, // 59 ; 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30, // 60 < 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 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, 0x7C, 0x00, // 65 A 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x00, // 66 B 0xFC, 0xC6, 0xC6, 0xFC, 0xC6, 0xC6, 0xFC, 0x00, // 67 C 0x7C, 0xC6, 0xC0, 0xC0, 0xC0, 0xC6, 0x7C, 0x00, // 68 D 0xF8, 0xCC, 0xC6, 0xC6, 0xC6, 0xCC, 0xF8, 0x00, // 69 E 0xFE, 0xC0, 0xC0, 0xF8, 0xC0, 0xC0, 0xFE, 0x00, // 70 F 0xFE, 0xC0, 0xC0, 0xF8, 0xC0, 0xC0, 0xC0, 0x00, // 71 G 0x7C, 0xC6, 0xC0, 0xCE, 0xC6, 0xC6, 0x7E, 0x00, // 72 H 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00, // 73 I 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, // 74 J 0x06, 0x06, 0x06, 0x06, 0xC6, 0xC6, 0x7C, 0x00, // 75 K 0xC6, 0xCC, 0xD8, 0xF0, 0xD8, 0xCC, 0xC6, 0x00, // 76 L 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0x00, // 77 M 0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0x00, // 78 N 0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, // 79 O 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, // 80 P 0xFC, 0xC6, 0xC6, 0xFC, 0xC0, 0xC0, 0xC0, 0x00, // 81 Q 0x7C, 0xC6, 0xC6, 0xC6, 0xD6, 0xDE, 0x7C, 0x06, // 82 R 0xFC, 0xC6, 0xC6, 0xFC, 0xD8, 0xCC, 0xC6, 0x00, // 83 S 0x7C, 0xC6, 0xC0, 0x7C, 0x06, 0xC6, 0x7C, 0x00, // 84 T 0xFE, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // 85 U 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, // 86 V 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00, // 87 W 0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00, // 88 X 0xC6, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0xC6, 0x00, // 89 Y 0xC6, 0xC6, 0x6C, 0x38, 0x18, 0x18, 0x18, 0x00, // 90 Z 0xFE, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFE, 0x00, // 91 [ 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, // 92 backslash 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, 0xFE, // 96 ` 0x18, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, // 97 a 0x00, 0x00, 0x7C, 0x06, 0x7E, 0xC6, 0x7E, 0x00, // 98 b 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xFC, 0x00, // 99 c 0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC6, 0x7C, 0x00, // 100 d 0x06, 0x06, 0x7E, 0xC6, 0xC6, 0xC6, 0x7E, 0x00, // 101 e 0x00, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0x7C, 0x00, // 102 f 0x1C, 0x36, 0x30, 0x78, 0x30, 0x30, 0x30, 0x00, // 103 g 0x00, 0x00, 0x7E, 0xC6, 0xC6, 0x7E, 0x06, 0x7C, // 104 h 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, // 105 i 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00, // 106 j 0x0C, 0x00, 0x0C, 0x0C, 0x0C, 0x0C, 0xCC, 0x78, // 107 k 0xC0, 0xC0, 0xCC, 0xD8, 0xF0, 0xD8, 0xCC, 0x00, // 108 l 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, // 109 m 0x00, 0x00, 0xEC, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, // 110 n 0x00, 0x00, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, // 111 o 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, // 112 p 0x00, 0x00, 0xFC, 0xC6, 0xC6, 0xFC, 0xC0, 0xC0, // 113 q 0x00, 0x00, 0x7E, 0xC6, 0xC6, 0x7E, 0x06, 0x06, // 114 r 0x00, 0x00, 0xDC, 0xE6, 0xC0, 0xC0, 0xC0, 0x00, // 115 s 0x00, 0x00, 0x7E, 0xC0, 0x7C, 0x06, 0xFC, 0x00, // 116 t 0x30, 0x30, 0x7C, 0x30, 0x30, 0x36, 0x1C, 0x00, // 117 u 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x00, // 118 v 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, // 119 w 0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xFE, 0x6C, 0x00, // 120 x 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00, // 121 y 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x7C, // 122 z 0x00, 0x00, 0xFE, 0x0C, 0x38, 0x60, 0xFE, 0x00, // 123 { 0x0E, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0E, 0x00, // 124 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // 125 } 0x70, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x70, 0x00, // 126 ~ 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; // clang-format on static constexpr int FONT_FIRST_CHAR = 32; // Space static constexpr int FONT_CHAR_COUNT = 95; // 32-126 static constexpr int FONT_GLYPH_SIZE = 8; // 8x8 pixels bool BitmapFont::initDefault(rhi::IRHIDevice& device) { // Create 128x64 atlas (16 chars per row, 8 rows = 128 chars max) // We only use 95 chars (ASCII 32-126) m_atlasWidth = 128; // 16 chars * 8 pixels m_atlasHeight = 64; // 8 rows * 8 pixels // Create RGBA texture data const int atlasPixels = m_atlasWidth * m_atlasHeight; std::vector atlasData(atlasPixels, 0); // Render each character into the atlas for (int charIdx = 0; charIdx < FONT_CHAR_COUNT; ++charIdx) { int col = charIdx % 16; int row = charIdx / 16; int baseX = col * FONT_GLYPH_SIZE; int baseY = row * FONT_GLYPH_SIZE; const uint8_t* glyphData = &g_fontData8x8[charIdx * 8]; for (int y = 0; y < 8; ++y) { uint8_t rowBits = glyphData[y]; for (int x = 0; x < 8; ++x) { // MSB first: bit 7 is leftmost pixel bool pixel = (rowBits & (0x80 >> x)) != 0; int atlasX = baseX + x; int atlasY = baseY + y; int idx = atlasY * m_atlasWidth + atlasX; // White pixel with alpha = 255 if set, 0 if not atlasData[idx] = pixel ? 0xFFFFFFFF : 0x00000000; } } } // Create GPU texture rhi::TextureDesc texDesc; texDesc.width = m_atlasWidth; texDesc.height = m_atlasHeight; texDesc.format = rhi::TextureDesc::RGBA8; texDesc.data = atlasData.data(); texDesc.dataSize = atlasPixels * sizeof(uint32_t); m_texture = device.createTexture(texDesc); if (!m_texture.isValid()) { return false; } // Generate glyph info generateDefaultGlyphs(); m_baseSize = 8.0f; m_lineHeight = 8.0f; return true; } void BitmapFont::generateDefaultGlyphs() { float invW = 1.0f / m_atlasWidth; float invH = 1.0f / m_atlasHeight; for (int charIdx = 0; charIdx < FONT_CHAR_COUNT; ++charIdx) { uint32_t codepoint = FONT_FIRST_CHAR + charIdx; int col = charIdx % 16; int row = charIdx / 16; GlyphInfo glyph; glyph.u0 = col * FONT_GLYPH_SIZE * invW; glyph.v0 = row * FONT_GLYPH_SIZE * invH; glyph.u1 = (col + 1) * FONT_GLYPH_SIZE * invW; glyph.v1 = (row + 1) * FONT_GLYPH_SIZE * invH; glyph.width = FONT_GLYPH_SIZE; glyph.height = FONT_GLYPH_SIZE; glyph.offsetX = 0.0f; glyph.offsetY = 0.0f; glyph.advance = FONT_GLYPH_SIZE; // Monospace: fixed advance m_glyphs[codepoint] = glyph; } // Default glyph for unknown characters (use '?' which is ASCII 63) m_defaultGlyph = m_glyphs['?']; } bool BitmapFont::loadBMFont(rhi::IRHIDevice& device, const std::string& fntPath, const std::string& pngPath) { // TODO: Implement BMFont loader if needed // For now, fall back to default font return initDefault(device); } void BitmapFont::shutdown(rhi::IRHIDevice& device) { if (m_texture.isValid()) { device.destroy(m_texture); m_texture = rhi::TextureHandle(); } m_glyphs.clear(); } const GlyphInfo& BitmapFont::getGlyph(uint32_t codepoint) const { auto it = m_glyphs.find(codepoint); if (it != m_glyphs.end()) { return it->second; } return m_defaultGlyph; } float BitmapFont::measureWidth(const char* text) const { if (!text) return 0.0f; float width = 0.0f; while (*text) { const GlyphInfo& glyph = getGlyph(static_cast(*text)); width += glyph.advance; ++text; } return width; } } // namespace grove