SDL_ttf: Refactored wrapping code into GetWrappedLines()

From 5ad40eb4e2d0761b495fb79000302f2b32589e62 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 27 Sep 2024 08:42:28 -0700
Subject: [PATCH] Refactored wrapping code into GetWrappedLines()

---
 src/SDL_ttf.c | 191 ++++++++++----------------------------------------
 1 file changed, 36 insertions(+), 155 deletions(-)

diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index 8d0762fd..5ca54d67 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -3209,11 +3209,10 @@ static SDL_Surface* TTF_Render_Internal(TTF_Font *font, const char *text, size_t
         goto failure;
     }
 
+    /* Create surface for rendering */
     if (fg.a == SDL_ALPHA_TRANSPARENT) {
         fg.a = SDL_ALPHA_OPAQUE;
     }
-
-    /* Create surface for rendering */
     if (render_mode == RENDER_SHADED) {
         textbuf = Create_Surface_Shaded(width, height, fg, bg, &color);
     } else if (render_mode == RENDER_BLENDED) {
@@ -3307,13 +3306,20 @@ static bool CharacterIsNewLine(Uint32 c)
     return false;
 }
 
-bool TTF_GetTextSizeWrapped(TTF_Font *font, const char *text, size_t length, int wrapLength, int *w, int *h)
+static bool GetWrappedLines(TTF_Font *font, const char *text, size_t length, int wrapLength, TTF_Line **lines, int *num_lines, int *w, int *h)
 {
     int width, height;
-    int i, numLines = 0, rowHeight, lineskip;
+    int i, numLines = 0, rowHeight;
     TTF_Line *strLines = NULL;
     bool result = false;
 
+    if (w) {
+        *w = 0;
+    }
+    if (h) {
+        *h = 0;
+    }
+
     TTF_CHECK_INITIALIZED(false);
     TTF_CHECK_POINTER("font", font, false);
     TTF_CHECK_POINTER("text", text, false);
@@ -3341,17 +3347,17 @@ bool TTF_GetTextSizeWrapped(TTF_Font *font, const char *text, size_t length, int
             size_t save_length = (size_t)(-1);
 
             if (numLines >= maxNumLines) {
-                TTF_Line *lines;
+                TTF_Line *new_lines;
                 if (wrapLength == 0) {
                     maxNumLines += 32;
                 } else {
                     maxNumLines += (width / wrapLength) + 1;
                 }
-                lines = (TTF_Line *)SDL_realloc(strLines, maxNumLines * sizeof (*strLines));
-                if (lines == NULL) {
+                new_lines = (TTF_Line *)SDL_realloc(strLines, maxNumLines * sizeof (*strLines));
+                if (new_lines == NULL) {
                     goto done;
                 }
-                strLines = lines;
+                strLines = new_lines;
             }
 
             if (numLines > 0) {
@@ -3417,8 +3423,7 @@ bool TTF_GetTextSizeWrapped(TTF_Font *font, const char *text, size_t length, int
         }
     }
 
-    lineskip = TTF_GetFontLineSkip(font);
-    rowHeight = SDL_max(height, lineskip);
+    rowHeight = SDL_max(height, font->lineskip);
 
     if (wrapLength == 0) {
         /* Find the max of all line lengths */
@@ -3442,12 +3447,20 @@ bool TTF_GetTextSizeWrapped(TTF_Font *font, const char *text, size_t length, int
             width = wrapLength;
         }
     }
-    height = rowHeight + lineskip * (numLines - 1);
+    height = rowHeight + font->lineskip * (numLines - 1);
 
     result = true;
 
 done:
     if (result) {
+        if (lines) {
+            *lines = strLines;
+        } else {
+            SDL_free(strLines);
+        }
+        if (num_lines) {
+            *num_lines = numLines;
+        }
         if (w) {
             *w = width;
         }
@@ -3455,51 +3468,33 @@ bool TTF_GetTextSizeWrapped(TTF_Font *font, const char *text, size_t length, int
             *h = height;
         }
     } else {
-        if (w) {
-            *w = 0;
-        }
-        if (h) {
-            *h = 0;
-        }
-    }
-    if (strLines) {
         SDL_free(strLines);
     }
     return result;
 }
 
+bool TTF_GetTextSizeWrapped(TTF_Font *font, const char *text, size_t length, int wrapLength, int *w, int *h)
+{
+    return GetWrappedLines(font, text, length, wrapLength, NULL, NULL, w, h);
+}
+
 static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text, size_t length, SDL_Color fg, SDL_Color bg, int wrapLength, const render_mode_t render_mode)
 {
     Uint32 color;
     int width, height;
     SDL_Surface *textbuf = NULL;
-
-    int i, numLines = 0, rowHeight, lineskip;
+    int i, numLines = 0;
     TTF_Line *strLines = NULL;
 
-    TTF_CHECK_INITIALIZED(NULL);
-    TTF_CHECK_POINTER("font", font, NULL);
-    TTF_CHECK_POINTER("text", text, NULL);
-    if (wrapLength < 0) {
-        SDL_InvalidParamError("wrapLength");
+    if (!GetWrappedLines(font, text, length, wrapLength, &strLines, &numLines, &width, &height)) {
         return NULL;
     }
 
-    if (!length) {
-        length = SDL_strlen(text);
-    }
-
     if (render_mode == RENDER_LCD && !FT_IS_SCALABLE(font->face)) {
         SDL_SetError("LCD rendering is not available for non-scalable font");
         goto failure;
     }
 
-    /* Get the dimensions of the text surface */
-    if (!TTF_GetTextSize(font, text, length, &width, &height) || !width) {
-        SDL_SetError("Text has zero width");
-        goto failure;
-    }
-
 #if TTF_USE_SDF
     /* Invalid cache if we were using SDF */
     if (render_mode != RENDER_BLENDED) {
@@ -3510,125 +3505,10 @@ static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text
     }
 #endif
 
-    if (*text) {
-        int maxNumLines = 0;
-        const char *spot = text;
-        size_t left = length;
-
-        do {
-            int extent = 0, max_count = 0, char_count = 0;
-            const char *save_text = NULL;
-            size_t save_length = (size_t)(-1);
-
-            if (numLines >= maxNumLines) {
-                TTF_Line *lines;
-                if (wrapLength == 0) {
-                    maxNumLines += 32;
-                } else {
-                    maxNumLines += (width / wrapLength) + 1;
-                }
-                lines = (TTF_Line *)SDL_realloc(strLines, maxNumLines * sizeof (*strLines));
-                if (lines == NULL) {
-                    goto failure;
-                }
-                strLines = lines;
-            }
-
-            if (numLines > 0) {
-                strLines[numLines - 1].length = spot - strLines[numLines - 1].text;
-            }
-            strLines[numLines].text = spot;
-            strLines[numLines].length = left;
-            ++numLines;
-
-            if (!TTF_MeasureText(font, spot, left, wrapLength, &extent, &max_count)) {
-                SDL_SetError("Error measure text");
-                goto failure;
-            }
-
-            if (wrapLength != 0) {
-                if (max_count == 0) {
-                    max_count = 1;
-                }
-            }
-
-            while (left > 0) {
-                int is_delim;
-                Uint32 c = SDL_StepUTF8((const char **)&spot, &left);
-
-                if (c == UNICODE_BOM_NATIVE || c == UNICODE_BOM_SWAPPED) {
-                    continue;
-                }
-
-                char_count += 1;
-
-                /* With wrapLength == 0, normal text rendering but newline aware */
-                is_delim = (wrapLength > 0) ?  CharacterIsDelimiter(c) : CharacterIsNewLine(c);
-
-                /* Record last delimiter position */
-                if (is_delim) {
-                    save_text = spot;
-                    save_length = left;
-                    /* Break, if new line */
-                    if (c == '\n' || c == '\r') {
-                        break;
-                    }
-                }
-
-                /* Break, if reach the limit */
-                if (char_count == max_count) {
-                    break;
-                }
-            }
-
-            /* Cut at last delimiter/new lines, otherwise in the middle of the word */
-            if (save_text && left > 0) {
-                spot = save_text;
-                left = save_length;
-            }
-        } while (left > 0);
-
-        /* Trim whitespace from the wrapped lines */
-        for (i = 0; i < (numLines - 1); ++i) {
-            TTF_Line *line = &strLines[i];
-            while (line->length > 0 && CharacterIsDelimiter(line->text[line->length - 1])) {
-                --line->length;
-            }
-        }
-    }
-
-    lineskip = TTF_GetFontLineSkip(font);
-    rowHeight = SDL_max(height, lineskip);
-
-    if (wrapLength == 0) {
-        /* Find the max of all line lengths */
-        if (numLines > 1) {
-            width = 0;
-            for (i = 0; i < numLines; i++) {
-                int w, h;
-
-                if (TTF_GetTextSize(font, strLines[i].text, strLines[i].length, &w, &h)) {
-                    width = SDL_max(w, width);
-                }
-            }
-            /* In case there are all newlines */
-            width = SDL_max(width, 1);
-        }
-    } else {
-        if (numLines <= 1 && font->horizontal_align == TTF_HORIZONTAL_ALIGN_LEFT) {
-            /* Don't go above wrapLength if you have only 1 line which hasn't been cut */
-            width = SDL_min((int)wrapLength, width);
-        } else {
-            width = wrapLength;
-        }
-    }
-    height = rowHeight + lineskip * (numLines - 1);
-
+    /* Create surface for rendering */
     if (fg.a == SDL_ALPHA_TRANSPARENT) {
         fg.a = SDL_ALPHA_OPAQUE;
     }
-
-    /* Create surface for rendering */
     if (render_mode == RENDER_SHADED) {
         textbuf = Create_Surface_Shaded(width, height, fg, bg, &color);
     } else if (render_mode == RENDER_BLENDED) {
@@ -3651,13 +3531,13 @@ static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text
         }
 
         /* Move to i-th line */
-        ystart += i * lineskip;
+        ystart += i * font->lineskip;
 
         /* Control left/right/center align of each bit of text */
         if (font->horizontal_align == TTF_HORIZONTAL_ALIGN_RIGHT) {
-            xoffset = width - line_width;
+            xoffset = (width - line_width);
         } else if (font->horizontal_align == TTF_HORIZONTAL_ALIGN_CENTER) {
-            xoffset = width / 2 - line_width / 2;
+            xoffset = (width - line_width) / 2;
         } else {
             xoffset = 0;
         }
@@ -3682,6 +3562,7 @@ static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text
         SDL_free(strLines);
     }
     return textbuf;
+
 failure:
     if (textbuf) {
         SDL_DestroySurface(textbuf);