From e4155d9a6ae77f3f487e852eadc251932ea4e08d Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 26 Jan 2025 09:46:42 -0800
Subject: [PATCH] Added support for fallback fonts
Fixes https://github.com/libsdl-org/SDL_ttf/issues/362
---
CHANGES.txt | 1 +
examples/showfont.c | 80 ++++++++++----
include/SDL3_ttf/SDL_ttf.h | 59 ++++++++++
src/SDL_ttf.c | 219 ++++++++++++++++++++++++++++++-------
src/SDL_ttf.sym | 3 +
5 files changed, 299 insertions(+), 63 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index a39ec1f5..2b61e537 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -35,3 +35,4 @@
- TTF_GetNextTextSubString()
- TTF_UpdateText()
- TTF_DestroyText()
+ * Added TTF_AddFallbackFont() to allow combining fonts with distinct glyph support
diff --git a/examples/showfont.c b/examples/showfont.c
index 2cb95add..cb9990d6 100644
--- a/examples/showfont.c
+++ b/examples/showfont.c
@@ -37,12 +37,15 @@
//#define DEFAULT_TEXT "\xc5\xab\xcc\x80\x20\xe1\xba\x83\x20\x6e\xcc\x82\x20\x48\xcc\xa8\x20\x6f\xcd\x9c\x75"
// Chinese text
//#define DEFAULT_TEXT "\xe5\xad\xa6\xe4\xb9\xa0\xe6\x9f\x90\xe8\xaf\xbe\xe7\xa8\x8b\xe5\xbf\x85\xe8\xaf\xbb\xe7\x9a\x84"
+// Mixed English, Chinese, and emoji text
+//#define DEFAULT_TEXT "The quick brown fox\njumped over the \xe5\xad\xa6\xe4\xb9\xa0\xe6\x9f\x90\xe8\xaf\xbe\xe7\xa8\x8b\xe5\xbf\x85\xe8\xaf\xbb\xe7\x9a\x84 \xf0\x9f\x98\x89"
#define WIDTH 640
#define HEIGHT 480
+#define MAX_FALLBACKS 2
#define TTF_SHOWFONT_USAGE \
-"Usage: %s [-textengine surface|renderer] [-solid] [-shaded] [-blended] [-wrapped] [-b] [-i] [-u] [-s] [-outline size] [-hintlight|-hintmono|-hintnone] [-nokerning] [-wrap] [-align left|center|right] [-fgcol r,g,b,a] [-bgcol r,g,b,a] [-disable-editbox] <font>.ttf [ptsize] [text]\n"
+"Usage: %s [-textengine surface|renderer] [-solid] [-shaded] [-blended] [-wrapped] [-b] [-i] [-u] [-s] [-outline size] [-hintlight|-hintmono|-hintnone] [-nokerning] [-wrap] [-align left|center|right] [-fgcol r,g,b,a] [-bgcol r,g,b,a] [-disable-editbox] [-fallback <font>.ttf>] <font>.ttf [ptsize] [text]\n"
typedef enum
{
@@ -264,13 +267,6 @@ static void HandleKeyDown(Scene *scene, SDL_Event *event)
}
}
-static void Cleanup(int exitcode)
-{
- TTF_Quit();
- SDL_Quit();
- exit(exitcode);
-}
-
int main(int argc, char *argv[])
{
char *argv0 = argv[0];
@@ -295,14 +291,29 @@ int main(int argc, char *argv[])
bool editbox = true;
bool dump = false;
char *message, string[128];
+ int num_fallbacks = 0;
+ const char *fallback_font_files[MAX_FALLBACKS];
+ TTF_Font *fallback_fonts[MAX_FALLBACKS];
+ int result = 0;
SDL_zero(scene);
scene.textEngine = TextEngineRenderer;
+ SDL_zeroa(fallback_fonts);
+
/* Default is black and white */
forecol = &black;
backcol = &white;
for (i=1; argv[i] && argv[i][0] == '-'; ++i) {
+ if (SDL_strcmp(argv[i], "-fallback") == 0 && argv[i+1]) {
+ ++i;
+ if (num_fallbacks < MAX_FALLBACKS) {
+ fallback_font_files[num_fallbacks++] = argv[i];
+ } else {
+ SDL_Log("Too many fallback fonts (maximum = %d)\n", MAX_FALLBACKS);
+ return(1);
+ }
+ } else
if (SDL_strcmp(argv[i], "-textengine") == 0 && argv[i+1]) {
++i;
if (SDL_strcmp(argv[i], "surface") == 0) {
@@ -413,8 +424,8 @@ int main(int argc, char *argv[])
/* Initialize the TTF library */
if (!TTF_Init()) {
SDL_Log("Couldn't initialize TTF: %s\n",SDL_GetError());
- SDL_Quit();
- return(2);
+ result = 2;
+ goto done;
}
/* Open the font file with the requested point size */
@@ -432,7 +443,8 @@ int main(int argc, char *argv[])
if (font == NULL) {
SDL_Log("Couldn't load %g pt font from %s: %s\n",
ptsize, argv[0], SDL_GetError());
- Cleanup(2);
+ result = 2;
+ goto done;
}
TTF_SetFontStyle(font, renderstyle);
TTF_SetFontOutline(font, outline);
@@ -441,6 +453,17 @@ int main(int argc, char *argv[])
TTF_SetFontWrapAlignment(font, align);
scene.font = font;
+ for (i = 0; i < num_fallbacks; ++i) {
+ fallback_fonts[i] = TTF_OpenFont(fallback_font_files[i], ptsize);
+ if (!fallback_fonts[i]) {
+ SDL_Log("Couldn't load %g pt font from %s: %s\n",
+ ptsize, fallback_font_files[i], SDL_GetError());
+ result = 2;
+ goto done;
+ }
+ TTF_AddFallbackFont(font, fallback_fonts[i]);
+ }
+
if(dump) {
for(i = 48; i < 123; i++) {
SDL_Surface* glyph = NULL;
@@ -454,20 +477,23 @@ int main(int argc, char *argv[])
}
}
- Cleanup(0);
+ result = 0;
+ goto done;
}
/* Create a window */
scene.window = SDL_CreateWindow("showfont demo", WIDTH, HEIGHT, 0);
if (!scene.window) {
SDL_Log("SDL_CreateWindow() failed: %s\n", SDL_GetError());
- Cleanup(2);
+ result = 2;
+ goto done;
}
if (scene.textEngine == TextEngineSurface) {
scene.window_surface = SDL_GetWindowSurface(scene.window);
if (!scene.window_surface) {
SDL_Log("SDL_CreateWindowSurface() failed: %s\n", SDL_GetError());
- Cleanup(2);
+ result = 2;
+ goto done;
}
SDL_SetWindowSurfaceVSync(scene.window, 1);
@@ -480,7 +506,8 @@ int main(int argc, char *argv[])
}
if (!scene.renderer) {
SDL_Log("SDL_CreateRenderer() failed: %s\n", SDL_GetError());
- Cleanup(2);
+ result = 2;
+ goto done;
}
switch (scene.textEngine) {
@@ -488,14 +515,16 @@ int main(int argc, char *argv[])
engine = TTF_CreateSurfaceTextEngine();
if (!engine) {
SDL_Log("Couldn't create surface text engine: %s\n", SDL_GetError());
- Cleanup(2);
+ result = 2;
+ goto done;
}
break;
case TextEngineRenderer:
engine = TTF_CreateRendererTextEngine(scene.renderer);
if (!engine) {
SDL_Log("Couldn't create renderer text engine: %s\n", SDL_GetError());
- Cleanup(2);
+ result = 2;
+ goto done;
}
break;
default:
@@ -541,8 +570,8 @@ int main(int argc, char *argv[])
}
if (text == NULL) {
SDL_Log("Couldn't render text: %s\n", SDL_GetError());
- TTF_CloseFont(font);
- Cleanup(2);
+ result = 2;
+ goto done;
}
scene.messageRect.x = (float)((WIDTH - text->w)/2);
scene.messageRect.y = (float)((HEIGHT - text->h)/2);
@@ -603,9 +632,15 @@ int main(int argc, char *argv[])
}
DrawScene(&scene);
}
+ result = 0;
+
+done:
SDL_DestroySurface(text);
EditBox_Destroy(scene.edit);
TTF_DestroyText(scene.caption);
+ for (i = 0; i < num_fallbacks; ++i) {
+ TTF_CloseFont(fallback_fonts[i]);
+ }
TTF_CloseFont(font);
switch (scene.textEngine) {
case TextEngineSurface:
@@ -618,8 +653,7 @@ int main(int argc, char *argv[])
break;
}
SDL_DestroyTexture(scene.message);
- Cleanup(0);
-
- /* Not reached, but fixes compiler warnings */
- return 0;
+ TTF_Quit();
+ SDL_Quit();
+ return result;
}
diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index f57e4309..1aad5ae9 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -273,6 +273,65 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL TTF_GetFontProperties(TTF_Font *fon
*/
extern SDL_DECLSPEC Uint32 SDLCALL TTF_GetFontGeneration(TTF_Font *font);
+/**
+ * Add a fallback font.
+ *
+ * Add a font that will be used for glyphs that are not in the current font. The fallback font should have the same size and style as the current font.
+ *
+ * If there are multiple fallback fonts, they are used in the order added.
+ *
+ * This updates any TTF_Text objects using this font.
+ *
+ * \param font the font to modify.
+ * \param fallback the font to add as a fallback.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should be called on the thread that created both
+ * fonts.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_ClearFallbackFonts
+ * \sa TTF_RemoveFallbackFont
+ */
+extern SDL_DECLSPEC bool SDLCALL TTF_AddFallbackFont(TTF_Font *font, TTF_Font *fallback);
+
+/**
+ * Remove a fallback font.
+ *
+ * This updates any TTF_Text objects using this font.
+ *
+ * \param font the font to modify.
+ * \param fallback the font to remove as a fallback.
+ *
+ * \threadsafety This function should be called on the thread that created both
+ * fonts.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_AddFallbackFont
+ * \sa TTF_ClearFallbackFonts
+ */
+extern SDL_DECLSPEC void SDLCALL TTF_RemoveFallbackFont(TTF_Font *font, TTF_Font *fallback);
+
+/**
+ * Remove all fallback fonts.
+ *
+ * This updates any TTF_Text objects using this font.
+ *
+ * \param font the font to modify.
+ *
+ * \threadsafety This function should be called on the thread that created the
+ * font.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_AddFallbackFont
+ * \sa TTF_RemoveFallbackFont
+ */
+extern SDL_DECLSPEC void SDLCALL TTF_ClearFallbackFonts(TTF_Font *font);
+
/**
* Set a font's size dynamically.
*
diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index 73a8c31c..4bff177a 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -225,6 +225,12 @@ typedef struct PosBuf {
int offset;
} PosBuf_t;
+// A structure maintaining a list of fonts
+typedef struct TTF_FontList {
+ TTF_Font *font;
+ struct TTF_FontList *next;
+} TTF_FontList;
+
// The structure used to hold internal font information
struct TTF_Font {
// Freetype2 maintains all sorts of useful info itself
@@ -299,6 +305,10 @@ struct TTF_Font {
// Extra layout setting for wrapped text
TTF_HorizontalAlignment horizontal_align;
+
+ // Fallback fonts
+ TTF_FontList *fallbacks;
+ TTF_FontList *fallback_for;
};
typedef struct
@@ -2113,16 +2123,25 @@ static bool RemoveFontTextReference(TTF_Font *font, TTF_Text *text)
return SDL_RemoveFromHashTable(font->text, text);
}
-static void UpdateFontText(TTF_Font *font)
+static void UpdateFontText(TTF_Font *font, TTF_Font *initial_font)
{
- if (!font->text) {
+ if (!initial_font) {
+ initial_font = font;
+ } else if (font == initial_font) {
+ // font fallback loop
return;
}
- TTF_Text *text = NULL;
- void *iter = NULL;
- while (SDL_IterateHashTable(font->text, (const void **)&text, NULL, &iter)) {
- text->internal->needs_layout_update = true;
+ if (font->text) {
+ TTF_Text *text = NULL;
+ void *iter = NULL;
+ while (SDL_IterateHashTable(font->text, (const void **)&text, NULL, &iter)) {
+ text->internal->needs_layout_update = true;
+ }
+ }
+
+ for (TTF_FontList *list = font->fallback_for; list; list = list->next) {
+ UpdateFontText(list->font, initial_font);
}
}
@@ -2143,6 +2162,91 @@ Uint32 TTF_GetFontGeneration(TTF_Font *font)
return font->generation;
}
+bool TTF_AddFallbackFont(TTF_Font *font, TTF_Font *fallback)
+{
+ TTF_CHECK_FONT(font, false);
+ TTF_CHECK_POINTER("fallback", fallback, false);
+
+ TTF_FontList *fallback_entry = (TTF_FontList *)SDL_calloc(1, sizeof(*fallback_entry));
+ TTF_FontList *fallback_for_entry = (TTF_FontList *)SDL_calloc(1, sizeof(*fallback_entry));
+ if (!fallback_entry || !fallback_for_entry) {
+ SDL_free(fallback_entry);
+ SDL_free(fallback_for_entry);
+ return false;
+ }
+
+ TTF_FontList *prev = NULL;
+ for (TTF_FontList *list = font->fallbacks; list; prev = list, list = list->next) {
+ continue;
+ }
+ fallback_entry->font = fallback;
+ if (prev) {
+ prev->next = fallback_entry;
+ } else {
+ font->fallbacks = fallback_entry;
+ }
+
+ prev = NULL;
+ for (TTF_FontList *list = fallback->fallback_for; list; prev = list, list = list->next) {
+ continue;
+ }
+ fallback_for_entry->font = font;
+ if (prev) {
+ prev->next = fallback_for_entry;
+ } else {
+ fallback->fallback_for = fallback_for_entry;
+ }
+
+ UpdateFontText(font, NULL);
+ return true;
+}
+
+void TTF_RemoveFallbackFont(TTF_Font *font, TTF_Font *fallback)
+{
+ if (!font || !fallback) {
+ return;
+ }
+
+ TTF_FontList *prev = NULL;
+ for (TTF_FontList *list = font->fallbacks; list; prev = list, list = list->next) {
+ if (fallback == list->font) {
+ if (prev) {
+ prev->next = list->next;
+ } else {
+ font->fallbacks = list->next;
+ }
+ SDL_free(list);
+ break;
+ }
+ }
+
+ prev = NULL;
+ for (TTF_FontList *list = fallback->fallback_for; list; prev = list, list = list->next) {
+ if (font == list->font) {
+ if (prev) {
+ prev->next = list->next;
+ } else {
+ fallback->fallback_for = list->next;
+ }
+ SDL_free(list);
+ break;
+ }
+ }
+
+ UpdateFontText(font, NULL);
+}
+
+void TTF_ClearFallbackFonts(TTF_Font *font)
+{
+ if (!font) {
+ return;
+ }
+
+ while (font->fallbacks) {
+ TTF_RemoveFallbackFont(font, font->fallbacks->font);
+ }
+}
+
// Update font parameter depending on a style change
static void TTF_InitFontMetrics(TTF_Font *font)
{
@@ -2853,20 +2957,52 @@ static FT_UInt get_char_index(TTF_Font *font, Uint32 ch)
return FT_Get_Char_Index(font->face, ch);
}
+static FT_UInt get_char_index_fallback(TTF_Font *font, Uint32 ch, TTF_Font *initial_font, TTF_Font **glyph_font)
+{
+ if (!initial_font) {
+ initial_font = font;
+ } else if (font == initial_font) {
+ // font fallback loop
+ return 0;
+ }
+
+ FT_UInt idx = get_char_index(font, ch);
+ if (idx > 0) {
+ if (glyph_font) {
+ *glyph_font = font;
+ }
+ } else {
+ for (TTF_FontList *list = font->fallbacks; list; list = list->next) {
+ idx = get_char_index_fallback(list->font, ch, initial_font, glyph_font);
+ if (idx > 0) {
+ break;
+ }
+ }
+ }
+ return idx;
+}
+
+
-static bool Find_GlyphMetrics(TTF_Font *font, Uint32 ch, c_glyph **out_glyph)
+static bool Find_GlyphMetrics(TTF_Font *font, Uint32 ch, c_glyph **out_glyph, bool allow_fallback)
{
TTF_CHECK_FONT(font, false);
- FT_UInt idx = get_char_index(font, ch);
- return Find_GlyphByIndex(font, idx, 0, 0, 0, 0, 0, 0, out_glyph, NULL);
+ TTF_Font *glyph_font = font;
+ FT_UInt idx;
+ if (allow_fallback) {
+ idx = get_char_index_fallback(font, ch, NULL, &glyph_font);
+ } else {
+ idx = get_char_index(font, ch);
+ }
+ return Find_GlyphByIndex(glyph_font, idx, 0, 0, 0, 0, 0, 0, out_glyph, NULL);
}
bool TTF_FontHasGlyph(TTF_Font *font, Uint32 ch)
{
TTF_CHECK_FONT(font, false);
- return (get_char_index(font, ch) > 0);
+ return (get_char_index_fallback(font, ch, NULL, NULL) > 0);
}
SDL_Surface *TTF_GetGlyphImage(TTF_Font *font, Uint32 ch)
@@ -2875,13 +3011,14 @@ SDL_Surface *TTF_GetGlyphImage(TTF_Font *font, Uint32 ch)
TTF_CHECK_FONT(font, NULL);
- idx = get_char_index(font, ch);
+ TTF_Font *glyph_font = NULL;
+ idx = get_char_index_fallback(font, ch, NULL, &glyph_font);
if (idx == 0) {
SDL_SetError("Codepoint not in font");
return NULL;
}
- return TTF_GetGlyphImageForIndex(font, idx);
+ return TTF_GetGlyphImageForIndex(glyph_font, idx);
}
SDL_Surface *TTF_GetGlyphImageForIndex(TTF_Font *font, Uint32 glyph_index)
@@ -2939,7 +3076,7 @@ bool TTF_GetGlyphMetrics(TTF_Font *font, Uint32 ch, int *minx, int *maxx, int *m
TTF_CHECK_FONT(font, false);
- if (!Find_GlyphMetrics(font, ch, &glyph)) {
+ if (!Find_GlyphMetrics(font, ch, &glyph, true)) {
return false;
}
@@ -2983,21 +3120,16 @@ bool TTF_GetGlyphKerning(TTF_Font *font, Uint32 previous_ch, Uint32 ch, int *ker
return true;
}
- if (!Find_GlyphMetrics(font, ch, &glyph)) {
- return false;
- }
-
- if (!Find_GlyphMetrics(font, previous_ch, &prev_glyph)) {
- return false;
- }
-
- error = FT_Get_Kerning(font->face, prev_glyph->index, glyph->index, FT_KERNING_DEFAULT, &delta);
- if (error) {
- return TTF_SetFTError("Couldn't get glyph kerning", error);
- }
+ if (Find_GlyphMetrics(font, ch, &glyph, false) &&
+ Find_GlyphMetrics(font, previous_ch, &prev_glyph, false)) {
+ error = FT_Get_Kerning(font->face, prev_glyph->index, glyph->index, FT_KERNING_DEFAULT, &delta);
+ if (error) {
+ return TTF_SetFTError("Couldn't get glyph kerning", error);
+ }
- if (kerning) {
- *kerning = (int)(delta.x >> 6);
+ if (kerning) {
+ *kerning = (int)(delta.x >> 6);
+ }
}
return true;
}
@@ -3021,6 +3153,7 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
int advance_if_bold = 0;
#else
int skip_first = 1;
+ TTF_Font *prev_font = NULL;
FT_UInt prev_index = 0;
FT_Pos prev_delta = 0;
#endif
@@ -3113,7 +3246,7 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
offset = (int)(text - start);
Uint32 c = SDL_StepUTF8(&text, &length);
TTF_Font *glyph_font = font;
- FT_UInt idx = get_char_index(glyph_font, c);
+ FT_UInt idx = get_char_index_fallback(font, c, NULL, &glyph_font);
if (c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED) {
continue;
@@ -3151,11 +3284,12 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
x += prev_advance;
prev_advance = glyph->advance;
if (font->use_kerning) {
- if (prev_index && glyph->index) {
+ if (prev_font == glyph_font && prev_index && glyph->index) {
FT_Vector delta;
FT_Get_Kerning(glyph_font->face, prev_index, glyph->index, FT_KERNING_UNFITTED, &delta);
x += delta.x;
}
+ prev_font = glyph_font;
prev_index = glyph->index;
}
// FT SUBPIXEL : LCD_MODE_LIGHT_SUBPIXEL
@@ -4957,7 +5091,7 @@ bool TTF_SetFontSizeDPI(TTF_Font *font, float ptsize, int hdpi, int vdpi)
font->vdpi = vdpi;
Flush_Cache(font);
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
#if TTF_USE_HARFBUZZ
// Call when size or variations settings on underlying FT_Face change.
@@ -5024,7 +5158,7 @@ void TTF_SetFontStyle(TTF_Font *font, TTF_FontStyleFlags style)
if ((font->style | TTF_STYLE_NO_GLYPH_CHANGE) != (prev_style | TTF_STYLE_NO_GLYPH_CHANGE)) {
Flush_Cache(font);
}
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
}
TTF_FontStyleFlags TTF_GetFontStyle(const TTF_Font *font)
@@ -5086,7 +5220,7 @@ bool TTF_SetFontOutline(TTF_Font *font, int outline)
TTF_InitFontMetrics(font);
Flush_Cache(font);
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
return true;
}
@@ -5127,7 +5261,7 @@ void TTF_SetFontHinting(TTF_Font *font, TTF_HintingFlags hinting)
#endif
Flush_Cache(font);
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
}
TTF_HintingFlags TTF_GetFontHinting(const TTF_Font *font)
@@ -5154,7 +5288,7 @@ bool TTF_SetFontSDF(TTF_Font *font, bool enabled)
#if TTF_USE_SDF
font->render_sdf = enabled;
Flush_Cache(font);
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
return true;
#else
(void)enabled;
@@ -5187,7 +5321,7 @@ void TTF_SetFontWrapAlignment(TTF_Font *font, TTF_HorizontalAlignment align)
// Ignore invalid values
break;
}
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
}
TTF_HorizontalAlignment TTF_GetFontWrapAlignment(const TTF_Font *font)
@@ -5227,7 +5361,7 @@ void TTF_SetFontLineSkip(TTF_Font *font, int lineskip)
}
font->lineskip = lineskip;
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
}
int TTF_GetFontLineSkip(const TTF_Font *font)
@@ -5251,7 +5385,7 @@ void TTF_SetFontKerning(TTF_Font *font, bool enabled)
#else
font->use_kerning = enabled && FT_HAS_KERNING(font->face);
#endif
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
}
bool TTF_GetFontKerning(const TTF_Font *font)
@@ -5319,7 +5453,7 @@ bool TTF_SetFontDirection(TTF_Font *font, TTF_Direction direction)
}
font->hb_direction = hb_direction;
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
return true;
#else
(void) direction;
@@ -5373,7 +5507,7 @@ bool TTF_SetFontScript(TTF_Font *font, const char *script)
}
font->hb_script = hb_script;
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
return true;
#else
(void) script;
@@ -5441,7 +5575,7 @@ bool TTF_SetFontLanguage(TTF_Font *font, const char *language_bcp47)
}
font->hb_language = hb_language;
- UpdateFontText(font);
+ UpdateFontText(font, NULL);
return true;
#else
(void) language_bcp47;
@@ -5472,6 +5606,11 @@ void TTF_CloseFont(TTF_Font *font)
}
Flush_Cache(font);
+ TTF_ClearFallbackFonts(font);
+ while (font->fallback_for) {
+ TTF_RemoveFallbackFont(font->fallback_for->font, font);
+ }
+
#if TTF_USE_HARFBUZZ
hb_font_destroy(font->hb_font);
#endif
diff --git a/src/SDL_ttf.sym b/src/SDL_ttf.sym
index bfed9146..ef188a7b 100644
--- a/src/SDL_ttf.sym
+++ b/src/SDL_ttf.sym
@@ -1,6 +1,8 @@
SDL3_ttf_0.0.0 {
global:
+ TTF_AddFallbackFont;
TTF_AppendTextString;
+ TTF_ClearFallbackFonts;
TTF_CloseFont;
TTF_CreateGPUTextEngine;
TTF_CreateRendererTextEngine;
@@ -64,6 +66,7 @@ SDL3_ttf_0.0.0 {
TTF_OpenFontIO;
TTF_OpenFontWithProperties;
TTF_Quit;
+ TTF_RemoveFallbackFont;
TTF_RenderGlyph_Blended;
TTF_RenderGlyph_LCD;
TTF_RenderGlyph_Shaded;