SDL_ttf: Added TTF_StringToTag() and TTF_TagToString()

From aada7689cc025605ba4b49f6d6b0bb83c720493f Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 6 Feb 2025 11:02:32 -0800
Subject: [PATCH] Added TTF_StringToTag() and TTF_TagToString()

Also clarified that the script used in the API is an ISO 15924 script code.

Fixes https://github.com/libsdl-org/SDL_ttf/issues/505
---
 include/SDL3_ttf/SDL_ttf.h | 53 +++++++++++++++++++++++++++++++++-----
 src/SDL_ttf.c              | 45 ++++++++++++++++++++++++++------
 src/SDL_ttf.sym            |  2 ++
 3 files changed, 85 insertions(+), 15 deletions(-)

diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index f39d0ab2..969cc1be 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -941,15 +941,44 @@ extern SDL_DECLSPEC bool SDLCALL TTF_SetFontDirection(TTF_Font *font, TTF_Direct
  */
 extern SDL_DECLSPEC TTF_Direction SDLCALL TTF_GetFontDirection(TTF_Font *font);
 
+/**
+ * Convert from a 4 character string to a 32-bit tag.
+ *
+ * \param string the 4 character string to convert.
+ * \returns the 32-bit representation of the string.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_TagToString
+ */
+extern SDL_DECLSPEC Uint32 SDLCALL TTF_StringToTag(const char *string);
+
+/**
+ * Convert from a 32-bit tag to a 4 character string.
+ *
+ * \param tag the 32-bit tag to convert.
+ * \param string a pointer filled in with the 4 character representation of the tag.
+ * \param size the size of the buffer pointed at by string, should be at least 4.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_TagToString
+ */
+extern SDL_DECLSPEC void SDLCALL TTF_TagToString(Uint32 tag, char *string, size_t size);
+
 /**
  * Set the script to be used for text shaping by a font.
  *
- * This returns false if SDL_ttf isn't build with HarfBuzz support.
+ * This returns false if SDL_ttf isn't built with HarfBuzz support.
  *
  * This updates any TTF_Text objects using this font.
  *
  * \param font the font to modify.
- * \param script a script tag in the format used by HarfBuzz.
+ * \param script an [ISO 15924 code](https://unicode.org/iso15924/iso15924-codes.html).
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *
@@ -957,6 +986,8 @@ extern SDL_DECLSPEC TTF_Direction SDLCALL TTF_GetFontDirection(TTF_Font *font);
  *               font.
  *
  * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_StringToTag
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_SetFontScript(TTF_Font *font, Uint32 script);
 
@@ -964,12 +995,14 @@ extern SDL_DECLSPEC bool SDLCALL TTF_SetFontScript(TTF_Font *font, Uint32 script
  * Get the script used for text shaping a font.
  *
  * \param font the font to query.
- * \returns a script tag in the format used by HarfBuzz.
+ * \returns an [ISO 15924 code](https://unicode.org/iso15924/iso15924-codes.html) or 0 if a script hasn't been set.
  *
  * \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_TagToString
  */
 extern SDL_DECLSPEC Uint32 SDLCALL TTF_GetFontScript(TTF_Font *font);
 
@@ -977,12 +1010,14 @@ extern SDL_DECLSPEC Uint32 SDLCALL TTF_GetFontScript(TTF_Font *font);
  * Get the script used by a 32-bit codepoint.
  *
  * \param ch the character code to check.
- * \returns a script tag in the format used by HarfBuzz on success, or 0 on
+ * \returns an [ISO 15924 code](https://unicode.org/iso15924/iso15924-codes.html) on success, or 0 on
  *          failure; call SDL_GetError() for more information.
  *
  * \threadsafety This function is thread-safe.
  *
  * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_TagToString
  */
 extern SDL_DECLSPEC Uint32 SDLCALL TTF_GetGlyphScript(Uint32 ch);
 
@@ -2110,10 +2145,10 @@ extern SDL_DECLSPEC TTF_Direction SDLCALL TTF_GetTextDirection(TTF_Text *text);
 /**
  * Set the script to be used for text shaping a text object.
  *
- * This returns false if SDL_ttf isn't build with HarfBuzz support.
+ * This returns false if SDL_ttf isn't built with HarfBuzz support.
  *
  * \param text the text to modify.
- * \param script a script tag in the format used by HarfBuzz.
+ * \param script an [ISO 15924 code](https://unicode.org/iso15924/iso15924-codes.html).
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *
@@ -2121,6 +2156,8 @@ extern SDL_DECLSPEC TTF_Direction SDLCALL TTF_GetTextDirection(TTF_Text *text);
  *               text.
  *
  * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_StringToTag
  */
 extern SDL_DECLSPEC bool SDLCALL TTF_SetTextScript(TTF_Text *text, Uint32 script);
 
@@ -2130,12 +2167,14 @@ extern SDL_DECLSPEC bool SDLCALL TTF_SetTextScript(TTF_Text *text, Uint32 script
  * This defaults to the script of the font used by the text object.
  *
  * \param text the text to query.
- * \returns a script tag in the format used by HarfBuzz.
+ * \returns an [ISO 15924 code](https://unicode.org/iso15924/iso15924-codes.html) or 0 if a script hasn't been set on either the text object or the font.
  *
  * \threadsafety This function should be called on the thread that created the
  *               text.
  *
  * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_TagToString
  */
 extern SDL_DECLSPEC Uint32 SDLCALL TTF_GetTextScript(TTF_Text *text);
 
diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index eb3f6b97..ea9edfc1 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -346,7 +346,7 @@ struct TTF_Font {
     hb_font_t *hb_font;
     hb_language_t hb_language;
 #endif
-    Uint32 script;
+    Uint32 script; // ISO 15924 script tag
     TTF_Direction direction;
     bool render_sdf;
 
@@ -3352,7 +3352,7 @@ static bool CollectGlyphsFromFont(TTF_Font *font, const char *text, size_t lengt
     // Set global configuration
     hb_buffer_set_language(hb_buffer, font->hb_language);
     hb_buffer_set_direction(hb_buffer, (hb_direction_t)direction);
-    hb_buffer_set_script(hb_buffer, script);
+    hb_buffer_set_script(hb_buffer, hb_script_from_iso15924_tag(script));
 
     // Layout the text
     hb_buffer_add_utf8(hb_buffer, text, (int)length, 0, -1);
@@ -4369,7 +4369,7 @@ SDL_Surface* TTF_RenderText_LCD_Wrapped(TTF_Font *font, const char *text, size_t
 struct TTF_TextLayout
 {
     TTF_Direction direction;
-    Uint32 script;
+    Uint32 script; // ISO 15924 script tag
     int font_height;
     int wrap_length;
     bool wrap_whitespace_visible;
@@ -4815,7 +4815,7 @@ Uint32 TTF_GetTextScript(TTF_Text *text)
 {
     TTF_CHECK_POINTER("text", text, 0);
 
-    if (text->internal->layout->script != 0) {
+    if (text->internal->layout->script) {
         return text->internal->layout->script;
     }
     return TTF_GetFontScript(text->internal->font);
@@ -6001,6 +6001,38 @@ TTF_Direction TTF_GetFontDirection(TTF_Font *font)
     return font->direction;
 }
 
+Uint32 TTF_StringToTag(const char *string)
+{
+    Uint8 bytes[4] = { 0, 0, 0, 0 };
+
+    if (string) {
+        for (size_t i = 0; i < 4 && string[i]; ++i) {
+            bytes[i] = (Uint8)string[i];
+        }
+    }
+
+    Uint32 tag = ((Uint32)bytes[0] << 24) |
+                 ((Uint32)bytes[1] << 16) |
+                 ((Uint32)bytes[2] <<  8) |
+                 ((Uint32)bytes[3] <<  0);
+    return tag;
+}
+
+void TTF_TagToString(Uint32 tag, char *string, size_t size)
+{
+    if (!string || !size) {
+        return;
+    }
+
+    for (size_t i = 0; i < 4 && i < size; ++i) {
+        string[i] = (char)(Uint8)(tag >> 24);
+        tag <<= 8;
+    }
+    if (size > 4) {
+        string[4] = '\0';
+    }
+}
+
 bool TTF_SetFontScript(TTF_Font *font, Uint32 script)
 {
     TTF_CHECK_FONT(font, false);
@@ -6045,10 +6077,7 @@ Uint32 TTF_GetGlyphScript(Uint32 ch)
     hb_buffer_clear_contents(hb_buffer);
     hb_buffer_set_content_type(hb_buffer, HB_BUFFER_CONTENT_TYPE_UNICODE);
 
-    script = hb_unicode_script(hb_unicode_functions, ch);
-    if (script == HB_SCRIPT_UNKNOWN) {
-        script = 0;
-    }
+    script = hb_script_to_iso15924_tag(hb_unicode_script(hb_unicode_functions, ch));
 
     hb_buffer_destroy(hb_buffer);
 #else
diff --git a/src/SDL_ttf.sym b/src/SDL_ttf.sym
index 0720cd19..51963f38 100644
--- a/src/SDL_ttf.sym
+++ b/src/SDL_ttf.sym
@@ -109,6 +109,8 @@ SDL3_ttf_0.0.0 {
     TTF_SetTextString;
     TTF_SetTextWrapWhitespaceVisible;
     TTF_SetTextWrapWidth;
+    TTF_StringToTag;
+    TTF_TagToString;
     TTF_TextWrapWhitespaceVisible;
     TTF_UpdateText;
     TTF_Version;