// SPDX-License-Identifier: Zlib OR MIT
//
// Copyright (c) 2024 Antonio Niño Díaz

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <nds.h>

#include <dsf.h>

// BMFont format structures
// ------------------------

typedef struct __attribute__((packed)) {
    uint8_t magic[3]; // "BMF"
    uint8_t version;  // 3
} bmf_header;

typedef struct __attribute__((packed)) {
    uint8_t type;     // 1, 2, 3, 4 or 5
    uint8_t size[4];  // Size of the block
    uint8_t data[];
} bmf_block_header;

typedef struct __attribute__((packed)) {
    uint16_t line_height; // Distance in pixels between each line of text.
    uint16_t base; // Number of pixels from the top of the line to the base of the characters.
    uint16_t scale_w;
    uint16_t scale_h;
    uint16_t pages;
    uint8_t  bit_field;
    uint8_t  alpha_channel;
    uint8_t  red_channel;
    uint8_t  green_channel;
    uint8_t  blue_channel;
} bmf_block_2_common;

// The number of characters in one file can be calculated by calculating the
// result of "size / sizeof(bmf_block_4_char)"
typedef struct __attribute__((packed)) {
    uint32_t id;
    uint16_t x;
    uint16_t y;
    uint16_t width;
    uint16_t height;
    int16_t  xoffset;
    int16_t  yoffset;
    int16_t  xadvance;
    uint8_t  page;
    uint8_t  channel;
} bmf_block_4_char;

typedef struct __attribute__((packed)) {
    uint32_t first;
    uint32_t second;
    uint16_t amount;
} bmf_block_5_kerning_pair;

// Internal data structures
// ------------------------

typedef bmf_block_4_char block_char;

typedef struct {
    uint32_t first;
    uint32_t second;
    int16_t  amount;
} kerning_pair;

typedef struct {
    uint16_t      line_height;
    uint16_t      base;

    size_t        num_chars;
    block_char   *chars;
    size_t        num_kernings;
    kerning_pair *kernings;

    // Variables used for the current printing context
    int16_t       pointer_x;
    int16_t       pointer_y;
    int16_t       box_left;
    int16_t       box_top;
    uint32_t      last_codepoint;

    // If the font includes a replacement character glyph, its pointer will be
    // saved here for ease of access.
    const block_char *replacement_character;
} dsf_font_internal_state;

// Functions
// ---------

static int DSF_block_char_cmp(const void *a, const void *b)
{
    const block_char *a_ = a;
    const block_char *b_ = b;
    return a_->id - b_->id;
}

static int DSF_kerning_pair_cmp(const void *a, const void *b)
{
    const kerning_pair *a_ = a;
    const kerning_pair *b_ = b;

    // Compare the first codepoint. If it matches, compare the second codepoint.
    if (a_->first != b_->first)
        return a_->first - b_->first;

    return a_->second - b_->second;
}

static const block_char *DSF_CodepointFindGlyph(dsf_handle handle,
                                                uint32_t codepoint)
{
    dsf_font_internal_state *font = (dsf_font_internal_state *)handle;

    // Codepoint to find
    block_char key = { 0 };
    key.id = codepoint;

    const block_char *ch = bsearch(&key, font->chars, font->num_chars,
                                   sizeof(block_char), DSF_block_char_cmp);
    if (ch == NULL)
        return font->replacement_character;

    return ch;
}

static dsf_error DSF_LoadFile(const char *path, void **data, size_t *_size)
{
    FILE *f = fopen(path, "rb");
    if (f == NULL)
        return DSF_FILE_OPEN_ERROR;

    if (fseek(f, 0, SEEK_END) != 0)
    {
        fclose(f);
        return DSF_FILE_SEEK_ERROR;
    }

    size_t size = ftell(f);
    if (size == 0)
    {
        fclose(f);
        return DSF_FILE_EMPTY;
    }

    rewind(f);

    char *buffer = malloc(size);
    if (buffer == NULL)
    {
        fclose(f);
        return DSF_NO_MEMORY;
    }

    if (fread(buffer, 1, size, f) != size)
    {
        fclose(f);
        return DSF_FILE_READ_ERROR;
    }

    fclose(f);

    *_size = size;
    *data = buffer;
    return DSF_NO_ERROR;
}

dsf_error DSF_LoadFontMemory(dsf_handle *handle,
                             const void *data, int32_t data_size)
{
    dsf_error ret = DSF_NO_ERROR;
    const uint8_t *ptr = data;

    // Basic checks

    if ((handle == NULL) || (data == NULL) || (data_size <= 0))
        return DSF_INVALID_ARGUMENT;

    // Read header

    const bmf_header *header = (const bmf_header *)ptr;

    if (!((header->magic[0] == 'B') && (header->magic[1] == 'M') &&
          (header->magic[2] == 'F')))
        return DSF_BAD_MAGIC;

    if (header->version != 3)
        return DSF_BAD_VERSION;

    ptr += sizeof(bmf_header);
    data_size -= sizeof(bmf_header);

    // Allocate space for the handle

    dsf_font_internal_state *font = calloc(1, sizeof(dsf_font_internal_state));
    if (font == NULL)
        return DSF_NO_MEMORY;

    *handle = (dsf_handle)font; // Return handle to user

    // Read blocks

    while (1)
    {
        const bmf_block_header *block_header = (const bmf_block_header *)ptr;

        uint8_t type = block_header->type;
        uint32_t block_size = ((uint32_t)block_header->size[0]) |
                              ((uint32_t)block_header->size[1] << 8) |
                              ((uint32_t)block_header->size[2] << 16) |
                              ((uint32_t)block_header->size[3] << 24);

        const uint8_t *data = ptr + 1 + 4;

        if (type == 2)
        {
            // Ensure the size is correct
            if (block_size != sizeof(bmf_block_2_common))
            {
                ret = DSF_BAD_CHUNK_SIZE;
                goto error;
            }

            bmf_block_2_common block_common;
            memcpy(&block_common, data, block_size);

            font->line_height = block_common.line_height;
            font->base = block_common.base;
        }
        else if (type == 4)
        {
            // Ensure the total size is a multiple of the block size
            if (block_size % sizeof(bmf_block_4_char) != 0)
            {
                ret = DSF_BAD_CHUNK_SIZE;
                goto error;
            }

            font->num_chars = block_size / sizeof(bmf_block_4_char);
            font->chars = calloc(font->num_chars, sizeof(block_char));
            if (font->chars == NULL)
            {
                ret = DSF_NO_MEMORY;
                goto error;
            }

            memcpy(font->chars, data, block_size);

            qsort(font->chars, font->num_chars, sizeof(block_char),
                  DSF_block_char_cmp);
        }
        else if (type == 5)
        {
            // Ensure the total size is a multiple of the block size
            if (block_size % sizeof(bmf_block_5_kerning_pair) != 0)
            {
                ret = DSF_BAD_CHUNK_SIZE;
                goto error;
            }

            font->num_kernings = block_size / sizeof(bmf_block_5_kerning_pair);
            font->kernings = calloc(font->num_kernings, sizeof(kerning_pair));
            if (font->kernings == NULL)
            {
                ret = DSF_NO_MEMORY;
                goto error;
            }

            const uint8_t *src = data;
            uint8_t *dst = (uint8_t *)font->kernings;

            for (size_t i = 0; i < font->num_kernings; i++)
            {
                memcpy(dst, src, sizeof(bmf_block_5_kerning_pair));
                dst += sizeof(kerning_pair);
                src += sizeof(bmf_block_5_kerning_pair);
            }

            qsort(font->kernings, font->num_kernings, sizeof(kerning_pair),
                  DSF_kerning_pair_cmp);
        }

        ptr += block_size + 1 + 4;
        data_size -= block_size + 1 + 4;

        if (data_size == 0)
            break;

        if (data_size < 0)
        {
            ret = DSF_UNEXPECTED_END;
            goto error;
        }
    }

    if (font->num_chars == 0)
    {
        ret = DSF_NO_CHARACTERS;
        goto error;
    }

    // Look for a replacement character glyph in the font.

    // Initialize the value to NULL so that DSF_CodepointFindGlyph() returns
    // NULL if no replacement character glyph is found.
    font->replacement_character = NULL;
    font->replacement_character =
                    DSF_CodepointFindGlyph(*handle, REPLACEMENT_CHARACTER);

    return DSF_NO_ERROR;

error:
    free(font->chars);
    free(font->kernings);
    free(font);
    return ret;
}

dsf_error DSF_FreeFont(dsf_handle *handle)
{
    if (handle == NULL)
        return DSF_INVALID_ARGUMENT;

    dsf_font_internal_state *font = (dsf_font_internal_state *)*handle;

    free(font->chars);
    free(font->kernings);
    free(font);

    *handle = 0;

    return DSF_NO_ERROR;
}

dsf_error DSF_LoadFontFilesystem(dsf_handle *handle, const char *path)
{
    if ((handle == NULL) || (path == NULL))
        return DSF_INVALID_ARGUMENT;

    size_t size;
    void *data;
    dsf_error error = DSF_LoadFile(path, &data, &size);
    if (error != DSF_NO_ERROR)
        return error;

    error = DSF_LoadFontMemory(handle, data, size);
    free(data);
    return error;
}

static size_t DSF_UTF8_CodepointRead(const char *str, uint32_t *codepoint)
{
    // https://en.wikipedia.org/wiki/UTF-8#Encoding

    size_t size;
    uint32_t rune;

    uint32_t b1 = str[0];

    if ((b1 & 0x80) == 0)
    {
        size = 1;
        *codepoint = b1 & 0x7F;
        return 1;
    }
    else if ((b1 & 0xE0) == 0xC0)
    {
        size = 2;
        rune = b1 & 0x1F;
    }
    else if ((b1 & 0xF0) == 0xE0)
    {
        size = 3;
        rune = b1 & 0x0F;
    }
    else if ((b1 & 0xF8) == 0xF0)
    {
        size = 4;
        rune = b1 & 0x07;
    }
    else
    {
        goto error;
    }

    for (size_t i = 1; i < size; i++)
    {
        uint32_t b = str[i];
        if ((b & 0xC0) != 0x80)
            goto error;

        rune <<= 6;
        rune |= b & 0x3F;
    }

    *codepoint = rune;
    return size;

error:

    // Incorrect encoding. Advance characters until we find one character
    // that isn't a continuation character.
    size = 1;
    str++;

    while (1)
    {
        uint8_t c = *str++;
        size++;
        if ((c & 0xC0) != 0x80)
            break;
    }

    *codepoint = REPLACEMENT_CHARACTER;
    return size;
}

dsf_error DSF_CodepointRenderDryRun(dsf_handle handle, uint32_t codepoint)
{
    if ((handle == 0) || (codepoint == 0))
        return DSF_INVALID_ARGUMENT;

    dsf_font_internal_state *font = (dsf_font_internal_state *)handle;

    if (codepoint == '\n')
    {
        font->pointer_x = font->box_left;
        font->pointer_y += font->line_height;
        return DSF_NO_ERROR;
    }

    const block_char *ch = DSF_CodepointFindGlyph(handle, codepoint);
    if (ch == NULL)
        return DSF_CODEPOINT_NOT_FOUND;

    int x1 = font->pointer_x;
    int x2 = x1 + ch->width;
    int y1 = font->pointer_y;
    int y2 = y1 + ch->height;

    x1 += ch->xoffset;
    x2 += ch->xoffset;
    y1 += ch->yoffset;
    y2 += ch->yoffset;

    font->pointer_x += ch->xadvance;

    // Kerning pair to find
    kerning_pair key = { 0 };
    key.first = font->last_codepoint;
    key.second = codepoint;

    const kerning_pair *ker = bsearch(&key, font->kernings, font->num_kernings,
                                      sizeof(kerning_pair), DSF_kerning_pair_cmp);
    if (ker != NULL)
    {
        x1 += ker->amount;
        x2 += ker->amount;
        font->pointer_x += ker->amount;
    }

    font->last_codepoint = codepoint;

    return DSF_NO_ERROR;
}

static dsf_error DSF_CodepointRender3D(dsf_handle handle, uint32_t codepoint,
                                       int16_t z)
{
    if ((handle == 0) || (codepoint == 0))
        return DSF_INVALID_ARGUMENT;

    dsf_font_internal_state *font = (dsf_font_internal_state *)handle;

    if (codepoint == '\n')
    {
        font->pointer_x = font->box_left;
        font->pointer_y += font->line_height;
        return DSF_NO_ERROR;
    }

    const block_char *ch = DSF_CodepointFindGlyph(handle, codepoint);
    if (ch == NULL)
        return DSF_CODEPOINT_NOT_FOUND;

    int tx1 = ch->x;
    int tx2 = tx1 + ch->width;
    int ty1 = ch->y;
    int ty2 = ty1 + ch->height;

    int x1 = font->pointer_x;
    int x2 = x1 + ch->width;
    int y1 = font->pointer_y;
    int y2 = y1 + ch->height;

    x1 += ch->xoffset;
    x2 += ch->xoffset;
    y1 += ch->yoffset;
    y2 += ch->yoffset;

    font->pointer_x += ch->xadvance;

    // Kerning pair to find
    kerning_pair key = { 0 };
    key.first = font->last_codepoint;
    key.second = codepoint;

    const kerning_pair *ker = bsearch(&key, font->kernings, font->num_kernings,
                                      sizeof(kerning_pair), DSF_kerning_pair_cmp);
    if (ker != NULL)
    {
        x1 += ker->amount;
        x2 += ker->amount;
        font->pointer_x += ker->amount;
    }

    font->last_codepoint = codepoint;

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(tx1), inttot16(ty1));
    GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left
    GFX_VERTEX16 = z;

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(tx1), inttot16(ty2));
    GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(tx2), inttot16(ty2));
    GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right

    GFX_TEX_COORD = TEXTURE_PACK(inttot16(tx2), inttot16(ty1));
    GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right

    return DSF_NO_ERROR;
}

dsf_error DSF_StringRenderDryRun(dsf_handle handle, const char *str,
                                 size_t *size_x, size_t *size_y)
{
    if ((handle == 0) || (str == NULL) || (size_x == NULL) || (size_y == NULL))
        return DSF_INVALID_ARGUMENT;

    if (strlen(str) == 0)
    {
        *size_x = 0;
        *size_y = 0;
        return DSF_NO_ERROR;
    }

    dsf_error ret = DSF_NO_ERROR;

    dsf_font_internal_state *font = (dsf_font_internal_state *)handle;

    font->pointer_x = 0;
    font->pointer_y = 0;
    font->box_left = 0;
    font->box_top = 0;
    font->last_codepoint = 0;

    const char *readptr = str;

    size_t max_x = 0;
    size_t max_y = 0;

    while (*readptr != '\0')
    {
        uint32_t codepoint;
        size_t size = DSF_UTF8_CodepointRead(readptr, &codepoint);
        readptr += size;

        ret = DSF_CodepointRenderDryRun(handle, codepoint);
        if (ret != DSF_NO_ERROR)
            break;

        if (font->pointer_x > max_x)
            max_x = font->pointer_x;
        if (font->pointer_y > max_y)
            max_y = font->pointer_y;
    }

    *size_x = max_x;
    *size_y = max_y + font->line_height;

    return ret;
}

dsf_error DSF_StringRender3D(dsf_handle handle, const char *str,
                             int32_t x, int32_t y, int32_t z)
{
    if ((handle == 0) || (str == NULL))
        return DSF_INVALID_ARGUMENT;

    dsf_error ret = DSF_NO_ERROR;

    dsf_font_internal_state *font = (dsf_font_internal_state *)handle;

    font->pointer_x = x;
    font->pointer_y = y;
    font->box_left = x;
    font->box_top = y;
    font->last_codepoint = 0;

    const char *readptr = str;

    glBegin(GL_QUADS);

    while (*readptr != '\0')
    {
        uint32_t codepoint;
        size_t size = DSF_UTF8_CodepointRead(readptr, &codepoint);
        readptr += size;

        ret = DSF_CodepointRender3D(handle, codepoint, z);
        if (ret != DSF_NO_ERROR)
            break;
    }

    glEnd();

    return ret;
}

dsf_error DSF_StringRender3DAlpha(dsf_handle handle, const char *str,
                                  int32_t x, int32_t y, int32_t z,
                                  uint32_t poly_fmt, int poly_id_base)
{
    if ((handle == 0) || (str == NULL))
        return DSF_INVALID_ARGUMENT;

    dsf_error ret = DSF_NO_ERROR;

    dsf_font_internal_state *font = (dsf_font_internal_state *)handle;

    font->pointer_x = x;
    font->pointer_y = y;
    font->box_left = x;
    font->box_top = y;
    font->last_codepoint = 0;

    const char *readptr = str;

    int id_index = 0;

    while (*readptr != '\0')
    {
        uint32_t codepoint;
        size_t size = DSF_UTF8_CodepointRead(readptr, &codepoint);
        readptr += size;

        glPolyFmt(poly_fmt | POLY_ID(poly_id_base + id_index));
        id_index ^= 1;

        glBegin(GL_QUADS);
        ret = DSF_CodepointRender3D(handle, codepoint, z);
        glEnd();
        if (ret != DSF_NO_ERROR)
            break;
    }

    return ret;
}

static dsf_error DSF_CodepointRenderBuffer(dsf_handle handle,
                    uint32_t codepoint, unsigned int texture_fmt,
                    const void *font_texture, size_t font_width, size_t font_height,
                    void *out_texture, size_t out_width, size_t out_height)
{
    // Don't check if the arguments are valid, this is an internal function

    dsf_font_internal_state *font = (dsf_font_internal_state *)handle;

    if (codepoint == '\n')
    {
        font->pointer_x = font->box_left;
        font->pointer_y += font->line_height;
        return DSF_NO_ERROR;
    }

    const block_char *ch = DSF_CodepointFindGlyph(handle, codepoint);
    if (ch == NULL)
        return DSF_CODEPOINT_NOT_FOUND;

    int tx1 = ch->x;
    int ty1 = ch->y;

    int x1 = font->pointer_x;
    int x2 = x1 + ch->width;
    int y1 = font->pointer_y;
    int y2 = y1 + ch->height;

    x1 += ch->xoffset;
    x2 += ch->xoffset;
    y1 += ch->yoffset;
    y2 += ch->yoffset;

    font->pointer_x += ch->xadvance;

    // Kerning pair to find
    kerning_pair key = { 0 };
    key.first = font->last_codepoint;
    key.second = codepoint;

    const kerning_pair *ker = bsearch(&key, font->kernings, font->num_kernings,
                                      sizeof(kerning_pair), DSF_kerning_pair_cmp);
    if (ker != NULL)
    {
        x1 += ker->amount;
        x2 += ker->amount;
        font->pointer_x += ker->amount;
    }

    font->last_codepoint = codepoint;

    if (texture_fmt == GL_RGB256)
    {
        const uint8_t *src = font_texture;
        uint8_t *dst = out_texture;

        src += tx1 + ty1 * font_width;
        dst += x1 + y1 * out_width;

        for (int y = 0; y <= y2 - y1; y++)
        {
            const uint8_t *src_row = src;
            uint8_t *dst_row = dst;

            for (int x = 0; x <= x2 - x1; x++)
            {
                uint8_t color = *src_row++;
                if (color != 0)
                    *dst_row = color;
                dst_row++;
            }

            src += font_width;
            dst += out_width;
        }
    }
    else if (texture_fmt == GL_RGBA)
    {
        const uint16_t *src = font_texture;
        uint16_t *dst = out_texture;

        src += tx1 + ty1 * font_width;
        dst += x1 + y1 * out_width;

        for (int y = 0; y <= y2 - y1; y++)
        {
            const uint16_t *src_row = src;
            uint16_t *dst_row = dst;

            for (int x = 0; x <= x2 - x1; x++)
            {
                uint16_t color = *src_row++;
                if (color & BIT(15))
                    *dst_row = color;
                dst_row++;
            }

            src += font_width;
            dst += out_width;
        }
    }
    else if ((texture_fmt == GL_RGB32_A3) || (texture_fmt == GL_RGB8_A5))
    {
        const uint8_t *src = font_texture;
        uint8_t *dst = out_texture;

        src += tx1 + ty1 * font_width;
        dst += x1 + y1 * out_width;

        int alpha_shift;

        if (texture_fmt == GL_RGB32_A3)
            alpha_shift = 5;
        else // if (texture_fmt == GL_RGB8_A5)
            alpha_shift = 3;

        for (int y = 0; y <= y2 - y1; y++)
        {
            const uint8_t *src_row = src;
            uint8_t *dst_row = dst;

            for (int x = 0; x <= x2 - x1; x++)
            {
                uint8_t color = *src_row++;
                // We can't really blend two different colors because we're
                // limited by the palette. For that reason, if the new alpha is
                // 0 we leave the old entry. If the new alpha is anything other
                // than 0, we replace the old entry by the new entry, even if
                // the result is incorrect.
                if ((color >> alpha_shift) != 0)
                    *dst_row = color;
                dst_row++;
            }

            src += font_width;
            dst += out_width;
        }
    }
    else if (texture_fmt == GL_RGB16)
    {
        const uint8_t *src = font_texture;
        uint8_t *dst = out_texture;

        for (int y = 0; y <= y2 - y1; y++)
        {
            const uint8_t *src_row = src + (((ty1 + y) * font_width) >> 1);
            uint8_t *dst_row = dst + (((y1 + y) * out_width) >> 1);

            for (int x = 0; x <= x2 - x1; x++)
            {
                const uint8_t *src_px = src_row + ((tx1 + x) >> 1);
                uint8_t *dst_px = dst_row + ((x1 + x) >> 1);

                int shift = ((tx1 + x) & 1) * 4;
                uint8_t src_color = (*src_px >> shift) & 0xF;

                if (src_color == 0)
                    continue;

                shift = ((x1 + x) & 1) * 4;
                uint8_t mask = ~(0xF << shift);
                *dst_px = (*dst_px & mask) | (src_color << shift);
            }
        }
    }
    else if (texture_fmt == GL_RGB4)
    {
        const uint8_t *src = font_texture;
        uint8_t *dst = out_texture;

        for (int y = 0; y <= y2 - y1; y++)
        {
            const uint8_t *src_row = src + (((ty1 + y) * font_width) >> 2);
            uint8_t *dst_row = dst + (((y1 + y) * out_width) >> 2);

            for (int x = 0; x <= x2 - x1; x++)
            {
                const uint8_t *src_px = src_row + ((tx1 + x) >> 2);
                uint8_t *dst_px = dst_row + ((x1 + x) >> 2);

                int shift = ((tx1 + x) & 3) * 2;
                uint8_t src_color = (*src_px >> shift) & 0x3;

                if (src_color == 0)
                    continue;

                shift = ((x1 + x) & 3) * 2;
                uint8_t mask = ~(0x3 << shift);
                *dst_px = (*dst_px & mask) | (src_color << shift);
            }
        }
    }

    return DSF_NO_ERROR;
}

dsf_error DSF_StringRenderToTexture(dsf_handle handle,
                    const char *str, unsigned int texture_fmt,
                    const void *font_texture, size_t font_width, size_t font_height,
                    void **out_texture, size_t *out_width, size_t *out_height)
{
    if ((handle == 0) || (str == NULL) || (font_texture == NULL) ||
        (out_texture == NULL) || (out_width == NULL) || (out_height == NULL))
        return DSF_INVALID_ARGUMENT;

    if ((texture_fmt < 1) || (texture_fmt > 7) || (texture_fmt == GL_COMPRESSED))
        return DSF_TEXTURE_BAD_FORMAT;

    if ((font_width == 0) || (font_height == 0))
        return DSF_INVALID_ARGUMENT;

    if (strlen(str) == 0)
        return DSF_INVALID_ARGUMENT;

    // Start process

    dsf_error ret = DSF_NO_ERROR;

    dsf_font_internal_state *font = (dsf_font_internal_state *)handle;

    // Get size

    size_t tex_width, tex_height;

    ret = DSF_StringRenderDryRun(handle, str, &tex_width, &tex_height);
    if (ret != DSF_NO_ERROR)
        return ret;

    if ((tex_width > 1024) || (tex_height > 1024))
        return DSF_TEXTURE_TOO_BIG;

    // Expand to a valid texture size

    for (size_t i = 8; i <= 1024; i <<= 1)
    {
        if (tex_width <= i)
        {
            tex_width = i;
            break;
        }
    }

    for (size_t i = 8; i <= 1024; i <<= 1)
    {
        if (tex_height <= i)
        {
            tex_height = i;
            break;
        }
    }

    const int size_shift[] = {
        0, // Nothing
        1, // A3PAL32
        3, // PAL4
        2, // PAL16
        1, // PAL256
        0, // TEX4X4 (This value isn't used)
        1, // A5PAL8
        0, // A1RGB5
        0, // RGB5
    };

    size_t tex_size = (2 * tex_width * tex_height) >> size_shift[texture_fmt];

    // Allocate buffer

    void *tex_buffer = calloc(1, tex_size);
    if (tex_buffer == NULL)
        return DSF_NO_MEMORY;

    // Render string

    font->pointer_x = 0;
    font->pointer_y = 0;
    font->box_left = 0;
    font->box_top = 0;
    font->last_codepoint = 0;

    const char *readptr = str;

    while (*readptr != '\0')
    {
        uint32_t codepoint;
        size_t size = DSF_UTF8_CodepointRead(readptr, &codepoint);
        readptr += size;

        ret = DSF_CodepointRenderBuffer(handle, codepoint, texture_fmt,
                                        font_texture, font_width, font_height,
                                        tex_buffer, tex_width, tex_height);
        if (ret != DSF_NO_ERROR)
            break;
    }

    // Return texture information

    *out_texture = tex_buffer;
    *out_width = tex_width;
    *out_height = tex_height;

    return ret;
}
