/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "text.h" #include "assert/assert.h" #include "util/memory.h" #include "display/spritebatch/spritebatch.h" #include "asset/asset.h" #include "asset/loader/display/assettextureloader.h" #include "asset/loader/display/assettilesetloader.h" #include "display/shader/shaderunlit.h" font_t FONT_DEFAULT; errorret_t textInit(void) { assetloaderinput_t input = { .texture = TEXTURE_FORMAT_RGBA }; assetentry_t *entryTexture = assetLock( "ui/minogram.png", ASSET_LOADER_TYPE_TEXTURE, &input ); assetentry_t *entryTileset = assetLock( "ui/minogram.dtf", ASSET_LOADER_TYPE_TILESET, NULL ); errorChain(assetRequireLoaded(entryTexture)); errorChain(assetRequireLoaded(entryTileset)); FONT_DEFAULT.texture = &entryTexture->data.texture; FONT_DEFAULT.tileset = &entryTileset->data.tileset; errorOk(); } errorret_t textDispose(void) { FONT_DEFAULT.texture = NULL; FONT_DEFAULT.tileset = NULL; assetUnlock("ui/minogram.png"); assetUnlock("ui/minogram.dtf"); errorOk(); } spritebatchsprite_t textGetSprite( const vec2 pos, const char_t c, const font_t *font ) { assertNotNull(font, "Font cannot be NULL"); // Change char from ASCII to a tile index. int32_t tileIndex = (int32_t)(c) - TEXT_CHAR_START; if(tileIndex < 0 || tileIndex >= font->tileset->tileCount) { tileIndex = ((int32_t)'@') - TEXT_CHAR_START; } assertTrue( tileIndex >= 0 && tileIndex <= font->tileset->tileCount, "Character is out of bounds for font tiles" ); // Create sprite. vec4 uv; tilesetTileGetUV(font->tileset, tileIndex, uv); spritebatchsprite_t sprite; sprite.min[0] = pos[0]; sprite.min[1] = pos[1]; sprite.min[2] = 0.0f; sprite.max[0] = pos[0] + font->tileset->tileWidth; sprite.max[1] = pos[1] + font->tileset->tileHeight; sprite.max[2] = 0.0f; sprite.uvMin[0] = uv[0]; sprite.uvMin[1] = uv[1]; sprite.uvMax[0] = uv[2]; sprite.uvMax[1] = uv[3]; return sprite; } errorret_t textDraw( const float_t x, const float_t y, const char_t *text, const color_t color, font_t *font ) { assertNotNull(text, "Text cannot be NULL"); spritebatchsprite_t sprite; shadermaterial_t material = { .unlit = { .color = color, .texture = font->texture } }; float_t posX = x; float_t posY = y; char_t c; int32_t i = 0; while((c = text[i++]) != '\0') { if(c == '\n') { posX = x; posY += font->tileset->tileHeight; continue; } if(c == ' ') { posX += font->tileset->tileWidth; continue; } sprite = textGetSprite((vec2){posX, posY}, c, font); errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material)); posX += font->tileset->tileWidth; } errorOk(); } void textMeasure( const char_t *text, const font_t *font, int32_t *outWidth, int32_t *outHeight ) { assertNotNull(text, "Text cannot be NULL"); assertNotNull(outWidth, "Output width pointer cannot be NULL"); assertNotNull(outHeight, "Output height pointer cannot be NULL"); int32_t width = 0; int32_t height = font->tileset->tileHeight; int32_t lineWidth = 0; char_t c; int32_t i = 0; while((c = text[i++]) != '\0') { if(c == '\n') { if(lineWidth > width) width = lineWidth; lineWidth = 0; height += font->tileset->tileHeight; continue; } lineWidth += font->tileset->tileWidth; } if(lineWidth > width) width = lineWidth; *outWidth = width; *outHeight = height; }