nano-ui-sdl-0.1.0.0: cbits/nano_ui_text_atlas.c
#include "nano_ui_text_atlas.h"
#include <SDL3/SDL.h>
#include <stdlib.h>
#include <string.h>
enum {
NANO_UI_TEXT_ATLAS_SIZE = 2048,
NANO_UI_TEXT_ATLAS_PAD = 1,
NANO_UI_WHITE_PATCH_SIZE = 4
};
struct NanoUiTextAtlas {
SDL_Renderer *renderer;
SDL_Texture *tex;
Uint8 *pixels;
int w;
int h;
int x;
int y;
int row_h;
};
static void init_white_pixel(NanoUiTextAtlas *atlas)
{
if (!atlas || !atlas->pixels || atlas->w <= 0 || atlas->h <= 0) {
return;
}
for (int y = 0; y < NANO_UI_WHITE_PATCH_SIZE && y < atlas->h; y++) {
for (int x = 0; x < NANO_UI_WHITE_PATCH_SIZE && x < atlas->w; x++) {
size_t off = ((size_t)y * (size_t)atlas->w + (size_t)x) * 4;
atlas->pixels[off + 0] = 255;
atlas->pixels[off + 1] = 255;
atlas->pixels[off + 2] = 255;
atlas->pixels[off + 3] = 255;
}
}
}
static bool upload_all(NanoUiTextAtlas *atlas)
{
if (!atlas->tex || !atlas->pixels || atlas->w <= 0 || atlas->h <= 0) {
return false;
}
return SDL_UpdateTexture(atlas->tex, NULL, atlas->pixels, atlas->w * 4);
}
static bool create_texture(NanoUiTextAtlas *atlas, int w, int h)
{
SDL_Texture *tex =
SDL_CreateTexture(atlas->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, w, h);
if (!tex) {
return false;
}
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
/* Bilinear filtering keeps glyph quads smooth when a quad lands off a
* whole texel boundary (fractional display scale, sub-pixel pen nudge,
* shaped-run placement). NEAREST snaps to the closest texel and makes
* scaled/slightly-misaligned text look blocky and pixelated. */
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_LINEAR);
Uint8 *px = (Uint8 *)calloc((size_t)w * (size_t)h, 4);
if (!px) {
SDL_DestroyTexture(tex);
return false;
}
atlas->tex = tex;
atlas->pixels = px;
atlas->w = w;
atlas->h = h;
init_white_pixel(atlas);
return upload_all(atlas);
}
static bool slot_for(NanoUiTextAtlas *atlas, int gw, int gh, int *out_x, int *out_y)
{
int pad = NANO_UI_TEXT_ATLAS_PAD;
if (gw + 2 * pad > NANO_UI_TEXT_ATLAS_SIZE || gh + 2 * pad > NANO_UI_TEXT_ATLAS_SIZE) {
return false;
}
if (!atlas->tex) {
if (!create_texture(atlas, NANO_UI_TEXT_ATLAS_SIZE, NANO_UI_TEXT_ATLAS_SIZE)) {
return false;
}
atlas->x = NANO_UI_WHITE_PATCH_SIZE + pad;
atlas->y = pad;
atlas->row_h = NANO_UI_WHITE_PATCH_SIZE;
}
if (atlas->x + gw + pad <= atlas->w && atlas->y + gh + pad <= atlas->h) {
*out_x = atlas->x;
*out_y = atlas->y;
return true;
}
int next_y = atlas->y + (atlas->row_h > 0 ? atlas->row_h + pad : pad);
if (next_y + gh + pad <= atlas->h && gw + 2 * pad <= atlas->w) {
atlas->y = next_y;
atlas->x = pad;
atlas->row_h = 0;
*out_x = atlas->x;
*out_y = atlas->y;
return true;
}
return false;
}
/* Glyph surfaces arrive as RGBA32 (glyph_image_to_rgba in nano_ui_ttf.c). */
static bool blit_surface(NanoUiTextAtlas *atlas, SDL_Surface *surface, int x, int y)
{
const Uint8 *src = (const Uint8 *)surface->pixels;
int w = surface->w;
int h = surface->h;
for (int row = 0; row < h; row++) {
Uint8 *dst = atlas->pixels + ((y + row) * atlas->w + x) * 4;
memcpy(dst, src + (size_t)row * (size_t)surface->pitch, (size_t)w * 4);
}
SDL_Rect rect = {x, y, w, h};
return SDL_UpdateTexture(atlas->tex, &rect, atlas->pixels + (y * atlas->w + x) * 4, atlas->w * 4);
}
NanoUiTextAtlas *nano_ui_text_atlas_create(SDL_Renderer *renderer)
{
if (!renderer) {
return NULL;
}
NanoUiTextAtlas *atlas = (NanoUiTextAtlas *)calloc(1, sizeof(NanoUiTextAtlas));
if (!atlas) {
return NULL;
}
atlas->renderer = renderer;
return atlas;
}
void nano_ui_text_atlas_destroy(NanoUiTextAtlas *atlas)
{
if (!atlas) {
return;
}
if (atlas->tex) {
SDL_DestroyTexture(atlas->tex);
}
free(atlas->pixels);
free(atlas);
}
SDL_Texture *nano_ui_text_atlas_texture(NanoUiTextAtlas *atlas)
{
return atlas ? atlas->tex : NULL;
}
bool nano_ui_text_atlas_insert_surface(
NanoUiTextAtlas *atlas,
SDL_Surface *surface,
float *out_x,
float *out_y,
float *out_w,
float *out_h)
{
if (!atlas || !surface) {
return false;
}
int gw = surface->w;
int gh = surface->h;
if (gw <= 0 || gh <= 0) {
return false;
}
int x = 0;
int y = 0;
if (!slot_for(atlas, gw, gh, &x, &y)) {
return false;
}
if (!blit_surface(atlas, surface, x, y)) {
return false;
}
atlas->x = x + gw + NANO_UI_TEXT_ATLAS_PAD;
if (gh > atlas->row_h) {
atlas->row_h = gh;
}
if (out_x) {
*out_x = (float)x;
}
if (out_y) {
*out_y = (float)y;
}
if (out_w) {
*out_w = (float)gw;
}
if (out_h) {
*out_h = (float)gh;
}
return true;
}
void nano_ui_text_atlas_reset(NanoUiTextAtlas *atlas)
{
if (!atlas) {
return;
}
atlas->x = NANO_UI_WHITE_PATCH_SIZE + NANO_UI_TEXT_ATLAS_PAD;
atlas->y = NANO_UI_TEXT_ATLAS_PAD;
atlas->row_h = NANO_UI_WHITE_PATCH_SIZE;
if (atlas->pixels && atlas->w > 0 && atlas->h > 0) {
memset(atlas->pixels, 0, (size_t)atlas->w * (size_t)atlas->h * 4);
init_white_pixel(atlas);
upload_all(atlas);
}
}