mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-09 03:29:43 -10:00
(Font) Change the menu language without restarting
font_driver_reload_fonts() rebuilt each font from the path it was created with, which re-reads the same face. Right for a file that changed underneath, useless for a language change, which wants a different file: Chinese needs pkg/chinese-fallback-font.ttf where the font was built from ozone/bold.ttf. So a font that follows the language now says so, and says what it needs to be worked out again: the assets pkg directory the language fonts live in, and the path to use when the language wants no special one. font_driver_set_language_font() records both, and the reload re-resolves through font_driver_language_font_file() rather than reusing the old path. The language setting calls the reload. A context reset would have done it too, at the cost of tearing down video, audio and input to change a typeface; this rebuilds the fonts where they stand. Each font_data_t keeps its address, so nothing holding one has to be told, and the generation bump makes ozone, materialui, widgets and xmb recompute their derived metrics on the next frame - which is why those four went first. ozone marks its six fonts, and skips marking when an explicit menu font is set: there is nothing to re-resolve then, and overriding the user's choice on a language change would be wrong. materialui, widgets and file_path_special still want the same one-line call. The tests grow the case this was for: that English keeps the default face, that Korean re-resolves to the Korean one, and that switching back restores the default - the point being the path changes rather than merely being re-read. They need the path helpers linked in now that font_driver.c joins one. Clean under ASan, UBSan, LeakSanitizer and TSan. Thai is still not covered, and not because of this: it has no case because no shipped font has the glyphs. fallback-font.ttf covers Arabic but not U+0E01. That needs an asset before it needs code.
This commit is contained in:
1 parent
4845f3b13f
commit
42a711fab5
7 files changed
+154
-17
No files matched your search
+55
-4
@@ -88,6 +88,38 @@ const char *font_driver_language_font_file(void)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void font_driver_set_language_font(font_data_t *font,
|
||||
const char *pkg_dir, const char *default_path)
|
||||
{
|
||||
if (!font)
|
||||
return;
|
||||
|
||||
free(font->lang_pkg_dir);
|
||||
free(font->lang_default_path);
|
||||
font->lang_pkg_dir = (pkg_dir && *pkg_dir)
|
||||
? strdup(pkg_dir) : NULL;
|
||||
font->lang_default_path = (default_path && *default_path)
|
||||
? strdup(default_path) : NULL;
|
||||
}
|
||||
|
||||
/* The path this font should be using now. For a language-following
|
||||
* font that is worked out again from the current language; for any
|
||||
* other it is the path it already has. */
|
||||
static const char *font_driver_resolve_path(font_data_t *font,
|
||||
char *s, size_t len)
|
||||
{
|
||||
const char *lang_font;
|
||||
|
||||
if (!font->lang_pkg_dir || !font->lang_default_path)
|
||||
return font->path;
|
||||
|
||||
if (!(lang_font = font_driver_language_font_file()))
|
||||
return font->lang_default_path;
|
||||
|
||||
fill_pathname_join_special(s, font->lang_pkg_dir, lang_font, len);
|
||||
return s;
|
||||
}
|
||||
|
||||
unsigned font_driver_reload_fonts(void)
|
||||
{
|
||||
font_data_t *font;
|
||||
@@ -106,9 +138,22 @@ unsigned font_driver_reload_fonts(void)
|
||||
/* The backend's own init does the resolving and the reading, so
|
||||
* this is the same call that created the font in the first
|
||||
* place - just against whatever the path now points at. */
|
||||
if (!(fresh = renderer->init(font->video_data, font->path,
|
||||
font->size, font->is_threaded)))
|
||||
continue; /* keep the old font rather than lose text */
|
||||
{
|
||||
char resolved[PATH_MAX_LENGTH];
|
||||
const char *want = font_driver_resolve_path(font,
|
||||
resolved, sizeof(resolved));
|
||||
|
||||
if (!(fresh = renderer->init(font->video_data, want,
|
||||
font->size, font->is_threaded)))
|
||||
continue; /* keep the old font rather than lose text */
|
||||
|
||||
/* Remember what it is actually built from now. */
|
||||
if (want != font->path)
|
||||
{
|
||||
free(font->path);
|
||||
font->path = strdup(want);
|
||||
}
|
||||
}
|
||||
|
||||
/* Swap in place: the font_data_t address does not change, so
|
||||
* every holder of the pointer stays valid. Only the renderer
|
||||
@@ -864,7 +909,11 @@ void font_driver_free(font_data_t *font)
|
||||
}
|
||||
|
||||
free(font->path);
|
||||
font->path = NULL;
|
||||
free(font->lang_pkg_dir);
|
||||
free(font->lang_default_path);
|
||||
font->path = NULL;
|
||||
font->lang_pkg_dir = NULL;
|
||||
font->lang_default_path = NULL;
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
/* Ask for the real threaded state, not the video_threaded
|
||||
@@ -916,6 +965,8 @@ font_data_t *font_driver_init_first(
|
||||
font->video_data = video_data;
|
||||
font->path = (font_path && *font_path)
|
||||
? strdup(font_path) : NULL;
|
||||
font->lang_pkg_dir = NULL;
|
||||
font->lang_default_path = NULL;
|
||||
font->is_threaded = is_threaded;
|
||||
|
||||
font_driver_cache_metrics(font);
|
||||
|
||||
@@ -92,6 +92,11 @@ typedef struct font_data
|
||||
struct font_data *next; /* list of live fonts */
|
||||
void *video_data;
|
||||
char *path; /* NULL when the renderer chose */
|
||||
/* Set for a font whose file depends on the menu language. Kept so
|
||||
* the path can be worked out again rather than merely re-read: a
|
||||
* language change wants a different face, not the same one. */
|
||||
char *lang_pkg_dir;
|
||||
char *lang_default_path;
|
||||
bool is_threaded;
|
||||
/* Line metrics, read from the renderer once when the font is
|
||||
* created. Renderers fill these at init and never change them, so
|
||||
@@ -159,6 +164,14 @@ unsigned font_driver_reload_fonts(void);
|
||||
* which is three places to forget when a language is added. */
|
||||
const char *font_driver_language_font_file(void);
|
||||
|
||||
/* Declare that this font's file follows the menu language, and give
|
||||
* the two things needed to work it out again: the assets pkg
|
||||
* directory the language fonts live in, and the path to use when the
|
||||
* language needs no special font. font_driver_reload_fonts() then
|
||||
* re-resolves instead of re-reading. */
|
||||
void font_driver_set_language_font(font_data_t *font,
|
||||
const char *pkg_dir, const char *default_path);
|
||||
|
||||
/* Returns a monotonic counter incremented on every font free;
|
||||
* see font_driver.c for rationale. Used to validate externally
|
||||
* cached per-font derived data */
|
||||
|
||||
+37
-8
@@ -10049,6 +10049,7 @@ static void ozone_set_layout(
|
||||
const char *path_menu_font = settings->paths.path_menu_ozone_font;
|
||||
char tmp_dir[DIR_MAX_LENGTH];
|
||||
char font_path[PATH_MAX_LENGTH];
|
||||
char default_font_path[PATH_MAX_LENGTH];
|
||||
bool font_inited = false;
|
||||
float scale_factor = ozone->last_scale_factor;
|
||||
float padding_factor = settings->floats.ozone_padding_factor;
|
||||
@@ -10117,13 +10118,14 @@ static void ozone_set_layout(
|
||||
{
|
||||
const char *lang_font = font_driver_language_font_file();
|
||||
|
||||
fill_pathname_join_special(tmp_dir, path_directory_assets, "pkg", sizeof(tmp_dir));
|
||||
fill_pathname_join_special(default_font_path, ozone->assets_path, "bold.ttf",
|
||||
sizeof(default_font_path));
|
||||
|
||||
if (lang_font)
|
||||
{
|
||||
fill_pathname_join_special(tmp_dir, path_directory_assets, "pkg", sizeof(tmp_dir));
|
||||
fill_pathname_join_special(font_path, tmp_dir, lang_font, sizeof(font_path));
|
||||
}
|
||||
else
|
||||
fill_pathname_join_special(font_path, ozone->assets_path, "bold.ttf", sizeof(font_path));
|
||||
strlcpy(font_path, default_font_path, sizeof(font_path));
|
||||
}
|
||||
|
||||
if (path_menu_font && *path_menu_font)
|
||||
@@ -10137,13 +10139,14 @@ static void ozone_set_layout(
|
||||
{
|
||||
const char *lang_font = font_driver_language_font_file();
|
||||
|
||||
fill_pathname_join_special(tmp_dir, path_directory_assets, "pkg", sizeof(tmp_dir));
|
||||
fill_pathname_join_special(default_font_path, ozone->assets_path, "regular.ttf",
|
||||
sizeof(default_font_path));
|
||||
|
||||
if (lang_font)
|
||||
{
|
||||
fill_pathname_join_special(tmp_dir, path_directory_assets, "pkg", sizeof(tmp_dir));
|
||||
fill_pathname_join_special(font_path, tmp_dir, lang_font, sizeof(font_path));
|
||||
}
|
||||
else
|
||||
fill_pathname_join_special(font_path, ozone->assets_path, "regular.ttf", sizeof(font_path));
|
||||
strlcpy(font_path, default_font_path, sizeof(font_path));
|
||||
}
|
||||
|
||||
if (path_menu_font && *path_menu_font)
|
||||
@@ -10181,6 +10184,32 @@ static void ozone_set_layout(
|
||||
if (!(((ozone->flags & OZONE_FLAG_HAS_ALL_ASSETS) > 0) && font_inited))
|
||||
ozone->flags &= ~OZONE_FLAG_HAS_ALL_ASSETS;
|
||||
|
||||
/* Mark every font as following the menu language, so a language
|
||||
* change can rebuild them in place instead of asking for a
|
||||
* restart. The title takes the bold face, the rest the regular
|
||||
* one; both are overridden by an explicit menu font setting, in
|
||||
* which case there is nothing to re-resolve. */
|
||||
if (!(path_menu_font && *path_menu_font))
|
||||
{
|
||||
char bold_path[PATH_MAX_LENGTH];
|
||||
|
||||
fill_pathname_join_special(bold_path, ozone->assets_path,
|
||||
"bold.ttf", sizeof(bold_path));
|
||||
|
||||
font_driver_set_language_font(ozone->fonts.title.font,
|
||||
tmp_dir, bold_path);
|
||||
font_driver_set_language_font(ozone->fonts.sidebar.font,
|
||||
tmp_dir, default_font_path);
|
||||
font_driver_set_language_font(ozone->fonts.entries_label.font,
|
||||
tmp_dir, default_font_path);
|
||||
font_driver_set_language_font(ozone->fonts.entries_sublabel.font,
|
||||
tmp_dir, default_font_path);
|
||||
font_driver_set_language_font(ozone->fonts.time.font,
|
||||
tmp_dir, default_font_path);
|
||||
font_driver_set_language_font(ozone->fonts.footer.font,
|
||||
tmp_dir, default_font_path);
|
||||
}
|
||||
|
||||
/* Cache footer text labels
|
||||
* > Fonts have been (re)initialised, so need
|
||||
* to recalculate label widths */
|
||||
|
||||
+10
-2
@@ -8934,16 +8934,24 @@ static void general_write_handler(rarch_setting_t *setting)
|
||||
case MENU_ENUM_LABEL_INPUT_POLL_TYPE_BEHAVIOR:
|
||||
core_set_poll_type(*setting->value.target.integer);
|
||||
break;
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) && defined(HAVE_MENU)
|
||||
case MENU_ENUM_LABEL_USER_LANGUAGE:
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) && defined(HAVE_MENU)
|
||||
/* The native Win32 menubar bakes translated strings at
|
||||
* menu-creation time (popup headers in particular have no
|
||||
* resource ID and so are not walked by win32_localize_menu
|
||||
* afterwards). Rebuild the whole menubar so every string -
|
||||
* including popup headers - reflects the new language. */
|
||||
win32_menubar_rebuild();
|
||||
break;
|
||||
#endif
|
||||
/* Arabic and Persian, Chinese and Korean each want a
|
||||
* different face. Rebuild the fonts that follow the language
|
||||
* where they stand: each font_data_t keeps its address, so
|
||||
* nothing holding one has to be told, and the generation bump
|
||||
* makes the derived metrics recompute on the next frame.
|
||||
* A context reset would do it too, at the cost of tearing
|
||||
* down video, audio and input to change a typeface. */
|
||||
font_driver_reload_fonts();
|
||||
break;
|
||||
case MENU_ENUM_LABEL_VIDEO_SCALE_INTEGER:
|
||||
{
|
||||
video_driver_state_t *video_st = video_state_get_ptr();
|
||||
|
||||
@@ -4,7 +4,13 @@ SAN ?= address,undefined
|
||||
CFLAGS := -std=gnu99 -g -O1 -fsanitize=$(SAN) -DHAVE_STB_FONT -DRARCH_INTERNAL \
|
||||
-I$(RA) -I$(RA)/libretro-common/include -I$(RA)/gfx/include \
|
||||
-I$(RA)/deps -I$(RA)/deps/stb
|
||||
SRC := $(RA)/gfx/font_driver.c
|
||||
SRC := $(RA)/gfx/font_driver.c \
|
||||
$(RA)/libretro-common/file/file_path.c \
|
||||
$(RA)/libretro-common/compat/compat_strl.c \
|
||||
$(RA)/libretro-common/string/stdstring.c \
|
||||
$(RA)/libretro-common/encodings/encoding_utf.c \
|
||||
$(RA)/libretro-common/lists/string_list.c \
|
||||
$(RA)/libretro-common/time/rtime.c
|
||||
all: lifecycle create
|
||||
lifecycle: font_lifecycle_test.c stubs_retroarch.c $(SRC)
|
||||
$(CC) $(CFLAGS) -o $@ $^ -lm
|
||||
|
||||
@@ -68,6 +68,8 @@ font_renderer_t fake_font = {
|
||||
|
||||
/* --- scenarios --- */
|
||||
int read_should_fail = 0;
|
||||
extern unsigned test_language;
|
||||
#define TEST_LANG_KOREAN 10 /* RETRO_LANGUAGE_KOREAN */
|
||||
static int fails = 0;
|
||||
#define CHECK(c, msg) do { if (!(c)) { printf(" FAIL: %s\n", msg); fails++; } } while (0)
|
||||
|
||||
@@ -170,6 +172,32 @@ int main(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* 8. a language-following font re-resolves rather than re-reads */
|
||||
{
|
||||
font_data_t *L = mk("/assets/ozone/bold.ttf", 16.0f);
|
||||
CHECK(L != NULL, "language font created");
|
||||
font_driver_set_language_font(L, "/assets/pkg", "/assets/ozone/bold.ttf");
|
||||
|
||||
/* English: no special font, so the default path stands */
|
||||
test_language = 0;
|
||||
font_driver_reload_fonts();
|
||||
CHECK(L->path && !strcmp(L->path, "/assets/ozone/bold.ttf"),
|
||||
"english keeps the default face");
|
||||
|
||||
/* Korean: the path must change, not merely be re-read */
|
||||
test_language = TEST_LANG_KOREAN;
|
||||
font_driver_reload_fonts();
|
||||
CHECK(L->path && strstr(L->path, "korean-fallback-font.ttf") != NULL,
|
||||
"korean re-resolves to the korean face");
|
||||
|
||||
/* and back */
|
||||
test_language = 0;
|
||||
font_driver_reload_fonts();
|
||||
CHECK(L->path && !strcmp(L->path, "/assets/ozone/bold.ttf"),
|
||||
"switching back restores the default face");
|
||||
font_driver_free(L);
|
||||
}
|
||||
|
||||
font_driver_free(a);
|
||||
font_driver_free(c);
|
||||
CHECK(live_renderer_state == 0, "all renderer state released at teardown");
|
||||
|
||||
@@ -28,11 +28,13 @@ bool filestream_read_file(const char *path, void **buf, int64_t *len)
|
||||
|
||||
/* font_driver_language_font_file() asks which language is selected.
|
||||
* English here: the tests are about the lifecycle, not the mapping. */
|
||||
unsigned test_language = 0; /* RETRO_LANGUAGE_ENGLISH */
|
||||
|
||||
unsigned *msg_hash_get_uint(enum msg_hash_action type)
|
||||
{
|
||||
static unsigned lang = 0; /* RETRO_LANGUAGE_ENGLISH */
|
||||
|
||||
(void)type;
|
||||
return ⟨
|
||||
return &test_language;
|
||||
}
|
||||
|
||||
static video_driver_state_t vst;
|
||||
|
||||
Reference in new issue
Block a user