(Font) Take the file I/O out of the font renderers

The renderers each resolved and opened their own font file:
path_is_valid() loops in stb and freetype to pick a default,
filestream_read_file() in both to load it, and a path_is_valid() in
coretext that was checking a font name it could never find on disk.

Move it up. get_default_font() becomes get_default_fonts(), returning
the NULL-terminated candidate list rather than walking it, and
font_renderer_create_default() picks the first path that exists, reads
it once, and hands the bytes to init(). The renderer takes ownership
on success; the caller frees on failure.

The special cases survive because init() still gets the path as well
as the bytes:

  - stb keeps its built-in glyphs, reached by an empty entry at the
    end of the list, which means "no file needed" rather than "no font
    available".
  - the WiiU shared font is fetched in init() as before; its list is
    just the empty entry.
  - freetype under fontconfig likewise.
  - coretext looks fonts up by name through CoreText, so it uses the
    path and releases the bytes. Its path_is_valid() goes: non-NULL
    bytes already prove the file is there. It still will not match
    "Verdana" on disk, which is what happened before too - init()
    rejected it at the same point.

One read remains, in freetype's fontconfig branch, and it is noted in
place: fontconfig resolves a path from the locale and system config at
init time, so there is no static candidate list to pre-read. Hoisting
it would put an optional dependency into font_driver.c, which is worse
layering than the read it removes.

stb, freetype and font_driver compile clean. coretext is Apple-only
and unbuildable here; its change is the signature, the dropped stat
and the list, all mechanical.
This commit is contained in:
I authored and libretroadmin committed 2026-08-08 18:13:36 +02:00
1 parent a717ad3cdd
commit 76f8821b7f
5 files changed
+126 -71

No files matched your search

+19 -9
View File
@@ -519,8 +519,9 @@ static bool coretext_font_renderer_render_glyph(CTFontRef face, ct_font_renderer
return true;
}
static void *font_renderer_ct_init(const char *font_path, float font_size,
enum font_atlas_format fmt)
static void *font_renderer_ct_init(const char *font_path,
uint8_t *font_data, size_t font_data_len,
float font_size, enum font_atlas_format fmt)
{
char err = 0;
CFStringRef cf_font_path = NULL;
@@ -530,11 +531,18 @@ static void *font_renderer_ct_init(const char *font_path, float font_size,
CGFontRef theCGFont = NULL;
ct_font_renderer_t *handle = (ct_font_renderer_t*)calloc(1, sizeof(*handle));
if (!handle || !path_is_valid(font_path))
/* font_data being non-NULL is proof the path exists - it was read
* by font_renderer_create_default() - so no stat is needed here.
* CoreText loads by URL, so the bytes themselves are not used;
* release them rather than hold them for the font's lifetime. */
if (!handle || !font_data || !font_data_len)
{
free(font_data);
err = 1;
goto error;
}
free(font_data);
font_data = NULL;
if (!(cf_font_path = CFStringCreateWithCString(
NULL, font_path, kCFStringEncodingUTF8)))
@@ -640,12 +648,14 @@ error:
return handle;
}
static const char *font_renderer_ct_get_default_font(void)
static const char * const *font_renderer_ct_get_default_fonts(void)
{
/* We can't tell if a font is going to be there until we actually
initialize CoreText and the best way to get fonts is by name, not
by path. */
return "Verdana";
/* A name rather than a path: CoreText looks fonts up by name, and
* there is no way to know one is present without initialising it.
* font_renderer_create_default() will not find this on disk, which
* matches the previous behaviour - init() rejected it too. */
static const char * const names[] = { "Verdana", NULL };
return names;
}
static void font_renderer_ct_get_line_metrics(
@@ -662,7 +672,7 @@ font_renderer_driver_t coretext_font_renderer = {
font_renderer_ct_get_atlas,
font_renderer_ct_get_glyph,
font_renderer_ct_free,
font_renderer_ct_get_default_font,
font_renderer_ct_get_default_fonts,
"font_renderer_ct",
font_renderer_ct_get_line_metrics
};
+26 -29
View File
@@ -387,8 +387,9 @@ static bool font_renderer_create_atlas(ft_font_renderer_t *handle,
return true;
}
static void *font_renderer_ft_init(const char *font_path, float font_size,
enum font_atlas_format fmt)
static void *font_renderer_ft_init(const char *font_path,
uint8_t *font_data_in, size_t font_data_in_len,
float font_size, enum font_atlas_format fmt)
{
FT_Error err;
@@ -478,6 +479,10 @@ static void *font_renderer_ft_init(const char *font_path, float font_size,
if (FcPatternGetInteger(found, FC_INDEX, 0,
&face_index) != FcResultMatch)
goto fc_cleanup;
/* The one read left in this directory. fontconfig resolves a
* path here, from the locale and the system configuration, so
* font_renderer_create_default() cannot have pre-read it from a
* static candidate list. */
if (!filestream_read_file((const char*)_font_path,
(void**)&font_data,
&font_data_size))
@@ -520,21 +525,15 @@ fc_done:
else
#endif
{
uint8_t* font_data = NULL;
int64_t font_data_size = 0;
/* No path_is_valid() first: filestream_read_file() opens the
* file itself and returns 0 when it cannot, so the stat only
* repeats the open's own lookup. */
if (!filestream_read_file(font_path,
(void**)&font_data, &font_data_size))
/* Bytes come from font_renderer_create_default(); no file I/O
* here. Ownership transfers on success. */
if (!font_data_in || !font_data_in_len)
goto error;
if ((err = FT_New_Memory_Face(handle->lib, (const FT_Byte*)font_data,
(FT_Long)font_data_size, (FT_Long)0, &handle->face)))
{
free(font_data);
if ((err = FT_New_Memory_Face(handle->lib,
(const FT_Byte*)font_data_in,
(FT_Long)font_data_in_len, (FT_Long)0, &handle->face)))
goto error;
}
handle->file_data = font_data;
handle->file_data = font_data_in;
}
if ((err = FT_Select_Charmap(handle->face, FT_ENCODING_UNICODE)))
@@ -560,8 +559,8 @@ error:
/* Not the cleanest way to do things for sure,
* but should hopefully work ... */
static const char *font_paths[] = {
/* Assets directory OSD Font, @see font_renderer_ft_get_default_font() */
static const char * const font_paths[] = {
/* Assets directory OSD Font, @see font_renderer_ft_get_default_fonts() */
"assets://pkg/osd-font.ttf",
#if defined(_WIN32)
"C:\\Windows\\Fonts\\consola.ttf",
@@ -588,25 +587,23 @@ static const char *font_paths[] = {
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", /* Debian, Ubuntu */
#endif
"osd-font.ttf", /* Magic font to search for, useful for distribution. */
NULL
};
/* Highly OS/platform dependent. */
static const char *font_renderer_ft_get_default_font(void)
static const char * const *font_renderer_ft_get_default_fonts(void)
{
/* Since fontconfig will return parameters more than a simple path
we will process these in the init function */
#if defined(WIIU) || defined(HAVE_FONTCONFIG_SUPPORT)
return "";
/* Resolved in init() - fontconfig returns more than a path, and
* WiiU uses the shared system font. */
static const char * const none[] = { "", NULL };
return none;
#else
size_t i;
for (i = 0; i < ARRAY_SIZE(font_paths); i++)
{
if (path_is_valid(font_paths[i]))
return font_paths[i];
}
return NULL;
/* Selection happens in font_renderer_create_default(), which is
* what keeps path_is_valid() out of this file. */
return font_paths;
#endif
}
@@ -624,7 +621,7 @@ font_renderer_driver_t freetype_font_renderer = {
font_renderer_ft_get_atlas,
font_renderer_ft_get_glyph,
font_renderer_ft_free,
font_renderer_ft_get_default_font,
font_renderer_ft_get_default_fonts,
"font_renderer_ft",
font_renderer_ft_get_line_metrics
};
+18 -25
View File
@@ -1636,8 +1636,9 @@ static bool font_renderer_stb_create_atlas_fmt(
return true;
}
static void *font_renderer_stb_init(const char *font_path, float font_size,
enum font_atlas_format fmt)
static void *font_renderer_stb_init(const char *font_path,
uint8_t *font_data, size_t font_data_len,
float font_size, enum font_atlas_format fmt)
{
int ascent, descent, line_gap;
stb_font_renderer_t *self =
@@ -1664,22 +1665,19 @@ static void *font_renderer_stb_init(const char *font_path, float font_size,
#endif
if (!font_path || !*font_path)
{
/* get_default_font() found no TrueType font. */
/* No TrueType font was found; use the built-in glyphs. */
if (!font_renderer_stb_init_builtin(self, -font_size))
goto error;
return self;
}
else
{
int64_t len = 0;
/* filestream_read_file() opens the file and returns 0 if it
* cannot, so a path_is_valid() stat first only repeats that
* lookup. */
if (!filestream_read_file(font_path, (void**)&self->font_data, &len))
/* Bytes come from font_renderer_create_default(); this renderer
* does no file I/O. Ownership transfers on success. */
if (!font_data || !font_data_len)
goto error;
if (len <= 0)
goto error;
self->font_data_size = (size_t)len;
self->font_data = font_data;
self->font_data_size = font_data_len;
self->font_data_owned = true;
}
@@ -1723,12 +1721,14 @@ error:
return NULL;
}
static const char *font_renderer_stb_get_default_font(void)
static const char * const *font_renderer_stb_get_default_fonts(void)
{
#ifdef WIIU
return "";
/* The shared system font, fetched in init(); no file to open. */
static const char * const wiiu_paths[] = { "", NULL };
return wiiu_paths;
#else
static const char *paths[] = {
static const char * const paths[] = {
#if defined(_WIN32) && !defined(__WINRT__)
"C:\\Windows\\Fonts\\consola.ttf",
"C:\\Windows\\Fonts\\verdana.ttf",
@@ -1767,19 +1767,12 @@ static const char *font_renderer_stb_get_default_font(void)
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"osd-font.ttf",
#endif
"", /* built-in glyphs, no file needed */
NULL
};
const char **p;
for (p = paths; *p; ++p)
if (path_is_valid(*p))
return *p;
/* No TrueType font on this system. Returning "" rather than NULL
* keeps this renderer in play: init() then builds the atlas from
* the built-in bitmap glyphs. */
return "";
/* The empty entry is the fallback: no file, built-in glyphs. */
return paths;
#endif
}
@@ -1795,7 +1788,7 @@ font_renderer_driver_t stb_font_renderer = {
font_renderer_stb_get_atlas,
font_renderer_stb_get_glyph,
font_renderer_stb_free,
font_renderer_stb_get_default_font,
font_renderer_stb_get_default_fonts,
"font_renderer_stb",
font_renderer_stb_get_line_metrics
};
+48 -5
View File
@@ -16,6 +16,8 @@
#include <stdlib.h>
#include <string.h>
#include <streams/file_stream.h>
#include <file/file_path.h>
#include <math.h>
#ifdef HAVE_CONFIG_H
@@ -57,19 +59,60 @@ int font_renderer_create_default(
for (i = 0; font_backends[i]; i++)
{
const char *path = font_path;
const char *path = font_path;
uint8_t *data = NULL;
int64_t len = 0;
if (!path)
path = font_backends[i]->get_default_font();
if (!path)
continue;
{
/* Pick the first candidate that exists. Doing it here rather
* than in the renderer is what keeps file I/O out of them. */
const char * const *cand = font_backends[i]->get_default_fonts
? font_backends[i]->get_default_fonts()
: NULL;
*handle = font_backends[i]->init(path, font_size, fmt);
if (!cand)
continue;
for (; *cand; cand++)
{
/* An empty entry means the renderer has an internal or
* system source and wants no file. */
if (!**cand)
{
path = *cand;
break;
}
if (path_is_valid(*cand))
{
path = *cand;
break;
}
}
if (!path)
continue;
}
/* Read it once, here. A failed read is not fatal: the renderer
* may still have an internal source to fall back on. */
if (path && *path)
if (!filestream_read_file(path, (void**)&data, &len) || len <= 0)
{
data = NULL;
len = 0;
}
*handle = font_backends[i]->init(path, data, (size_t)len,
font_size, fmt);
if (*handle)
{
/* The renderer owns data now. */
*drv = font_backends[i];
return 1;
}
free(data);
}
*drv = NULL;
+15 -3
View File
@@ -51,8 +51,16 @@ typedef struct font_renderer
typedef struct font_renderer_driver
{
void *(*init)(const char *font_path, float font_size,
enum font_atlas_format fmt);
/* font_data/font_data_len carry the contents of font_path, already
* read by font_renderer_create_default(). Renderers do no file I/O
* of their own: a NULL font_data means no usable file was found,
* and the renderer should fall back to whatever internal or system
* source it has (stb's built-in glyphs, the WiiU shared font,
* fontconfig). On success the renderer takes ownership of
* font_data and frees it in free(). */
void *(*init)(const char *font_path,
uint8_t *font_data, size_t font_data_len,
float font_size, enum font_atlas_format fmt);
struct font_atlas *(*get_atlas)(void *data);
@@ -61,7 +69,11 @@ typedef struct font_renderer_driver
void (*free)(void *data);
const char *(*get_default_font)(void);
/* NULL-terminated list of candidate paths, best first. Returning
* the list rather than a chosen path keeps path_is_valid() - and so
* all file I/O - out of the renderers. An empty first entry means
* "no file needed", for renderers with an internal source. */
const char * const *(*get_default_fonts)(void);
const char *ident;