Merge pull request #3839 from aliaspider/master

add unicode support for the freetype font renderer and gl_raster_font.
This commit is contained in:
Twinaphex 2016-10-21 19:05:46 +02:00 committed by GitHub
commit 84d750bae1
5 changed files with 190 additions and 168 deletions

View File

@ -51,15 +51,14 @@ typedef struct
const font_renderer_driver_t *font_driver; const font_renderer_driver_t *font_driver;
void *font_data; void *font_data;
struct font_atlas *atlas;
video_font_raster_block_t *block; video_font_raster_block_t *block;
} gl_raster_t; } gl_raster_t;
static void gl_raster_font_free_font(void *data); static void gl_raster_font_free_font(void *data);
static bool gl_raster_font_upload_atlas(gl_raster_t *font, static bool gl_raster_font_upload_atlas(gl_raster_t *font)
const struct font_atlas *atlas,
unsigned width, unsigned height)
{ {
unsigned i, j; unsigned i, j;
GLint gl_internal = GL_LUMINANCE_ALPHA; GLint gl_internal = GL_LUMINANCE_ALPHA;
@ -97,30 +96,30 @@ static bool gl_raster_font_upload_atlas(gl_raster_t *font,
} }
#endif #endif
tmp = (uint8_t*)calloc(height, width * ncomponents); tmp = (uint8_t*)calloc(font->tex_height, font->tex_width * ncomponents);
if (!tmp) if (!tmp)
return false; return false;
for (i = 0; i < atlas->height; ++i) for (i = 0; i < font->atlas->height; ++i)
{ {
const uint8_t *src = &atlas->buffer[i * atlas->width]; const uint8_t *src = &font->atlas->buffer[i * font->atlas->width];
uint8_t *dst = &tmp[i * width * ncomponents]; uint8_t *dst = &tmp[i * font->tex_width * ncomponents];
switch (ncomponents) switch (ncomponents)
{ {
case 1: case 1:
memcpy(dst, src, atlas->width); memcpy(dst, src, font->atlas->width);
break; break;
case 2: case 2:
for (j = 0; j < atlas->width; ++j) for (j = 0; j < font->atlas->width; ++j)
{ {
*dst++ = 0xff; *dst++ = 0xff;
*dst++ = *src++; *dst++ = *src++;
} }
break; break;
case 4: case 4:
for (j = 0; j < atlas->width; ++j) for (j = 0; j < font->atlas->width; ++j)
{ {
*dst++ = 0xff; *dst++ = 0xff;
*dst++ = 0xff; *dst++ = 0xff;
@ -136,7 +135,7 @@ static bool gl_raster_font_upload_atlas(gl_raster_t *font,
} }
} }
glTexImage2D(GL_TEXTURE_2D, 0, gl_internal, width, height, glTexImage2D(GL_TEXTURE_2D, 0, gl_internal, font->tex_width, font->tex_height,
0, gl_format, GL_UNSIGNED_BYTE, tmp); 0, gl_format, GL_UNSIGNED_BYTE, tmp);
free(tmp); free(tmp);
@ -174,14 +173,15 @@ static void *gl_raster_font_init_font(void *data,
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
atlas = font->font_driver->get_atlas(font->font_data); font->atlas = font->font_driver->get_atlas(font->font_data);
font->tex_width = next_pow2(atlas->width); font->tex_width = next_pow2(font->atlas->width);
font->tex_height = next_pow2(atlas->height); font->tex_height = next_pow2(font->atlas->height);
if (!gl_raster_font_upload_atlas(font, atlas, if (!gl_raster_font_upload_atlas(font))
font->tex_width, font->tex_height))
goto error; goto error;
font->atlas->dirty = false;
glBindTexture(GL_TEXTURE_2D, font->gl->texture[font->gl->tex_index]); glBindTexture(GL_TEXTURE_2D, font->gl->texture[font->gl->tex_index]);
return font; return font;
@ -212,12 +212,12 @@ static void gl_raster_font_free_font(void *data)
} }
static int gl_get_message_width(void *data, const char *msg, static int gl_get_message_width(void *data, const char *msg,
unsigned msg_len_full, float scale) unsigned msg_len, float scale)
{ {
unsigned i; unsigned i;
int delta_x = 0;
unsigned msg_len = MIN(msg_len_full, MAX_MSG_LEN_CHUNK);
gl_raster_t *font = (gl_raster_t*)data; gl_raster_t *font = (gl_raster_t*)data;
const char* msg_end = msg + msg_len;
int delta_x = 0;
if (!font) if (!font)
return 0; return 0;
@ -228,17 +228,10 @@ static int gl_get_message_width(void *data, const char *msg,
|| !font->font_data ) || !font->font_data )
return 0; return 0;
while (msg_len_full) while (msg < msg_end)
{
for (i = 0; i < msg_len; i++)
{ {
const struct font_glyph *glyph = NULL; const struct font_glyph *glyph = NULL;
const char *msg_tmp = &msg[i]; unsigned code = utf8_walk(&msg);
unsigned code = utf8_walk(&msg_tmp);
unsigned skip = msg_tmp - &msg[i];
if (skip > 1)
i += skip - 1;
glyph = font->font_driver->get_glyph(font->font_data, code); glyph = font->font_driver->get_glyph(font->font_data, code);
@ -250,26 +243,27 @@ static int gl_get_message_width(void *data, const char *msg,
delta_x += glyph->advance_x; delta_x += glyph->advance_x;
} }
msg_len_full -= msg_len;
msg += msg_len;
msg_len = MIN(msg_len_full, MAX_MSG_LEN_CHUNK);
}
return delta_x * scale; return delta_x * scale;
} }
static void gl_raster_font_draw_vertices(gl_t *gl, const video_coords_t *coords) static void gl_raster_font_draw_vertices(gl_raster_t *font, const video_coords_t *coords)
{ {
video_shader_ctx_mvp_t mvp; video_shader_ctx_mvp_t mvp;
video_shader_ctx_coords_t coords_data; video_shader_ctx_coords_t coords_data;
if (font->atlas->dirty)
{
gl_raster_font_upload_atlas(font);
font->atlas->dirty = false;
}
coords_data.handle_data = NULL; coords_data.handle_data = NULL;
coords_data.data = coords; coords_data.data = coords;
video_shader_driver_set_coords(&coords_data); video_shader_driver_set_coords(&coords_data);
mvp.data = gl; mvp.data = font->gl;
mvp.matrix = &gl->mvp_no_rot; mvp.matrix = &font->gl->mvp_no_rot;
video_shader_driver_set_mvp(&mvp); video_shader_driver_set_mvp(&mvp);
@ -277,25 +271,24 @@ static void gl_raster_font_draw_vertices(gl_t *gl, const video_coords_t *coords)
} }
static void gl_raster_font_render_line( static void gl_raster_font_render_line(
gl_raster_t *font, const char *msg, unsigned msg_len_full, gl_raster_t *font, const char *msg, unsigned msg_len,
GLfloat scale, const GLfloat color[4], GLfloat pos_x, GLfloat scale, const GLfloat color[4], GLfloat pos_x,
GLfloat pos_y, unsigned text_align) GLfloat pos_y, unsigned text_align)
{ {
int x, y, delta_x, delta_y; int x, y, delta_x, delta_y;
float inv_tex_size_x, inv_tex_size_y, inv_win_width, inv_win_height; float inv_tex_size_x, inv_tex_size_y, inv_win_width, inv_win_height;
unsigned i, msg_len; unsigned i;
struct video_coords coords; struct video_coords coords;
GLfloat font_tex_coords[2 * 6 * MAX_MSG_LEN_CHUNK]; GLfloat font_tex_coords[2 * 6 * MAX_MSG_LEN_CHUNK];
GLfloat font_vertex[2 * 6 * MAX_MSG_LEN_CHUNK]; GLfloat font_vertex[2 * 6 * MAX_MSG_LEN_CHUNK];
GLfloat font_color[4 * 6 * MAX_MSG_LEN_CHUNK]; GLfloat font_color[4 * 6 * MAX_MSG_LEN_CHUNK];
GLfloat font_lut_tex_coord[2 * 6 * MAX_MSG_LEN_CHUNK]; GLfloat font_lut_tex_coord[2 * 6 * MAX_MSG_LEN_CHUNK];
gl_t *gl = font ? font->gl : NULL; gl_t *gl = font ? font->gl : NULL;
const char* msg_end = msg + msg_len;
if (!gl) if (!gl)
return; return;
msg_len = MIN(msg_len_full, MAX_MSG_LEN_CHUNK);
x = roundf(pos_x * gl->vp.width); x = roundf(pos_x * gl->vp.width);
y = roundf(pos_y * gl->vp.height); y = roundf(pos_y * gl->vp.height);
delta_x = 0; delta_x = 0;
@ -304,10 +297,10 @@ static void gl_raster_font_render_line(
switch (text_align) switch (text_align)
{ {
case TEXT_ALIGN_RIGHT: case TEXT_ALIGN_RIGHT:
x -= gl_get_message_width(font, msg, msg_len_full, scale); x -= gl_get_message_width(font, msg, msg_len, scale);
break; break;
case TEXT_ALIGN_CENTER: case TEXT_ALIGN_CENTER:
x -= gl_get_message_width(font, msg, msg_len_full, scale) / 2.0; x -= gl_get_message_width(font, msg, msg_len, scale) / 2.0;
break; break;
} }
@ -316,23 +309,15 @@ static void gl_raster_font_render_line(
inv_win_width = 1.0f / font->gl->vp.width; inv_win_width = 1.0f / font->gl->vp.width;
inv_win_height = 1.0f / font->gl->vp.height; inv_win_height = 1.0f / font->gl->vp.height;
while (msg_len_full)
while (msg < msg_end)
{ {
for (i = 0; i < msg_len; i++) i = 0;
while ((i < MAX_MSG_LEN_CHUNK) && (msg < msg_end))
{ {
int off_x, off_y, tex_x, tex_y, width, height; int off_x, off_y, tex_x, tex_y, width, height;
const struct font_glyph *glyph = NULL; const struct font_glyph *glyph = NULL;
const char *msg_tmp = &msg[i]; unsigned code = utf8_walk(&msg);
unsigned code = utf8_walk(&msg_tmp);
unsigned skip = msg_tmp - &msg[i];
if (skip > 1)
{
i += skip - 1;
if (i >= msg_len)
break;
}
glyph = font->font_driver->get_glyph(font->font_data, code); glyph = font->font_driver->get_glyph(font->font_data, code);
@ -356,6 +341,8 @@ static void gl_raster_font_render_line(
gl_raster_font_emit(4, 0, 0); /* Top-left */ gl_raster_font_emit(4, 0, 0); /* Top-left */
gl_raster_font_emit(5, 1, 1); /* Bottom-right */ gl_raster_font_emit(5, 1, 1); /* Bottom-right */
i++;
delta_x += glyph->advance_x; delta_x += glyph->advance_x;
delta_y -= glyph->advance_y; delta_y -= glyph->advance_y;
} }
@ -363,17 +350,13 @@ static void gl_raster_font_render_line(
coords.tex_coord = font_tex_coords; coords.tex_coord = font_tex_coords;
coords.vertex = font_vertex; coords.vertex = font_vertex;
coords.color = font_color; coords.color = font_color;
coords.vertices = 6 * msg_len; coords.vertices = i * 6;
coords.lut_tex_coord = font_lut_tex_coord; coords.lut_tex_coord = font_lut_tex_coord;
if (font->block) if (font->block)
video_coord_array_append(&font->block->carr, &coords, coords.vertices); video_coord_array_append(&font->block->carr, &coords, coords.vertices);
else else
gl_raster_font_draw_vertices(gl, &coords); gl_raster_font_draw_vertices(font, &coords);
msg_len_full -= msg_len;
msg += msg_len;
msg_len = MIN(msg_len_full, MAX_MSG_LEN_CHUNK);
} }
} }
@ -568,7 +551,7 @@ static void gl_raster_font_flush_block(void *data)
return; return;
gl_raster_font_setup_viewport(font, block->fullscreen); gl_raster_font_setup_viewport(font, block->fullscreen);
gl_raster_font_draw_vertices(font->gl, (video_coords_t*)&block->carr.coords); gl_raster_font_draw_vertices(font, (video_coords_t*)&block->carr.coords);
gl_raster_font_restore_viewport(font->gl, block->fullscreen); gl_raster_font_restore_viewport(font->gl, block->fullscreen);
} }

View File

@ -34,7 +34,7 @@ typedef struct bm_renderer
struct font_atlas atlas; struct font_atlas atlas;
} bm_renderer_t; } bm_renderer_t;
static const struct font_atlas *font_renderer_bmp_get_atlas(void *data) static struct font_atlas *font_renderer_bmp_get_atlas(void *data)
{ {
bm_renderer_t *handle = (bm_renderer_t*)data; bm_renderer_t *handle = (bm_renderer_t*)data;
if (!handle) if (!handle)
@ -76,6 +76,7 @@ static void char_to_texture(bm_renderer_t *handle, uint8_t letter,
dst[xo + yo * handle->atlas.width] = col; dst[xo + yo * handle->atlas.width] = col;
} }
} }
handle->atlas.dirty = true;
} }
static void *font_renderer_bmp_init(const char *font_path, float font_size) static void *font_renderer_bmp_init(const char *font_path, float font_size)

View File

@ -24,23 +24,34 @@
#include <retro_miscellaneous.h> #include <retro_miscellaneous.h>
#include FT_FREETYPE_H #include FT_FREETYPE_H
#include "../font_driver.h" #include "../font_driver.h"
#define FT_ATLAS_ROWS 16 #define FT_ATLAS_ROWS 16
#define FT_ATLAS_COLS 16 #define FT_ATLAS_COLS 16
#define FT_ATLAS_SIZE (FT_ATLAS_ROWS * FT_ATLAS_COLS) #define FT_ATLAS_SIZE (FT_ATLAS_ROWS * FT_ATLAS_COLS)
#if FT_ATLAS_SIZE <= 256
typedef uint8_t atlas_index_t;
#else
typedef uint16_t atlas_index_t;
#endif
typedef struct freetype_renderer typedef struct freetype_renderer
{ {
FT_Library lib; FT_Library lib;
FT_Face face; FT_Face face;
int max_glyph_width;
int max_glyph_height;
struct font_atlas atlas; struct font_atlas atlas;
struct font_glyph glyphs[FT_ATLAS_SIZE]; struct font_glyph glyphs[FT_ATLAS_SIZE];
atlas_index_t uc_to_id[0x10000];
uint16_t id_to_uc[FT_ATLAS_SIZE];
unsigned last_used[FT_ATLAS_SIZE];
unsigned usage_counter;
} ft_font_renderer_t; } ft_font_renderer_t;
static const struct font_atlas *font_renderer_ft_get_atlas(void *data) static struct font_atlas *font_renderer_ft_get_atlas(void *data)
{ {
ft_font_renderer_t *handle = (ft_font_renderer_t*)data; ft_font_renderer_t *handle = (ft_font_renderer_t*)data;
if (!handle) if (!handle)
@ -48,15 +59,6 @@ static const struct font_atlas *font_renderer_ft_get_atlas(void *data)
return &handle->atlas; return &handle->atlas;
} }
static const struct font_glyph *font_renderer_ft_get_glyph(
void *data, uint32_t code)
{
ft_font_renderer_t *handle = (ft_font_renderer_t*)data;
if (!handle)
return NULL;
return code < FT_ATLAS_SIZE ? &handle->glyphs[code] : NULL;
}
static void font_renderer_ft_free(void *data) static void font_renderer_ft_free(void *data)
{ {
ft_font_renderer_t *handle = (ft_font_renderer_t*)data; ft_font_renderer_t *handle = (ft_font_renderer_t*)data;
@ -72,96 +74,110 @@ static void font_renderer_ft_free(void *data)
free(handle); free(handle);
} }
static bool font_renderer_create_atlas(ft_font_renderer_t *handle) static unsigned font_renderer_get_slot(ft_font_renderer_t *handle)
{ {
unsigned i; int i;
bool ret = true; unsigned oldest = 1;
uint8_t *buffer[FT_ATLAS_SIZE] = {NULL}; for (i = 2; i < FT_ATLAS_SIZE; i++)
unsigned pitches[FT_ATLAS_SIZE] = {0}; if(handle->last_used[i] < handle->last_used[oldest])
oldest = i;
unsigned max_width = 0; handle->uc_to_id[handle->id_to_uc[oldest]] = 0;
unsigned max_height = 0; handle->id_to_uc[oldest] = 0;
return oldest;
}
for (i = 0; i < FT_ATLAS_SIZE; i++) static unsigned font_renderer_update_atlas(ft_font_renderer_t *handle, FT_ULong charcode)
{ {
FT_GlyphSlot slot; FT_GlyphSlot slot;
struct font_glyph *glyph = &handle->glyphs[i];
if (!glyph) if(charcode > 0x10000)
continue; return 0;
if(handle->uc_to_id[charcode])
return handle->uc_to_id[charcode];
if (FT_Load_Char(handle->face, i, FT_LOAD_RENDER)) unsigned id = font_renderer_get_slot(handle);
{ handle->id_to_uc[id] = charcode;
ret = false; handle->uc_to_id[charcode] = id;
goto end; handle->atlas.dirty = true;
}
struct font_glyph *glyph = &handle->glyphs[id];
memset(glyph, 0, sizeof(*glyph)) ;
if (FT_Load_Char(handle->face, charcode, FT_LOAD_RENDER))
return -1;
FT_Render_Glyph(handle->face->glyph, FT_RENDER_MODE_NORMAL); FT_Render_Glyph(handle->face->glyph, FT_RENDER_MODE_NORMAL);
slot = handle->face->glyph; slot = handle->face->glyph;
/* Some glyphs can be blank. */ /* Some glyphs can be blank. */
buffer[i] = (uint8_t*)calloc(slot->bitmap.rows * slot->bitmap.pitch, 1);
glyph->width = slot->bitmap.width; glyph->width = slot->bitmap.width;
glyph->height = slot->bitmap.rows; glyph->height = slot->bitmap.rows;
pitches[i] = slot->bitmap.pitch;
glyph->advance_x = slot->advance.x >> 6; glyph->advance_x = slot->advance.x >> 6;
glyph->advance_y = slot->advance.y >> 6; glyph->advance_y = slot->advance.y >> 6;
glyph->draw_offset_x = slot->bitmap_left; glyph->draw_offset_x = slot->bitmap_left;
glyph->draw_offset_y = -slot->bitmap_top; glyph->draw_offset_y = -slot->bitmap_top;
if (buffer[i] && slot->bitmap.buffer)
memcpy(buffer[i], slot->bitmap.buffer,
slot->bitmap.rows * pitches[i]);
max_width = MAX(max_width, (unsigned)slot->bitmap.width);
max_height = MAX(max_height, (unsigned)slot->bitmap.rows);
}
handle->atlas.width = max_width * FT_ATLAS_COLS;
handle->atlas.height = max_height * FT_ATLAS_ROWS;
handle->atlas.buffer = (uint8_t*)
calloc(handle->atlas.width * handle->atlas.height, 1);
if (!handle->atlas.buffer)
{
ret = false;
goto end;
}
/* Blit our texture atlas. */
for (i = 0; i < FT_ATLAS_SIZE; i++)
{
uint8_t *dst = NULL; uint8_t *dst = NULL;
unsigned offset_x = (i % FT_ATLAS_COLS) * max_width; unsigned offset_x = (id % FT_ATLAS_COLS) * handle->max_glyph_width;
unsigned offset_y = (i / FT_ATLAS_COLS) * max_height; unsigned offset_y = (id / FT_ATLAS_COLS) * handle->max_glyph_height;
handle->glyphs[i].atlas_offset_x = offset_x; handle->glyphs[id].atlas_offset_x = offset_x;
handle->glyphs[i].atlas_offset_y = offset_y; handle->glyphs[id].atlas_offset_y = offset_y;
dst = (uint8_t*)handle->atlas.buffer; dst = (uint8_t*)handle->atlas.buffer;
dst += offset_x + offset_y * handle->atlas.width; dst += offset_x + offset_y * handle->atlas.width;
if (buffer[i]) if (slot->bitmap.buffer)
{ {
unsigned r, c; unsigned r, c;
const uint8_t *src = (const uint8_t*)buffer[i]; const uint8_t *src = (const uint8_t*)slot->bitmap.buffer;
for (r = 0; r < handle->glyphs[i].height; for (r = 0; r < handle->glyphs[id].height;
r++, dst += handle->atlas.width, src += pitches[i]) r++, dst += handle->atlas.width, src += slot->bitmap.pitch)
for (c = 0; c < handle->glyphs[i].width; c++) for (c = 0; c < handle->glyphs[id].width; c++)
dst[c] = src[c]; dst[c] = src[c];
} }
handle->usage_counter++;
return id;
} }
end: static const struct font_glyph *font_renderer_ft_get_glyph(
for (i = 0; i < FT_ATLAS_SIZE; i++) void *data, uint32_t code)
free(buffer[i]); {
return ret; ft_font_renderer_t *handle = (ft_font_renderer_t*)data;
if (!handle)
return NULL;
if(code > 0x10000)
return NULL;
unsigned id = handle->uc_to_id[code];
if(!id)
id = font_renderer_update_atlas(handle, (FT_ULong)code);
handle->last_used[id] = handle->usage_counter++;
return &handle->glyphs[id];
} }
static bool font_renderer_create_atlas(ft_font_renderer_t *handle)
{
unsigned i;
for (i = 0; i < 256; i++)
if(font_renderer_update_atlas(handle, i) < 0)
return false;
return true;
}
#include "locale.h"
static void *font_renderer_ft_init(const char *font_path, float font_size) static void *font_renderer_ft_init(const char *font_path, float font_size)
{ {
FT_Error err; FT_Error err;
@ -172,6 +188,9 @@ static void *font_renderer_ft_init(const char *font_path, float font_size)
if (!handle) if (!handle)
goto error; goto error;
if (font_size < 1.0)
goto error;
err = FT_Init_FreeType(&handle->lib); err = FT_Init_FreeType(&handle->lib);
if (err) if (err)
goto error; goto error;
@ -180,10 +199,26 @@ static void *font_renderer_ft_init(const char *font_path, float font_size)
if (err) if (err)
goto error; goto error;
err = FT_Select_Charmap(handle->face, FT_ENCODING_UNICODE);
if (err)
goto error;
err = FT_Set_Pixel_Sizes(handle->face, 0, font_size); err = FT_Set_Pixel_Sizes(handle->face, 0, font_size);
if (err) if (err)
goto error; goto error;
/* TODO: find a better way to determine onmax_glyph_width/height */
handle->max_glyph_width = font_size;
handle->max_glyph_height = font_size;
handle->atlas.width = handle->max_glyph_width * 2 * FT_ATLAS_COLS;
handle->atlas.height = handle->max_glyph_height * 2 * FT_ATLAS_ROWS;
handle->atlas.buffer = (uint8_t*)
calloc(handle->atlas.width * handle->atlas.height, 1);
if (!handle->atlas.buffer)
return false;
if (!font_renderer_create_atlas(handle)) if (!font_renderer_create_atlas(handle))
goto error; goto error;

View File

@ -38,7 +38,7 @@ typedef struct
struct font_glyph glyphs[256]; struct font_glyph glyphs[256];
} stb_font_renderer_t; } stb_font_renderer_t;
static const struct font_atlas *font_renderer_stb_get_atlas(void *data) static struct font_atlas *font_renderer_stb_get_atlas(void *data)
{ {
stb_font_renderer_t *self = (stb_font_renderer_t*)data; stb_font_renderer_t *self = (stb_font_renderer_t*)data;
return &self->atlas; return &self->atlas;
@ -89,6 +89,8 @@ static bool font_renderer_stb_create_atlas(stb_font_renderer_t *self,
stbtt_PackFontRange(&pc, font_data, 0, font_size, 0, 256, chardata); stbtt_PackFontRange(&pc, font_data, 0, font_size, 0, 256, chardata);
stbtt_PackEnd(&pc); stbtt_PackEnd(&pc);
self->atlas.dirty = true;
for (i = 0; i < 256; ++i) for (i = 0; i < 256; ++i)
{ {
struct font_glyph *g = &self->glyphs[i]; struct font_glyph *g = &self->glyphs[i];

View File

@ -73,6 +73,7 @@ struct font_atlas
uint8_t *buffer; /* Alpha channel. */ uint8_t *buffer; /* Alpha channel. */
unsigned width; unsigned width;
unsigned height; unsigned height;
bool dirty;
}; };
struct font_params struct font_params
@ -112,7 +113,7 @@ typedef struct font_renderer_driver
{ {
void *(*init)(const char *font_path, float font_size); void *(*init)(const char *font_path, float font_size);
const struct font_atlas *(*get_atlas)(void *data); struct font_atlas *(*get_atlas)(void *data);
/* Returns NULL if no glyph for this code is found. */ /* Returns NULL if no glyph for this code is found. */
const struct font_glyph *(*get_glyph)(void *data, uint32_t code); const struct font_glyph *(*get_glyph)(void *data, uint32_t code);