SDL_ttf: Allow each glyph to come from a different font face

From ca057090791f0ad8de1769feba9f03db02de6988 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 25 Jan 2025 08:19:33 -0800
Subject: [PATCH] Allow each glyph to come from a different font face

---
 include/SDL3_ttf/SDL_textengine.h |  3 ++-
 src/SDL_gpu_textengine.c          |  3 ++-
 src/SDL_renderer_textengine.c     |  3 ++-
 src/SDL_surface_textengine.c      | 10 +++++-----
 src/SDL_ttf.c                     | 31 +++++++++++++++++++------------
 5 files changed, 30 insertions(+), 20 deletions(-)

diff --git a/include/SDL3_ttf/SDL_textengine.h b/include/SDL3_ttf/SDL_textengine.h
index 287809f8..78e34c46 100644
--- a/include/SDL3_ttf/SDL_textengine.h
+++ b/include/SDL3_ttf/SDL_textengine.h
@@ -79,7 +79,8 @@ typedef struct TTF_CopyOperation
                                  later. In this case the glyphs and codepoints are grouped
                                  together and the group bounding box is the union of the dst
                                  rectangles for the corresponding glyphs. */
-    Uint32 glyph_index;     /**< The glyph index of the glyph to be drawn, can be passed to TTF_GetGlyphForIndex() */
+    TTF_Font *glyph_font;   /**< The font containing the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
+    Uint32 glyph_index;     /**< The glyph index of the glyph to be drawn, can be passed to TTF_GetGlyphImageForIndex() */
     SDL_Rect src;           /**< The area within the glyph to be drawn */
     SDL_Rect dst;           /**< The drawing coordinates of the glyph, in pixels. The x coordinate is relative to the left side of the text area, going right, and the y coordinate is relative to the top side of the text area, going down. */
     void *reserved;
diff --git a/src/SDL_gpu_textengine.c b/src/SDL_gpu_textengine.c
index 16eab888..35c05742 100644
--- a/src/SDL_gpu_textengine.c
+++ b/src/SDL_gpu_textengine.c
@@ -505,6 +505,7 @@ static bool CreateMissingGlyphs(TTF_GPUTextEngineData *enginedata, TTF_GPUTextEn
     for (int i = 0; i < num_ops; ++i) {
         TTF_DrawOperation *op = &ops[i];
         if (op->cmd == TTF_DRAW_COMMAND_COPY && !op->copy.reserved) {
+            TTF_Font *glyph_font = op->copy.glyph_font;
             Uint32 glyph_index = op->copy.glyph_index;
             if (SDL_FindInHashTable(checked, (const void *)(uintptr_t)glyph_index, NULL)) {
                 continue;
@@ -513,7 +514,7 @@ static bool CreateMissingGlyphs(TTF_GPUTextEngineData *enginedata, TTF_GPUTextEn
                 goto done;
             }
 
-            surfaces[i] = TTF_GetGlyphImageForIndex(fontdata->font, glyph_index);
+            surfaces[i] = TTF_GetGlyphImageForIndex(glyph_font, glyph_index);
             if (!surfaces[i]) {
                 goto done;
             }
diff --git a/src/SDL_renderer_textengine.c b/src/SDL_renderer_textengine.c
index 7eef5829..3ff42e1f 100644
--- a/src/SDL_renderer_textengine.c
+++ b/src/SDL_renderer_textengine.c
@@ -439,6 +439,7 @@ static bool CreateMissingGlyphs(TTF_RendererTextEngineData *enginedata, TTF_Rend
     for (int i = 0; i < num_ops; ++i) {
         TTF_DrawOperation *op = &ops[i];
         if (op->cmd == TTF_DRAW_COMMAND_COPY && !op->copy.reserved) {
+            TTF_Font *glyph_font = op->copy.glyph_font;
             Uint32 glyph_index = op->copy.glyph_index;
             if (SDL_FindInHashTable(checked, (const void *)(uintptr_t)glyph_index, NULL)) {
                 continue;
@@ -447,7 +448,7 @@ static bool CreateMissingGlyphs(TTF_RendererTextEngineData *enginedata, TTF_Rend
                 goto done;
             }
 
-            surfaces[i] = TTF_GetGlyphImageForIndex(fontdata->font, glyph_index);
+            surfaces[i] = TTF_GetGlyphImageForIndex(glyph_font, glyph_index);
             if (!surfaces[i]) {
                 goto done;
             }
diff --git a/src/SDL_surface_textengine.c b/src/SDL_surface_textengine.c
index e5d313f3..5f1a3565 100644
--- a/src/SDL_surface_textengine.c
+++ b/src/SDL_surface_textengine.c
@@ -80,12 +80,12 @@ static TTF_SurfaceTextEngineGlyphData *CreateGlyphData(SDL_Surface *surface)
     return data;
 }
 
-static TTF_SurfaceTextEngineGlyphData *GetGlyphData(TTF_SurfaceTextEngineFontData *fontdata, Uint32 idx)
+static TTF_SurfaceTextEngineGlyphData *GetGlyphData(TTF_SurfaceTextEngineFontData *fontdata, TTF_Font *glyph_font, Uint32 glyph_index)
 {
     TTF_SurfaceTextEngineGlyphData *data;
 
-    if (!SDL_FindInHashTable(fontdata->glyphs, (const void *)(uintptr_t)idx, (const void **)&data)) {
-        SDL_Surface *surface = TTF_GetGlyphImageForIndex(fontdata->font, idx);
+    if (!SDL_FindInHashTable(fontdata->glyphs, (const void *)(uintptr_t)glyph_index, (const void **)&data)) {
+        SDL_Surface *surface = TTF_GetGlyphImageForIndex(glyph_font, glyph_index);
         if (!surface) {
             return NULL;
         }
@@ -95,7 +95,7 @@ static TTF_SurfaceTextEngineGlyphData *GetGlyphData(TTF_SurfaceTextEngineFontDat
             return NULL;
         }
 
-        if (!SDL_InsertIntoHashTable(fontdata->glyphs, (const void *)(uintptr_t)idx, data)) {
+        if (!SDL_InsertIntoHashTable(fontdata->glyphs, (const void *)(uintptr_t)glyph_index, data)) {
             DestroyGlyphData(data);
             return NULL;
         }
@@ -140,7 +140,7 @@ static TTF_SurfaceTextEngineTextData *CreateTextData(TTF_SurfaceTextEngineFontDa
     for (int i = 0; i < data->num_ops; ++i) {
         TTF_DrawOperation *op = &data->ops[i];
         if (op->cmd == TTF_DRAW_COMMAND_COPY) {
-            TTF_SurfaceTextEngineGlyphData *glyph = GetGlyphData(fontdata, op->copy.glyph_index);
+            TTF_SurfaceTextEngineGlyphData *glyph = GetGlyphData(fontdata, op->copy.glyph_font, op->copy.glyph_index);
             if (!glyph) {
                 DestroyTextData(data);
                 return NULL;
diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index 4290fe48..73a8c31c 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -218,6 +218,7 @@ typedef struct cached_glyph {
 /* Internal buffer to store positions computed by TTF_Size_Internal()
  * for rendered string by Render_Line() */
 typedef struct PosBuf {
+    TTF_Font *font;
     FT_UInt index;
     int x;
     int y;
@@ -1173,12 +1174,13 @@ static bool Render_Line_##NAME(TTF_Font *font, SDL_Surface *textbuf, int xstart,
     int i;                                                                                                              \
     Uint8 fg_alpha = (fg ? fg->a : 0);                                                                                  \
     for (i = 0; i < font->pos_len; i++) {                                                                               \
+        TTF_Font *glyph_font = font->pos_buf[i].font;                                                                   \
         FT_UInt idx = font->pos_buf[i].index;                                                                           \
-        int x       = font->pos_buf[i].x;                                                                               \
-        int y       = font->pos_buf[i].y;                                                                               \
+        int x = font->pos_buf[i].x;                                                                                     \
+        int y = font->pos_buf[i].y;                                                                                     \
         TTF_Image *image;                                                                                               \
                                                                                                                         \
-        if (Find_GlyphByIndex(font, idx, WB_WP_WC, WS, x & 63, NULL, &image)) {                                         \
+        if (Find_GlyphByIndex(glyph_font, idx, WB_WP_WC, WS, x & 63, NULL, &image)) {                                   \
             int above_w, above_h;                                                                                       \
             Uint32 dstskip;                                                                                             \
             Sint32 srcskip; /* Can be negative */                                                                       \
@@ -1435,13 +1437,14 @@ static bool Render_Line_TextEngine(TTF_Font *font, int xstart, int ystart, int w
     bounds.h = font->height;
 
     for (i = 0; i < font->pos_len; i++) {
+        TTF_Font *glyph_font = font->pos_buf[i].font;
         FT_UInt idx = font->pos_buf[i].index;
-        int x       = font->pos_buf[i].x;
-        int y       = font->pos_buf[i].y;
-        int offset  = font->pos_buf[i].offset;
+        int x = font->pos_buf[i].x;
+        int y = font->pos_buf[i].y;
+        int offset = font->pos_buf[i].offset;
         c_glyph *glyph;
 
-        if (Find_GlyphByIndex(font, idx, 0, 0, 0, 0, 0, 0, &glyph, NULL)) {
+        if (Find_GlyphByIndex(glyph_font, idx, 0, 0, 0, 0, 0, 0, &glyph, NULL)) {
             int above_w, above_h;
             int glyph_x = 0;
             int glyph_y = 0;
@@ -1480,6 +1483,7 @@ static bool Render_Line_TextEngine(TTF_Font *font, int xstart, int ystart, int w
                 op = &ops[op_index++];
                 op->cmd = TTF_DRAW_COMMAND_COPY;
                 op->copy.text_offset = offset;
+                op->copy.glyph_font = glyph_font;
                 op->copy.glyph_index = idx;
                 op->copy.src.x = glyph_x;
                 op->copy.src.y = glyph_y;
@@ -3093,6 +3097,7 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
     // Load and render each character
     int offset = 0;
     for (g = 0; g < glyph_count; g++) {
+        TTF_Font *glyph_font = font;
         FT_UInt idx   = hb_glyph_info[g].codepoint;
         int x_advance = hb_glyph_position[g].x_advance;
         int y_advance = hb_glyph_position[g].y_advance;
@@ -3107,7 +3112,8 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
     while (length > 0) {
         offset = (int)(text - start);
         Uint32 c = SDL_StepUTF8(&text, &length);
-        FT_UInt idx = get_char_index(font, c);
+        TTF_Font *glyph_font = font;
+        FT_UInt idx = get_char_index(glyph_font, c);
 
         if (c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED) {
             continue;
@@ -3117,7 +3123,7 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
             --length;
         }
 #endif
-        if (!Find_GlyphByIndex(font, idx, 0, 0, 0, 0, 0, 0, &glyph, NULL)) {
+        if (!Find_GlyphByIndex(glyph_font, idx, 0, 0, 0, 0, 0, 0, &glyph, NULL)) {
             goto failure;
         }
 
@@ -3136,7 +3142,7 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
 
 #if TTF_USE_HARFBUZZ
         // Compute positions
-        pos_x  = x                     + x_offset;
+        pos_x  = x                         + x_offset;
         pos_y  = y + F26Dot6(font->ascent) - y_offset;
         x     += x_advance + advance_if_bold;
         y     += y_advance;
@@ -3147,7 +3153,7 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
         if (font->use_kerning) {
             if (prev_index && glyph->index) {
                 FT_Vector delta;
-                FT_Get_Kerning(font->face, prev_index, glyph->index, FT_KERNING_UNFITTED, &delta);
+                FT_Get_Kerning(glyph_font->face, prev_index, glyph->index, FT_KERNING_UNFITTED, &delta);
                 x += delta.x;
             }
             prev_index = glyph->index;
@@ -3177,9 +3183,10 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
         pos_y = F26Dot6(font->ascent);
 #endif
         // Store things for Render_Line()
+        font->pos_buf[font->pos_len].font   = glyph_font;
+        font->pos_buf[font->pos_len].index  = idx;
         font->pos_buf[font->pos_len].x      = pos_x;
         font->pos_buf[font->pos_len].y      = pos_y;
-        font->pos_buf[font->pos_len].index  = idx;
         font->pos_buf[font->pos_len].offset = offset;
         font->pos_len += 1;