From d1b61f38fc0e521491883f7204294546afb12758 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 27 Sep 2024 00:00:00 -0700
Subject: [PATCH] Added TTF_GetTextSizeWrapped()
Also renamed TTF_SizeText() to TTF_GetTextSize()
Fixes https://github.com/libsdl-org/SDL_ttf/issues/292
---
build-scripts/SDL_migration.cocci | 5 +
docs/README-migration.md | 1 +
examples/testapp.c | 2 +-
include/SDL3_ttf/SDL_ttf.h | 30 ++++-
src/SDL_ttf.c | 193 +++++++++++++++++++++++++++++-
src/SDL_ttf.sym | 3 +-
6 files changed, 226 insertions(+), 8 deletions(-)
diff --git a/build-scripts/SDL_migration.cocci b/build-scripts/SDL_migration.cocci
index 9a7c931d..a4135c75 100644
--- a/build-scripts/SDL_migration.cocci
+++ b/build-scripts/SDL_migration.cocci
@@ -160,3 +160,8 @@
- TTF_SetFontScriptName
+ TTF_SetFontScript
(...)
+@@
+@@
+- TTF_SizeText
++ TTF_GetTextSize
+ (...)
diff --git a/docs/README-migration.md b/docs/README-migration.md
index bebaa9b7..59edec5c 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -65,6 +65,7 @@ The following functions have been renamed:
* TTF_RenderUTF8_Solid_Wrapped() => TTF_RenderText_Solid_Wrapped()
* TTF_SetFontScriptName() => TTF_SetFontScript()
* TTF_SetFontWrappedAlign() => TTF_SetFontWrapAlignment()
+* TTF_SizeText() => TTF_GetTextSize()
* TTF_SizeUTF8() => TTF_SizeText()
The following functions have been removed:
diff --git a/examples/testapp.c b/examples/testapp.c
index 4c92260a..b2e85995 100644
--- a/examples/testapp.c
+++ b/examples/testapp.c
@@ -898,7 +898,7 @@ int main(void)
}
}
#endif
- if (!TTF_SizeText(font, text, 0, &w, &h)) {
+ if (!TTF_GetTextSize(font, text, 0, &w, &h)) {
SDL_Log("size failed");
}
if (w == 0) {
diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index 938e0b02..a6b11868 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -696,12 +696,38 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GetGlyphMetrics(TTF_Font *font, Uint32 ch,
* This will report the width and height, in pixels, of the space that the
* specified string will take to fully render.
*
- * This does not need to render the string to do this calculation.
+ * \param font the font to query.
+ * \param text text to calculate, in UTF-8 encoding.
+ * \param length the length of the text, in bytes, or 0 for null terminated
+ * text.
+ * \param w will be filled with width, in pixels, on return.
+ * \param h will be filled with height, in pixels, on return.
+ * \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 the
+ * font.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ */
+extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSize(TTF_Font *font, const char *text, size_t length, int *w, int *h);
+
+/**
+ * Calculate the dimensions of a rendered string of UTF-8 text.
+ *
+ * This will report the width and height, in pixels, of the space that the
+ * specified string will take to fully render.
+ *
+ * Text is wrapped to multiple lines on line endings and on word boundaries if
+ * it extends beyond `wrapLength` in pixels.
+ *
+ * If wrapLength is 0, this function will only wrap on newline characters.
*
* \param font the font to query.
* \param text text to calculate, in UTF-8 encoding.
* \param length the length of the text, in bytes, or 0 for null terminated
* text.
+ * \param wrapLength the maximum width or 0 to wrap on newline characters.
* \param w will be filled with width, in pixels, on return.
* \param h will be filled with height, in pixels, on return.
* \returns true on success or false on failure; call SDL_GetError() for more
@@ -712,7 +738,7 @@ extern SDL_DECLSPEC bool SDLCALL TTF_GetGlyphMetrics(TTF_Font *font, Uint32 ch,
*
* \since This function is available since SDL_ttf 3.0.0.
*/
-extern SDL_DECLSPEC bool SDLCALL TTF_SizeText(TTF_Font *font, const char *text, size_t length, int *w, int *h);
+extern SDL_DECLSPEC bool SDLCALL TTF_GetTextSizeWrapped(TTF_Font *font, const char *text, size_t length, int wrapLength, int *w, int *h);
/**
* Calculate how much of a UTF-8 string will fit in a given width.
diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index 2fa48811..7339dd2e 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -3238,7 +3238,7 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
return false;
}
-bool TTF_SizeText(TTF_Font *font, const char *text, size_t length, int *w, int *h)
+bool TTF_GetTextSize(TTF_Font *font, const char *text, size_t length, int *w, int *h)
{
return TTF_Size_Internal(font, text, length, w, h, NULL, NULL, NO_MEASUREMENT);
}
@@ -3393,6 +3393,192 @@ 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)
+{
+ int width, height;
+ Uint8 *utf8_alloc = NULL;
+
+ int i, numLines, rowHeight, lineskip;
+ char **strLines = NULL, *text_cpy;
+ bool result = false;
+
+ TTF_CHECK_INITIALIZED(false);
+ TTF_CHECK_POINTER("font", font, false);
+ TTF_CHECK_POINTER("text", text, false);
+
+ if (!length) {
+ length = SDL_strlen(text);
+ }
+
+ /* Use a copy of the text */
+ utf8_alloc = SDL_stack_alloc(Uint8, length + 1);
+ if (utf8_alloc == NULL) {
+ SDL_OutOfMemory();
+ goto done;
+ }
+ SDL_memcpy(utf8_alloc, text, length);
+ utf8_alloc[length] = 0;
+ text_cpy = (char *)utf8_alloc;
+
+ /* Get the dimensions of the text surface */
+ if (!TTF_GetTextSize(font, text_cpy, length, &width, &height) || !width) {
+ SDL_SetError("Text has zero width");
+ goto done;
+ }
+
+ if (wrapLength < 0) {
+ SDL_InvalidParamError("wrapLength");
+ goto done;
+ }
+
+ numLines = 1;
+
+ if (*text_cpy) {
+ int maxNumLines = 0;
+ size_t textlen = length;
+ numLines = 0;
+
+ do {
+ int extent = 0, max_count = 0, char_count = 0;
+ size_t save_textlen = (size_t)(-1);
+ char *save_text = NULL;
+
+ if (numLines >= maxNumLines) {
+ char **saved = strLines;
+ if (wrapLength == 0) {
+ maxNumLines += 32;
+ } else {
+ maxNumLines += (width / wrapLength) + 1;
+ }
+ strLines = (char **)SDL_realloc(strLines, maxNumLines * sizeof (*strLines));
+ if (strLines == NULL) {
+ strLines = saved;
+ goto done;
+ }
+ }
+
+ strLines[numLines++] = text_cpy;
+
+ if (!TTF_MeasureText(font, text_cpy, 0, wrapLength, &extent, &max_count)) {
+ SDL_SetError("Error measure text");
+ goto done;
+ }
+
+ if (wrapLength != 0) {
+ if (max_count == 0) {
+ max_count = 1;
+ }
+ }
+
+ while (textlen > 0) {
+ int is_delim;
+ Uint32 c = SDL_StepUTF8((const char **)&text_cpy, &textlen);
+
+ 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_textlen = textlen;
+ save_text = text_cpy;
+ /* Break, if new line */
+ if (c == '\n' || c == '\r') {
+ *(text_cpy - 1) = '\0';
+ 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 && textlen) {
+ text_cpy = save_text;
+ textlen = save_textlen;
+ }
+ } while (textlen > 0);
+ }
+
+ 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++) {
+ char save_c = 0;
+ int w, h;
+
+ /* Add end-of-line */
+ if (strLines) {
+ text = strLines[i];
+ if (i + 1 < numLines) {
+ save_c = strLines[i + 1][0];
+ strLines[i + 1][0] = '\0';
+ }
+ }
+
+ if (TTF_GetTextSize(font, text, 0, &w, &h)) {
+ width = SDL_max(w, width);
+ }
+
+ /* Remove end-of-line */
+ if (strLines) {
+ if (i + 1 < numLines) {
+ strLines[i + 1][0] = save_c;
+ }
+ }
+ }
+ /* 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);
+
+ result = true;
+
+done:
+ if (result) {
+ if (w) {
+ *w = width;
+ }
+ if (h) {
+ *h = height;
+ }
+ } else {
+ if (w) {
+ *w = 0;
+ }
+ if (h) {
+ *h = 0;
+ }
+ }
+ if (strLines) {
+ SDL_free(strLines);
+ }
+ if (utf8_alloc) {
+ SDL_stack_free(utf8_alloc);
+ }
+ return result;
+}
+
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;
@@ -3427,12 +3613,11 @@ static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text
text_cpy = (char *)utf8_alloc;
/* Get the dimensions of the text surface */
- if (!TTF_SizeText(font, text_cpy, length, &width, &height) || !width) {
+ if (!TTF_GetTextSize(font, text_cpy, length, &width, &height) || !width) {
SDL_SetError("Text has zero width");
goto failure;
}
- /* wrapLength is unsigned, but don't allow negative values */
if (wrapLength < 0) {
SDL_InvalidParamError("wrapLength");
goto failure;
@@ -3545,7 +3730,7 @@ static SDL_Surface* TTF_Render_Wrapped_Internal(TTF_Font *font, const char *text
}
}
- if (TTF_SizeText(font, text, 0, &w, &h)) {
+ if (TTF_GetTextSize(font, text, 0, &w, &h)) {
width = SDL_max(w, width);
}
diff --git a/src/SDL_ttf.sym b/src/SDL_ttf.sym
index b8bfb910..bb853c13 100644
--- a/src/SDL_ttf.sym
+++ b/src/SDL_ttf.sym
@@ -22,6 +22,8 @@ SDL3_ttf_0.0.0 {
TTF_GetGlyphMetrics;
TTF_GetGlyphScript;
TTF_GetHarfBuzzVersion;
+ TTF_GetTextSize;
+ TTF_GetTextSizeWrapped;
TTF_Init;
TTF_MeasureText;
TTF_OpenFont;
@@ -52,7 +54,6 @@ SDL3_ttf_0.0.0 {
TTF_SetFontSizeDPI;
TTF_SetFontStyle;
TTF_SetFontWrapAlignment;
- TTF_SizeText;
TTF_Version;
TTF_WasInit;
local: *;