packages feed

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);
    }
}