SDL_ttf: Implemented fallback fonts for harfbuzz support

From bc981decced8dce9e73cf24f8a90718f68186eba Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 26 Jan 2025 13:41:42 -0800
Subject: [PATCH] Implemented fallback fonts for harfbuzz support

---
 src/SDL_ttf.c | 462 +++++++++++++++++++++++++++++++-------------------
 1 file changed, 287 insertions(+), 175 deletions(-)

diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index 4bff177a..4f9e2615 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -217,13 +217,23 @@ typedef struct cached_glyph {
 
 /* Internal buffer to store positions computed by TTF_Size_Internal()
  * for rendered string by Render_Line() */
-typedef struct PosBuf {
+typedef struct GlyphPosition {
     TTF_Font *font;
     FT_UInt index;
+    int x_offset;
+    int y_offset;
+    int x_advance;
+    int y_advance;
     int x;
     int y;
     int offset;
-} PosBuf_t;
+} GlyphPosition;
+
+typedef struct GlyphPositions {
+    GlyphPosition *pos;
+    int len;
+    int maxlen;
+} GlyphPositions;
 
 // A structure maintaining a list of fonts
 typedef struct TTF_FontList {
@@ -285,9 +295,7 @@ struct TTF_Font {
 
     /* Internal buffer to store positions computed by TTF_Size_Internal()
      * for rendered string by Render_Line() */
-    PosBuf_t *pos_buf;
-    int pos_len;
-    int pos_max;
+    GlyphPositions positions;
     int num_clusters;
 
     // Hinting modes
@@ -1183,11 +1191,12 @@ static bool Render_Line_##NAME(TTF_Font *font, SDL_Surface *textbuf, int xstart,
     const int bpp = ((IS_BLENDED || IS_LCD) ? 4 : 1);                                                                   \
     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;                                                                                     \
+    for (i = 0; i < font->positions.len; i++) {                                                                         \
+        GlyphPosition *pos = &font->positions.pos[i];                                                                   \
+        TTF_Font *glyph_font = pos->font;                                                                               \
+        FT_UInt idx = pos->index;                                                                                       \
+        int x = pos->x;                                                                                                 \
+        int y = pos->y;                                                                                                 \
         TTF_Image *image;                                                                                               \
                                                                                                                         \
         if (Find_GlyphByIndex(glyph_font, idx, WB_WP_WC, WS, x & 63, NULL, &image)) {                                   \
@@ -1370,7 +1379,7 @@ static int (*Render_Line_SDF_LCD_SP)(TTF_Font *font, SDL_Surface *textbuf, int x
 
 static bool Render_Line(const render_mode_t render_mode, int subpixel, TTF_Font *font, SDL_Surface *textbuf, int xstart, int ystart, SDL_Color fg)
 {
-    // Render line (pos_buf) to textbuf at (xstart, ystart)
+    // Render line (positions) to textbuf at (xstart, ystart)
 
     // Subpixel with RENDER_SOLID doesn't make sense.
     // (and 'cached->subpixel.translation' would need to distinguish bitmap/pixmap).
@@ -1446,12 +1455,13 @@ static bool Render_Line_TextEngine(TTF_Font *font, int xstart, int ystart, int w
     bounds.w = 0;
     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;
+    for (i = 0; i < font->positions.len; i++) {
+        GlyphPosition *pos = &font->positions.pos[i];
+        TTF_Font *glyph_font = pos->font;
+        FT_UInt idx = pos->index;
+        int x = pos->x;
+        int y = pos->y;
+        int offset = pos->offset;
         c_glyph *glyph;
 
         if (Find_GlyphByIndex(glyph_font, idx, 0, 0, 0, 0, 0, 0, &glyph, NULL)) {
@@ -1505,7 +1515,7 @@ static bool Render_Line_TextEngine(TTF_Font *font, int xstart, int ystart, int w
                 op->copy.dst.h = op->copy.src.h;
             } else {
                 // Use the distance to the next glyph as our bounds width
-                glyph_width = FT_FLOOR(glyph->advance);
+                glyph_width = FT_FLOOR(pos->x_advance);
             }
 
             bounds.x = x;
@@ -2050,15 +2060,6 @@ TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
     font->ft_load_target = FT_LOAD_TARGET_NORMAL;
     TTF_SetFontKerning(font, true);
 
-    font->pos_len = 0;
-    font->pos_max = 16;
-    font->pos_buf = (PosBuf_t *)SDL_malloc(font->pos_max * sizeof (font->pos_buf[0]));
-    if (!font->pos_buf) {
-        SDL_SetError("Out of memory");
-        TTF_CloseFont(font);
-        return NULL;
-    }
-
 #if TTF_USE_HARFBUZZ
     font->hb_font = hb_ft_font_create(face, NULL);
     if (font->hb_font == NULL) {
@@ -3134,82 +3135,20 @@ bool TTF_GetGlyphKerning(TTF_Font *font, Uint32 previous_ch, Uint32 ch, int *ker
     return true;
 }
 
-static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, int *w, int *h, int *xstart, int *ystart, bool measure_width, int max_width, int *measured_width, size_t *measured_length)
+static bool TTF_CollectGlyphsFromFont(TTF_Font *font, const char *text, size_t length, GlyphPositions *positions)
 {
-    int x = 0;
-    int pos_x, pos_y;
-    int minx = 0, maxx = 0;
-    int miny = 0, maxy = 0;
-    c_glyph *glyph;
 #if TTF_USE_HARFBUZZ
-    hb_direction_t hb_direction;
-    hb_script_t hb_script;
-    hb_buffer_t *hb_buffer = NULL;
-    unsigned int g;
-    unsigned int glyph_count;
-    hb_glyph_info_t *hb_glyph_info;
-    hb_glyph_position_t *hb_glyph_position;
-    int y = 0;
-    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
-    int prev_advance = 0;
-
-    if (w) {
-        *w = 0;
-    }
-    if (h) {
-        *h = 0;
-    }
-    if (measured_width) {
-        *measured_width = 0;
-    }
-    if (measured_length) {
-        *measured_length = 0;
-    }
-
-    TTF_CHECK_INITIALIZED(false);
-    TTF_CHECK_POINTER("font", font, false);
-    TTF_CHECK_POINTER("text", text, false);
-
-    if (measured_length) {
-        *measured_length = length;
-    }
-
-    maxy = font->height;
-
-    // Reset buffer
-    font->pos_len = 0;
-    font->num_clusters = 0;
-
-    int last_offset = -1;
-
-#if TTF_USE_HARFBUZZ
-
-    // Adjust for bold text
-    if (TTF_HANDLE_STYLE_BOLD(font)) {
-        advance_if_bold = F26Dot6(font->glyph_overhang);
-    }
-
     // Create a buffer for harfbuzz to use
-    hb_buffer = hb_buffer_create();
-    if (hb_buffer == NULL) {
-       SDL_SetError("Cannot create harfbuzz buffer");
-       goto failure;
+    hb_buffer_t *hb_buffer = hb_buffer_create();
+    if (!hb_buffer) {
+        SDL_SetError("Cannot create harfbuzz buffer");
+        return false;
     }
 
-
-    hb_direction = font->hb_direction;
-    hb_script = font->hb_script;
-
     // Set global configuration
     hb_buffer_set_language(hb_buffer, font->hb_language);
-    hb_buffer_set_direction(hb_buffer, hb_direction);
-    hb_buffer_set_script(hb_buffer, hb_script);
+    hb_buffer_set_direction(hb_buffer, font->hb_direction);
+    hb_buffer_set_script(hb_buffer, font->hb_script);
     hb_buffer_guess_segment_properties(hb_buffer);
 
     // Layout the text
@@ -3224,30 +3163,56 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
     hb_shape(font->hb_font, hb_buffer, userfeatures, 1);
 
     // Get the result
-    hb_glyph_info = hb_buffer_get_glyph_infos(hb_buffer, &glyph_count);
-    hb_glyph_position = hb_buffer_get_glyph_positions(hb_buffer, &glyph_count);
+    unsigned int glyph_count_u = 0;
+    hb_glyph_info_t *hb_glyph_info = hb_buffer_get_glyph_infos(hb_buffer, &glyph_count_u);
+    hb_glyph_position_t *hb_glyph_position = hb_buffer_get_glyph_positions(hb_buffer, &glyph_count_u);
+
+    // Adjust for bold text
+    int advance_if_bold = 0;
+    if (TTF_HANDLE_STYLE_BOLD(font)) {
+        advance_if_bold = F26Dot6(font->glyph_overhang);
+    }
+
+    // Realloc, if needed
+    int glyph_count = (int)glyph_count_u;
+    if (glyph_count > positions->maxlen) {
+        GlyphPosition *saved = positions->pos;
+        positions->pos = (GlyphPosition *)SDL_realloc(positions->pos, glyph_count * sizeof(*positions->pos));
+        if (positions->pos) {
+            positions->maxlen = glyph_count;
+        } else {
+            positions->pos = saved;
+            hb_buffer_destroy(hb_buffer);
+            return false;
+        }
+    }
+    positions->len = glyph_count;
+
+    for (int i = 0; i < glyph_count; ++i) {
+        GlyphPosition *pos = &positions->pos[i];
+        pos->font = font;
+        pos->index = hb_glyph_info[i].codepoint;
+        pos->x_advance = hb_glyph_position[i].x_advance + advance_if_bold;
+        pos->y_advance = hb_glyph_position[i].y_advance;
+        pos->x_offset = hb_glyph_position[i].x_offset;
+        pos->y_offset = hb_glyph_position[i].y_offset;
+        pos->offset = (int)hb_glyph_info[i].cluster;
+    }
+    hb_buffer_destroy(hb_buffer);
 
-    // 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;
-        int x_offset  = hb_glyph_position[g].x_offset;
-        int y_offset  = hb_glyph_position[g].y_offset;
-
-        offset = (int)hb_glyph_info[g].cluster;
 #else
+    bool skip_first = true;
+    FT_UInt prev_index = 0;
+    FT_Pos  prev_delta = 0;
+
+    positions->len = 0;
+
     // Load each character and sum it's bounding box
     const char *start = text;
     int offset = 0;
     while (length > 0) {
         offset = (int)(text - start);
         Uint32 c = SDL_StepUTF8(&text, &length);
-        TTF_Font *glyph_font = font;
-        FT_UInt idx = get_char_index_fallback(font, c, NULL, &glyph_font);
-
         if (c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED) {
             continue;
         }
@@ -3255,79 +3220,237 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
         if (c == 0 && length > 0) {
             --length;
         }
-#endif
-        if (!Find_GlyphByIndex(glyph_font, idx, 0, 0, 0, 0, 0, 0, &glyph, NULL)) {
-            goto failure;
+
+        FT_UInt idx = get_char_index(font, c);
+        c_glyph *glyph = NULL;
+        if (!Find_GlyphByIndex(font, idx, 0, 0, 0, 0, 0, 0, &glyph, NULL)) {
+            return SDL_SetError("Couldn't find glyph %u in font", idx);
         }
 
         // Realloc, if needed
-        if (font->pos_len >= font->pos_max) {
-            PosBuf_t *saved = font->pos_buf;
-            font->pos_max *= 2;
-            font->pos_buf = (PosBuf_t *)SDL_realloc(font->pos_buf, font->pos_max * sizeof (font->pos_buf[0]));
-            if (font->pos_buf == NULL) {
-                font->pos_max /= 2;
-                font->pos_buf = saved;
-                SDL_SetError("Out of memory");
-                goto failure;
+        if (positions->len >= positions->maxlen) {
+            GlyphPosition *saved = positions->pos;
+            int maxlen = (positions->maxlen ? positions->maxlen * 2 : 16);
+            positions->pos = (GlyphPosition *)SDL_realloc(positions->pos, maxlen * sizeof(*positions->pos));
+            if (positions->pos) {
+                positions->maxlen = maxlen;
+            } else {
+                positions->pos = saved;
+                return false;
             }
         }
 
-#if TTF_USE_HARFBUZZ
         // Compute positions
-        pos_x  = x                         + x_offset;
-        pos_y  = y + F26Dot6(font->ascent) - y_offset;
-        x     += x_advance + advance_if_bold;
-        y     += y_advance;
-#else
-        // Compute positions
-        x += prev_advance;
-        prev_advance = glyph->advance;
+        GlyphPosition *pos = &positions->pos[positions->len++];
+        pos->font = font;
+        pos->index = idx;
+        pos->offset = offset;
+        pos->x_advance = glyph->advance;
+        pos->y_advance = 0;
+        pos->x_offset = 0;
+        pos->y_offset = 0;
         if (font->use_kerning) {
-            if (prev_font == glyph_font && prev_index && glyph->index) {
+            if (prev_index && glyph->index) {
                 FT_Vector delta;
-                FT_Get_Kerning(glyph_font->face, prev_index, glyph->index, FT_KERNING_UNFITTED, &delta);
-                x += delta.x;
+                FT_Get_Kerning(font->face, prev_index, glyph->index, FT_KERNING_UNFITTED, &delta);
+                pos->x_offset += delta.x;
             }
-            prev_font = glyph_font;
             prev_index = glyph->index;
         }
         // FT SUBPIXEL : LCD_MODE_LIGHT_SUBPIXEL
         if (font->render_subpixel) {
-            x += prev_delta;
             // Increment by prev_glyph->lsb_delta - prev_glyph->rsb_delta;
-            prev_delta = glyph->subpixel.lsb_minus_rsb;
+            pos->x_advance += glyph->subpixel.lsb_minus_rsb;
         } else {
             // FT KERNING_MODE_SMART: Use `lsb_delta' and `rsb_delta' to improve integer positioning of glyphs
             if (skip_first) {
-                skip_first = 0;
+                skip_first = false;
             } else {
                 if (prev_delta - glyph->kerning_smart.lsb_delta >  32 ) {
-                    x -= 64;
+                    pos->x_offset -= 64;
                 } else if (prev_delta - glyph->kerning_smart.lsb_delta < -31 ) {
-                    x += 64;
+                    pos->x_offset += 64;
                 }
             }
             prev_delta = glyph->kerning_smart.rsb_delta;
-            x = ((x + 32) & -64); // ROUND()
+            pos->x_offset = ((pos->x_offset + 32) & -64); // ROUND()
+        }
+    }
+#endif
+
+    return true;
+}
+
+static bool ReplaceGlyphPositions(GlyphPositions *positions, int start, int length, GlyphPositions *replacement)
+{
+    int initial_offset = positions->pos[start].offset;
+
+    int length_delta = (replacement->len - length);
+    if (length_delta != 0) {
+        int newlen = (positions->len + length_delta);
+        if (newlen > positions->maxlen) {
+            GlyphPosition *pos = SDL_realloc(positions->pos, newlen * sizeof(*pos));
+            if (!pos) {
+                return false;
+            }
+            positions->pos = pos;
+            positions->maxlen = newlen;
         }
 
-        // Compute positions where to copy the glyph bitmap
-        pos_x = x;
-        pos_y = F26Dot6(font->ascent);
+        int remainder = positions->len - (start + length);
+        if (remainder > 0) {
+            SDL_memmove(&positions->pos[start + replacement->len], &positions->pos[start + length], (remainder * sizeof(*positions->pos)));
+        }
+        positions->len = newlen;
+    }
+
+    SDL_memcpy(&positions->pos[start], replacement->pos, length * sizeof(*positions->pos));
+
+    for (int i = 0; i < replacement->len; ++i) {
+        positions->pos[start + i].offset += initial_offset;
+    }
+    return true;
+}
+
+static bool TTF_CollectGlyphsWithFallbacks(TTF_Font *font, const char *text, size_t length, GlyphPositions *positions, TTF_Font *initial_font)
+{
+    if (!initial_font) {
+        initial_font = font;
+    } else if (font == initial_font) {
+        // font fallback loop
+        return true;
+    }
+
+    if (!TTF_CollectGlyphsFromFont(font, text, length, positions)) {
+        return false;
+    }
+
+    // Create spans of missing characters and fill them in from fallback fonts
+    bool complete = false;
+    TTF_FontList *fallback = font->fallbacks;
+    while (!complete && fallback) {
+        complete = true;
+        int start = -1;
+        for (int i = 0; i < positions->len; ++i) {
+            GlyphPosition *pos = &positions->pos[i];
+            if (pos->index == 0) {
+                complete = false;
+                if (start < 0) {
+                    start = i;
+                }
+            } else if (start >= 0) {
+                // Fill in this span with the fallback font
+                GlyphPositions span;
+                SDL_zero(span);
+                int span_offset = positions->pos[start].offset;
+                int span_length = pos->offset - span_offset;
+                TTF_CollectGlyphsWithFallbacks(fallback->font, text + span_offset, span_length, &span, initial_font);
+                if (span.len > 0) {
+                    ReplaceGlyphPositions(positions, start, (i - start), &span);
+                    SDL_free(span.pos);
+                    i = start + span.len;
+                }
+                start = -1;
+            }
+        }
+        if (start >= 0) {
+            // Fill in this span with the fallback font
+            GlyphPositions span;
+            SDL_zero(span);
+            int span_offset = positions->pos[start].offset;
+            int span_length = (int)(length - span_offset);
+            TTF_CollectGlyphsWithFallbacks(fallback->font, text + span_offset, span_length, &span, initial_font);
+            if (span.len > 0) {
+                ReplaceGlyphPositions(positions, start, (positions->len - start), &span);
+                SDL_free(span.pos);
+            }
+        }
+        fallback = fallback->next;
+    }
+
+    return true;
+}
+
+static bool TTF_CollectGlyphs(TTF_Font *font, const char *text, size_t length, GlyphPositions *positions)
+{
+    if (!TTF_CollectGlyphsWithFallbacks(font, text, length, positions, NULL)) {
+        return false;
+    }
+
+    // Make sure any missing characters use the tofu from the initial font
+    for (int i = 0; i < positions->len; ++i) {
+        GlyphPosition *pos = &positions->pos[i];
+        if (pos->index == 0) {
+            pos->font = font;
+        }
+    }
+    return true;
+}
+
+static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, int *w, int *h, int *xstart, int *ystart, bool measure_width, int max_width, int *measured_width, size_t *measured_length)
+{
+    int x = 0, y = 0;
+    int pos_x, pos_y;
+    int minx = 0, maxx = 0;
+    int miny = 0, maxy = 0;
+
+    if (w) {
+        *w = 0;
+    }
+    if (h) {
+        *h = 0;
+    }
+    if (measured_width) {
+        *measured_width = 0;
+    }
+    if (measured_length) {
+        *measured_length = 0;
+    }
+
+    TTF_CHECK_INITIALIZED(false);
+    TTF_CHECK_POINTER("font", font, false);
+    TTF_CHECK_POINTER("text", text, false);
+
+    if (measured_length) {
+        *measured_length = length;
+    }
+
+    maxy = font->height;
+
+    // Reset buffer
+    font->num_clusters = 0;
+
+    GlyphPositions *positions = &font->positions;
+    if (!TTF_CollectGlyphs(font, text, length, positions)) {
+        return false;
+    }
+
+    int last_offset = -1;
+    for (int i = 0; i < positions->len; ++i) {
+        GlyphPosition *pos = &positions->pos[i];
+
+        c_glyph *glyph = NULL;
+        if (!Find_GlyphByIndex(pos->font, pos->index, 0, 0, 0, 0, 0, 0, &glyph, NULL)) {
+            return SDL_SetError("Couldn't find glyph %u in font", pos->index);
+        }
+
+        // Compute positions
+        pos_x = x + pos->x_offset;
+        pos_y = y + F26Dot6(font->ascent) - pos->y_offset;
+        x += pos->x_advance;
+        y += pos->y_advance;
+#if !TTF_USE_HARFBUZZ
+        if (!font->render_subpixel) {
+            x = ((x + 32) & -64); // ROUND()
+        }
 #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].offset = offset;
-        font->pos_len += 1;
+        pos->x = pos_x;
+        pos->y = pos_y;
 
         // Save the number of clusters we've seen
-        if (offset != last_offset) {
+        if (pos->offset != last_offset) {
             ++font->num_clusters;
-            last_offset = offset;
+            last_offset = pos->offset;
         }
 
         // Compute provisional global bounding box
@@ -3341,7 +3464,7 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
 
         // Measurement mode
         if (measure_width) {
-            int cw = SDL_max(maxx, FT_FLOOR(x + prev_advance)) - minx;
+            int cw = SDL_max(maxx, FT_FLOOR(x)) - minx;
             cw += 2 * font->outline;
             if (!max_width || cw <= max_width) {
                 if (measured_width) {
@@ -3349,15 +3472,18 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
                 }
             } else {
                 if (measured_length) {
-                    *measured_length = (size_t)offset;
+                    *measured_length = (size_t)pos->offset;
                 }
+
+                // Truncate glyph positions
+                positions->len = (i + 1);
                 break;
             }
         }
     }
 
     // Allows to render a string with only one space (bug 4344).
-    maxx = SDL_max(maxx, FT_FLOOR(x + prev_advance));
+    maxx = SDL_max(maxx, FT_FLOOR(x));
 
     /* Initial x start position: often 0, except when a glyph would be written at
      * a negative position. In this case an offset is needed for the whole line. */
@@ -3389,21 +3515,7 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
         *h = (maxy - miny);
         *h += 2 * font->outline;
     }
-
-#if TTF_USE_HARFBUZZ
-    if (hb_buffer) {
-        hb_buffer_destroy(hb_buffer);
-    }
-#endif
     return true;
-
-failure:
-#if TTF_USE_HARFBUZZ
-    if (hb_buffer) {
-        hb_buffer_destroy(hb_buffer);
-    }
-#endif
-    return false;
 }
 
 bool TTF_GetStringSize(TTF_Font *font, const char *text, size_t length, int *w, int *h)
@@ -4123,7 +4235,7 @@ static bool LayoutText(TTF_Text *text)
         }
 
         // Allocate space for the operations on this line
-        additional_ops = (font->pos_len + extra_ops);
+        additional_ops = (font->positions.len + extra_ops);
         new_ops = (TTF_DrawOperation *)SDL_realloc(ops, (max_ops + additional_ops) * sizeof(*new_ops));
         if (!new_ops) {
             goto done;
@@ -5629,8 +5741,8 @@ void TTF_CloseFont(TTF_Font *font)
     if (font->closeio) {
         SDL_CloseIO(font->src);
     }
-    if (font->pos_buf) {
-        SDL_free(font->pos_buf);
+    if (font->positions.pos) {
+        SDL_free(font->positions.pos);
     }
     SDL_free(font);
 }