mirror of
https://github.com/AntonioND/nitro-engine.git
synced 2025-06-18 16:45:33 -04:00
968 lines
24 KiB
C
968 lines
24 KiB
C
// 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;
|
|
}
|