/* SPDX-License-Identifier: MIT */ /* * konfont.h * * Copyright (c) 2026 KonAki * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef KONFONT_H #define KONFONT_H #include #include #include #include #include "konsofren.h" #define KON_FONT_FIRST_CHAR 32 #define KON_FONT_NUM_CHARS 95 typedef struct kon_font kon_font_t; /*** font declarations ***/ kon_font_t *kon_loadFont(const uint8_t *ttf_data, size_t size, float pixelHeight); /* a small built in 8x13 bitmap font, needs no files. scale 1 is 13 pixels high, 2 is 26 and so on. monospace, ascii only, the baseline is 10 rows below the top. kon_fontGetAtlas gives NULL for it */ kon_font_t *kon_loadBuiltinFont(int scale); void kon_freeFont(kon_font_t *font); kon_image *kon_fontGetAtlas(kon_font_t *font); /* the ttf data has to stay alive for as long as the font, resizing bakes from it again */ float kon_fontPixelHeight(const kon_font_t *font); int kon_fontResize(kon_font_t *font, float pixelHeight); /*** text draw declarations ***/ void kon_drawText(kon_framebuffer_t *fb, kon_font_t *font, int x, int y, const char *text, uint32_t color); void kon_measureText(kon_font_t *font, const char *text, int *outWidth, int *outHeight); /*** implementatoin ***/ #ifdef KONFONT_IMPLEMENTATION #include "stb_truetype.h" struct kon_font { int builtin, scale; const uint8_t *ttf; stbtt_bakedchar chardata[KON_FONT_NUM_CHARS]; uint8_t *atlasPixels; int atlasSize; float pixelHeight; }; /*** built in font ***/ /* 8x13 glyphs for ascii 32 to 126, one byte per row, leftmost pixel is the top bit. public domain misc-fixed derived font */ static const unsigned char kon_builtinGlyphs_[KON_FONT_NUM_CHARS][13] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* */ { 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x00 }, /* ! */ { 0x00, 0x00, 0x24, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* " */ { 0x00, 0x00, 0x00, 0x24, 0x24, 0x7E, 0x24, 0x7E, 0x24, 0x24, 0x00, 0x00, 0x00 }, /* # */ { 0x00, 0x00, 0x10, 0x3C, 0x50, 0x50, 0x38, 0x14, 0x14, 0x78, 0x10, 0x00, 0x00 }, /* $ */ { 0x00, 0x00, 0x22, 0x52, 0x24, 0x08, 0x08, 0x10, 0x24, 0x2A, 0x44, 0x00, 0x00 }, /* % */ { 0x00, 0x00, 0x00, 0x00, 0x30, 0x48, 0x48, 0x30, 0x4A, 0x44, 0x3A, 0x00, 0x00 }, /* & */ { 0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* ' */ { 0x00, 0x00, 0x04, 0x08, 0x08, 0x10, 0x10, 0x10, 0x08, 0x08, 0x04, 0x00, 0x00 }, /* ( */ { 0x00, 0x00, 0x20, 0x10, 0x10, 0x08, 0x08, 0x08, 0x10, 0x10, 0x20, 0x00, 0x00 }, /* ) */ { 0x00, 0x00, 0x00, 0x00, 0x24, 0x18, 0x7E, 0x18, 0x24, 0x00, 0x00, 0x00, 0x00 }, /* ch42 */ { 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x7C, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00 }, /* + */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x30, 0x40, 0x00 }, /* , */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* - */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x10, 0x00 }, /* . */ { 0x00, 0x00, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x80, 0x00, 0x00 }, /* ch47 */ { 0x00, 0x00, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x42, 0x24, 0x18, 0x00, 0x00 }, /* 0 */ { 0x00, 0x00, 0x10, 0x30, 0x50, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00 }, /* 1 */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x02, 0x04, 0x18, 0x20, 0x40, 0x7E, 0x00, 0x00 }, /* 2 */ { 0x00, 0x00, 0x7E, 0x02, 0x04, 0x08, 0x1C, 0x02, 0x02, 0x42, 0x3C, 0x00, 0x00 }, /* 3 */ { 0x00, 0x00, 0x04, 0x0C, 0x14, 0x24, 0x44, 0x44, 0x7E, 0x04, 0x04, 0x00, 0x00 }, /* 4 */ { 0x00, 0x00, 0x7E, 0x40, 0x40, 0x5C, 0x62, 0x02, 0x02, 0x42, 0x3C, 0x00, 0x00 }, /* 5 */ { 0x00, 0x00, 0x1C, 0x20, 0x40, 0x40, 0x5C, 0x62, 0x42, 0x42, 0x3C, 0x00, 0x00 }, /* 6 */ { 0x00, 0x00, 0x7E, 0x02, 0x04, 0x08, 0x08, 0x10, 0x10, 0x20, 0x20, 0x00, 0x00 }, /* 7 */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x3C, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00 }, /* 8 */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x46, 0x3A, 0x02, 0x02, 0x04, 0x38, 0x00, 0x00 }, /* 9 */ { 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x10, 0x00, 0x00, 0x10, 0x38, 0x10, 0x00 }, /* : */ { 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x10, 0x00, 0x00, 0x38, 0x30, 0x40, 0x00 }, /* ; */ { 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00 }, /* < */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00 }, /* = */ { 0x00, 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00 }, /* > */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x02, 0x04, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00 }, /* ? */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x4E, 0x52, 0x56, 0x4A, 0x40, 0x3C, 0x00, 0x00 }, /* @ */ { 0x00, 0x00, 0x18, 0x24, 0x42, 0x42, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x00, 0x00 }, /* A */ { 0x00, 0x00, 0x78, 0x44, 0x42, 0x44, 0x78, 0x44, 0x42, 0x44, 0x78, 0x00, 0x00 }, /* B */ { 0x00, 0x00, 0x3C, 0x42, 0x40, 0x40, 0x40, 0x40, 0x40, 0x42, 0x3C, 0x00, 0x00 }, /* C */ { 0x00, 0x00, 0x78, 0x44, 0x42, 0x42, 0x42, 0x42, 0x42, 0x44, 0x78, 0x00, 0x00 }, /* D */ { 0x00, 0x00, 0x7E, 0x40, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x7E, 0x00, 0x00 }, /* E */ { 0x00, 0x00, 0x7E, 0x40, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00 }, /* F */ { 0x00, 0x00, 0x3C, 0x42, 0x40, 0x40, 0x40, 0x4E, 0x42, 0x46, 0x3A, 0x00, 0x00 }, /* G */ { 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00 }, /* H */ { 0x00, 0x00, 0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00 }, /* I */ { 0x00, 0x00, 0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00 }, /* J */ { 0x00, 0x00, 0x42, 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x42, 0x00, 0x00 }, /* K */ { 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7E, 0x00, 0x00 }, /* L */ { 0x00, 0x00, 0x82, 0x82, 0xC6, 0xAA, 0x92, 0x92, 0x82, 0x82, 0x82, 0x00, 0x00 }, /* M */ { 0x00, 0x00, 0x42, 0x42, 0x62, 0x52, 0x4A, 0x46, 0x42, 0x42, 0x42, 0x00, 0x00 }, /* N */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00 }, /* O */ { 0x00, 0x00, 0x7C, 0x42, 0x42, 0x42, 0x7C, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00 }, /* P */ { 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x52, 0x4A, 0x3C, 0x02, 0x00 }, /* Q */ { 0x00, 0x00, 0x7C, 0x42, 0x42, 0x42, 0x7C, 0x50, 0x48, 0x44, 0x42, 0x00, 0x00 }, /* R */ { 0x00, 0x00, 0x3C, 0x42, 0x40, 0x40, 0x3C, 0x02, 0x02, 0x42, 0x3C, 0x00, 0x00 }, /* S */ { 0x00, 0x00, 0xFE, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00 }, /* T */ { 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00 }, /* U */ { 0x00, 0x00, 0x82, 0x82, 0x44, 0x44, 0x44, 0x28, 0x28, 0x28, 0x10, 0x00, 0x00 }, /* V */ { 0x00, 0x00, 0x82, 0x82, 0x82, 0x82, 0x92, 0x92, 0x92, 0xAA, 0x44, 0x00, 0x00 }, /* W */ { 0x00, 0x00, 0x82, 0x82, 0x44, 0x28, 0x10, 0x28, 0x44, 0x82, 0x82, 0x00, 0x00 }, /* X */ { 0x00, 0x00, 0x82, 0x82, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00 }, /* Y */ { 0x00, 0x00, 0x7E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x40, 0x7E, 0x00, 0x00 }, /* Z */ { 0x00, 0x00, 0x3C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3C, 0x00, 0x00 }, /* [ */ { 0x00, 0x00, 0x80, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x02, 0x00, 0x00 }, /* ch92 */ { 0x00, 0x00, 0x78, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x78, 0x00, 0x00 }, /* ] */ { 0x00, 0x00, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* ^ */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00 }, /* _ */ { 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* ` */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x02, 0x3E, 0x42, 0x46, 0x3A, 0x00, 0x00 }, /* a */ { 0x00, 0x00, 0x40, 0x40, 0x40, 0x5C, 0x62, 0x42, 0x42, 0x62, 0x5C, 0x00, 0x00 }, /* b */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x40, 0x40, 0x42, 0x3C, 0x00, 0x00 }, /* c */ { 0x00, 0x00, 0x02, 0x02, 0x02, 0x3A, 0x46, 0x42, 0x42, 0x46, 0x3A, 0x00, 0x00 }, /* d */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x7E, 0x40, 0x42, 0x3C, 0x00, 0x00 }, /* e */ { 0x00, 0x00, 0x1C, 0x22, 0x20, 0x20, 0x7C, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00 }, /* f */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x44, 0x44, 0x38, 0x40, 0x3C, 0x42, 0x3C }, /* g */ { 0x00, 0x00, 0x40, 0x40, 0x40, 0x5C, 0x62, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00 }, /* h */ { 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00 }, /* i */ { 0x00, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x44, 0x44, 0x38 }, /* j */ { 0x00, 0x00, 0x40, 0x40, 0x40, 0x44, 0x48, 0x70, 0x48, 0x44, 0x42, 0x00, 0x00 }, /* k */ { 0x00, 0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00 }, /* l */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0x92, 0x92, 0x92, 0x92, 0x82, 0x00, 0x00 }, /* m */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0x62, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00 }, /* n */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00 }, /* o */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0x62, 0x42, 0x62, 0x5C, 0x40, 0x40, 0x40 }, /* p */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x46, 0x42, 0x46, 0x3A, 0x02, 0x02, 0x02 }, /* q */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0x22, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00 }, /* r */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x30, 0x0C, 0x42, 0x3C, 0x00, 0x00 }, /* s */ { 0x00, 0x00, 0x00, 0x20, 0x20, 0x7C, 0x20, 0x20, 0x20, 0x22, 0x1C, 0x00, 0x00 }, /* t */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x3A, 0x00, 0x00 }, /* u */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x28, 0x10, 0x00, 0x00 }, /* v */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x82, 0x92, 0x92, 0xAA, 0x44, 0x00, 0x00 }, /* w */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 0x00 }, /* x */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42, 0x42, 0x46, 0x3A, 0x02, 0x42, 0x3C }, /* y */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E, 0x00, 0x00 }, /* z */ { 0x00, 0x00, 0x0E, 0x10, 0x10, 0x08, 0x30, 0x08, 0x10, 0x10, 0x0E, 0x00, 0x00 }, /* { */ { 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00 }, /* | */ { 0x00, 0x00, 0x70, 0x08, 0x08, 0x10, 0x0C, 0x10, 0x08, 0x08, 0x70, 0x00, 0x00 }, /* } */ { 0x00, 0x00, 0x24, 0x54, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* ~ */ }; #define KON_BUILTIN_WIDTH 8 #define KON_BUILTIN_HEIGHT 13 #define KON_BUILTIN_BASELINE 10 #define KON_BUILTIN_MAX_SCALE 8 static void kon_fontDrawBuiltin_(kon_framebuffer_t *fb, const kon_font_t *font, int x, int y, const char *text, uint32_t color) { int s = font->scale; int top = y - KON_BUILTIN_BASELINE * s; color = (color & 0x00FFFFFFu) | 0xFF000000u; for (const char *p = text; *p; p++) { unsigned char c = (unsigned char)*p; if (c < KON_FONT_FIRST_CHAR || c >= KON_FONT_FIRST_CHAR + KON_FONT_NUM_CHARS) continue; for (int row = 0; row < KON_BUILTIN_HEIGHT; row++) { unsigned char bits = kon_builtinGlyphs_[c - KON_FONT_FIRST_CHAR][row]; for (int bit = 0; bit < KON_BUILTIN_WIDTH; ) { if (!(bits & (0x80 >> bit))) { bit++; continue; } int start = bit; while (bit < KON_BUILTIN_WIDTH && (bits & (0x80 >> bit))) bit++; kon_fillRectangle(fb, x + start * s, top + row * s, (bit - start) * s, s, color); } } x += KON_BUILTIN_WIDTH * s; } } kon_font_t *kon_loadBuiltinFont(int scale) { if (scale < 1 || scale > KON_BUILTIN_MAX_SCALE) return NULL; kon_font_t *font = calloc(1, sizeof(kon_font_t)); if (!font) return NULL; font->builtin = 1; font->scale = scale; font->pixelHeight = (float)(KON_BUILTIN_HEIGHT * scale); return font; } /*** private helpers ***/ static bool kon_fontBakeAtlas(kon_font_t *font, const uint8_t *ttf_data, float pixelHeight) { int sizes[] = {256, 512, 1024, 2048}; for (int i = 0; i < 4; i++) { int atlasSize = sizes[i]; uint8_t *bitmap = malloc((size_t)atlasSize * (size_t)atlasSize); if (!bitmap) return false; int result = stbtt_BakeFontBitmap(ttf_data, 0, pixelHeight, bitmap, atlasSize, atlasSize, KON_FONT_FIRST_CHAR, KON_FONT_NUM_CHARS, font->chardata); if (result > 0) { font->atlasPixels = bitmap; font->atlasSize = atlasSize; return true; } free(bitmap); } return false; } /*** font implementation ***/ kon_font_t *kon_loadFont(const uint8_t *ttf_data, size_t size, float pixelHeight) { (void)size; if (!ttf_data || pixelHeight <= 0.0f) return NULL; kon_font_t *font = calloc(1, sizeof(kon_font_t)); if (!font) return NULL; font->ttf = ttf_data; font->atlasPixels = NULL; font->pixelHeight = pixelHeight; if (!kon_fontBakeAtlas(font, ttf_data, pixelHeight)) { free(font); return NULL; } return font; } void kon_freeFont(kon_font_t *font) { if (!font) return; free(font->atlasPixels); free(font); } float kon_fontPixelHeight(const kon_font_t *font) { return font ? font->pixelHeight : 0.0f; } int kon_fontResize(kon_font_t *font, float pixelHeight) { kon_font_t next; if (!font || pixelHeight <= 0.0f) return 0; if (font->builtin) { int scale = (int)(pixelHeight / (float)KON_BUILTIN_HEIGHT + 0.5f); if (scale < 1) scale = 1; if (scale > KON_BUILTIN_MAX_SCALE) scale = KON_BUILTIN_MAX_SCALE; font->scale = scale; font->pixelHeight = (float)(KON_BUILTIN_HEIGHT * scale); return 1; } next = *font; next.atlasPixels = NULL; if (!kon_fontBakeAtlas(&next, font->ttf, pixelHeight)) return 0; free(font->atlasPixels); next.pixelHeight = pixelHeight; *font = next; return 1; } kon_image *kon_fontGetAtlas(kon_font_t *font) { if (!font || font->builtin) return NULL; int n = font->atlasSize * font->atlasSize; uint8_t *rgba = malloc((size_t)n * 4); if (!rgba) return NULL; for (int i = 0; i < n; i++) { uint8_t a = font->atlasPixels[i]; rgba[i * 4 + 0] = 255; rgba[i * 4 + 1] = 255; rgba[i * 4 + 2] = 255; rgba[i * 4 + 3] = a; } kon_image *img = kon_loadImage(rgba, font->atlasSize, font->atlasSize, konFormatRGBA8); free(rgba); return img; } /*** text draw implementation ***/ void kon_drawText(kon_framebuffer_t *fb, kon_font_t *font, int x, int y, const char *text, uint32_t color) { if (!fb || !font || !text) return; if (font->builtin) { kon_fontDrawBuiltin_(fb, font, x, y, text, color); return; } uint32_t baseColor = color & 0x00FFFFFF; float cursorX = (float)x; float cursorY = (float)y; for (const char *p = text; *p; p++) { unsigned char c = (unsigned char)*p; if (c < KON_FONT_FIRST_CHAR || c >= KON_FONT_FIRST_CHAR + KON_FONT_NUM_CHARS) continue; stbtt_aligned_quad q; stbtt_GetBakedQuad(font->chardata, font->atlasSize, font->atlasSize, c - KON_FONT_FIRST_CHAR, &cursorX, &cursorY, &q, 0); int src_x0 = (int)(q.s0 * font->atlasSize); int src_y0 = (int)(q.t0 * font->atlasSize); int glyphW = (int)(q.x1 - q.x0); int glyphH = (int)(q.y1 - q.y0); int dstX = (int)q.x0; int dstY = (int)q.y0; int gx0 = 0, gy0 = 0; if (dstX < 0) gx0 = -dstX; if (dstY < 0) gy0 = -dstY; int gx1 = glyphW, gy1 = glyphH; if (dstX + gx1 > fb->width) gx1 = fb->width - dstX; if (dstY + gy1 > fb->height) gy1 = fb->height - dstY; for (int gy = gy0; gy < gy1; gy++) { const uint8_t *in = font->atlasPixels + (size_t)(src_y0 + gy) * font->atlasSize + src_x0; uint32_t *out = fb->data + (size_t)(dstY + gy) * fb->width + dstX; for (int gx = gx0; gx < gx1; gx++) { uint32_t alpha = in[gx]; if (alpha == 0) continue; if (alpha == 0xFF) { out[gx] = 0xFF000000u | baseColor; continue; } uint32_t dst = out[gx]; uint32_t inv = 255 - alpha; uint32_t r = (((baseColor >> 16) & 0xFF) * alpha + ((dst >> 16) & 0xFF) * inv) / 255; uint32_t g = (((baseColor >> 8) & 0xFF) * alpha + ((dst >> 8) & 0xFF) * inv) / 255; uint32_t b = ((baseColor & 0xFF) * alpha + (dst & 0xFF) * inv) / 255; out[gx] = (dst & 0xFF000000u) | (r << 16) | (g << 8) | b; } } } } void kon_measureText(kon_font_t *font, const char *text, int *outWidth, int *outHeight) { if (!font || !text) return; if (font->builtin) { int count = 0; for (const char *p = text; *p; p++) { unsigned char c = (unsigned char)*p; if (c >= KON_FONT_FIRST_CHAR && c < KON_FONT_FIRST_CHAR + KON_FONT_NUM_CHARS) count++; } if (outWidth) *outWidth = count * KON_BUILTIN_WIDTH * font->scale; if (outHeight) *outHeight = KON_BUILTIN_HEIGHT * font->scale; return; } float cursorX = 0.0f; float cursorY = 0.0f; float maxX = 0.0f; for (const char *p = text; *p; p++) { unsigned char c = (unsigned char)*p; if (c < KON_FONT_FIRST_CHAR || c >= KON_FONT_FIRST_CHAR + KON_FONT_NUM_CHARS) continue; stbtt_aligned_quad q; stbtt_GetBakedQuad(font->chardata, font->atlasSize, font->atlasSize, c - KON_FONT_FIRST_CHAR, &cursorX, &cursorY, &q, 0); if (cursorX > maxX) maxX = cursorX; } if (outWidth) *outWidth = (int)maxX; if (outHeight) *outHeight = (int)font->pixelHeight; } #endif /* KONFONT_IMPLEMENTATION */ #endif /* KONFONT_H */