From 2e4f4ce1da66e0af80a93830b90c947ba0304167 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 29 Sep 2024 10:45:04 -0700
Subject: [PATCH] Added support for custom font atlas and text drawing
implementations
Included are two implementations for SDL_Surface and SDL_Renderer
---
CMakeLists.txt | 3 +
VisualC/SDL_ttf.vcxproj | 21 +-
VisualC/SDL_ttf.vcxproj.filters | 9 +
examples/showfont.c | 178 ++++++++--
include/SDL3_ttf/SDL_textengine.h | 147 ++++++++
include/SDL3_ttf/SDL_ttf.h | 227 +++++++++++-
src/SDL_hashtable.c | 558 ++++++++++++++++++++++++++++++
src/SDL_hashtable.h | 64 ++++
src/SDL_renderer_textengine.c | 364 +++++++++++++++++++
src/SDL_surface_textengine.c | 369 ++++++++++++++++++++
src/SDL_ttf.c | 410 +++++++++++++++++++++-
src/SDL_ttf.sym | 11 +
12 files changed, 2295 insertions(+), 66 deletions(-)
create mode 100644 include/SDL3_ttf/SDL_textengine.h
create mode 100644 src/SDL_hashtable.c
create mode 100644 src/SDL_hashtable.h
create mode 100644 src/SDL_renderer_textengine.c
create mode 100644 src/SDL_surface_textengine.c
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 27e03f1e..db61f93d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -100,6 +100,9 @@ if (LIBC_IS_GLIBC AND CMAKE_SIZEOF_VOID_P EQUAL 4)
endif()
add_library(${sdl3_ttf_target_name}
+ src/SDL_hashtable.c
+ src/SDL_renderer_textengine.c
+ src/SDL_surface_textengine.c
src/SDL_ttf.c
)
add_library(SDL3_ttf::${sdl3_ttf_target_name} ALIAS ${sdl3_ttf_target_name})
diff --git a/VisualC/SDL_ttf.vcxproj b/VisualC/SDL_ttf.vcxproj
index bca85e73..a5174617 100644
--- a/VisualC/SDL_ttf.vcxproj
+++ b/VisualC/SDL_ttf.vcxproj
@@ -371,24 +371,13 @@
<ClCompile Include="..\external\harfbuzz\src\hb-uniscribe.cc" />
<ClCompile Include="..\external\harfbuzz\src\hb-wasm-api.cc" />
<ClCompile Include="..\external\harfbuzz\src\hb-wasm-shape.cc" />
- <ClCompile Include="..\src\SDL_ttf.c">
- <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
+ <ClCompile Include="..\src\SDL_hashtable.c" />
+ <ClCompile Include="..\src\SDL_renderer_textengine.c" />
+ <ClCompile Include="..\src\SDL_surface_textengine.c" />
+ <ClCompile Include="..\src\SDL_ttf.c" />
</ItemGroup>
<ItemGroup>
- <ResourceCompile Include="..\src\version.rc">
- <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ResourceCompile>
+ <ResourceCompile Include="..\src\version.rc" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\SDL3_ttf\SDL_ttf.h" />
diff --git a/VisualC/SDL_ttf.vcxproj.filters b/VisualC/SDL_ttf.vcxproj.filters
index ea6ea142..ff341e02 100644
--- a/VisualC/SDL_ttf.vcxproj.filters
+++ b/VisualC/SDL_ttf.vcxproj.filters
@@ -21,6 +21,15 @@
</Filter>
</ItemGroup>
<ItemGroup>
+ <ClCompile Include="..\src\SDL_hashtable.c">
+ <Filter>Sources</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\SDL_renderer_textengine.c">
+ <Filter>Sources</Filter>
+ </ClCompile>
+ <ClCompile Include="..\src\SDL_surface_textengine.c">
+ <Filter>Sources</Filter>
+ </ClCompile>
<ClCompile Include="..\src\SDL_ttf.c">
<Filter>Sources</Filter>
</ClCompile>
diff --git a/examples/showfont.c b/examples/showfont.c
index ac9cc194..f468c7ae 100644
--- a/examples/showfont.c
+++ b/examples/showfont.c
@@ -35,7 +35,14 @@
#define HEIGHT 480
#define TTF_SHOWFONT_USAGE \
-"Usage: %s [-shaded] [-blended] [-wrapped] [-b] [-i] [-u] [-s] [-outline size] [-hintlight|-hintmono|-hintnone] [-nokerning] [-wrap] [-align left|center|right] [-fgcol r,g,b,a] [-bgcol r,g,b,a] <font>.ttf [ptsize] [text]\n"
+"Usage: %s [-textengine surface|renderer] [-shaded] [-blended] [-wrapped] [-b] [-i] [-u] [-s] [-outline size] [-hintlight|-hintmono|-hintnone] [-nokerning] [-wrap] [-align left|center|right] [-fgcol r,g,b,a] [-bgcol r,g,b,a] <font>.ttf [ptsize] [text]\n"
+
+typedef enum
+{
+ TextEngineNone,
+ TextEngineSurface,
+ TextEngineRenderer
+} TextEngine;
typedef enum
{
@@ -44,21 +51,58 @@ typedef enum
} TextRenderMethod;
typedef struct {
+ SDL_Window *window;
+ SDL_Surface *window_surface;
+ SDL_Renderer *renderer;
SDL_Texture *caption;
SDL_FRect captionRect;
SDL_Texture *message;
SDL_FRect messageRect;
+ TextEngine textEngine;
+ TTF_Text *text;
} Scene;
-static void draw_scene(SDL_Renderer *renderer, Scene *scene)
+static void draw_scene(Scene *scene)
{
+ SDL_Renderer *renderer = scene->renderer;
+
/* Clear the background to background color */
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
SDL_RenderClear(renderer);
+ /* Flush the renderer so we can draw to the window surface in TextEngineSurface mode */
+ SDL_FlushRenderer(renderer);
+
+ if (scene->text) {
+ int i;
+ int w, h;
+
+ SDL_GetRenderOutputSize(renderer, &w, &h);
+
+ for (i = 0; i < 100; ++i) {
+ int x = SDL_rand(w) - scene->text->w / 2;
+ int y = SDL_rand(h) - scene->text->h / 2;
+
+ switch (scene->textEngine) {
+ case TextEngineSurface:
+ TTF_DrawSurfaceText(scene->text, x, y, scene->window_surface);
+ break;
+ case TextEngineRenderer:
+ TTF_DrawRendererText(scene->text, (float)x, (float)y);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
SDL_RenderTexture(renderer, scene->caption, NULL, &scene->captionRect);
SDL_RenderTexture(renderer, scene->message, NULL, &scene->messageRect);
SDL_RenderPresent(renderer);
+
+ if (scene->window_surface) {
+ SDL_UpdateWindowSurface(scene->window);
+ }
}
static void cleanup(int exitcode)
@@ -71,9 +115,7 @@ static void cleanup(int exitcode)
int main(int argc, char *argv[])
{
char *argv0 = argv[0];
- SDL_Window *window;
- SDL_Renderer *renderer;
- TTF_Font *font;
+ TTF_Font *font = NULL;
SDL_Surface *text = NULL;
Scene scene;
float ptsize;
@@ -83,6 +125,7 @@ int main(int argc, char *argv[])
SDL_Color *forecol;
SDL_Color *backcol;
SDL_Event event;
+ TTF_TextEngine *engine = NULL;
TextRenderMethod rendermethod;
int renderstyle;
int outline;
@@ -96,15 +139,28 @@ int main(int argc, char *argv[])
/* Look for special execution mode */
dump = 0;
/* Look for special rendering types */
+ SDL_zero(scene);
rendermethod = TextRenderShaded;
renderstyle = TTF_STYLE_NORMAL;
outline = 0;
hinting = TTF_HINTING_NORMAL;
kerning = 1;
+ wrap = 0;
/* Default is black and white */
forecol = &black;
backcol = &white;
for (i=1; argv[i] && argv[i][0] == '-'; ++i) {
+ if (SDL_strcmp(argv[i], "-textengine") == 0 && argv[i+1]) {
+ ++i;
+ if (SDL_strcmp(argv[i], "surface") == 0) {
+ scene.textEngine = TextEngineSurface;
+ } else if (SDL_strcmp(argv[i], "renderer") == 0) {
+ scene.textEngine = TextEngineRenderer;
+ } else {
+ SDL_Log(TTF_SHOWFONT_USAGE, argv0);
+ return(1);
+ }
+ } else
if (SDL_strcmp(argv[i], "-shaded") == 0) {
rendermethod = TextRenderShaded;
} else
@@ -123,7 +179,7 @@ int main(int argc, char *argv[])
if (SDL_strcmp(argv[i], "-s") == 0) {
renderstyle |= TTF_STYLE_STRIKETHROUGH;
} else
- if (SDL_strcmp(argv[i], "-outline") == 0) {
+ if (SDL_strcmp(argv[i], "-outline") == 0 && argv[i+1]) {
if (SDL_sscanf(argv[++i], "%d", &outline) != 1) {
SDL_Log(TTF_SHOWFONT_USAGE, argv0);
return(1);
@@ -144,7 +200,7 @@ int main(int argc, char *argv[])
if (SDL_strcmp(argv[i], "-wrap") == 0) {
wrap = 1;
} else
- if (SDL_strcmp(argv[i], "-align") == 0) {
+ if (SDL_strcmp(argv[i], "-align") == 0 && argv[i+1]) {
++i;
if (SDL_strcmp(argv[i], "left") == 0) {
align = TTF_HORIZONTAL_ALIGN_LEFT;
@@ -160,7 +216,7 @@ int main(int argc, char *argv[])
if (SDL_strcmp(argv[i], "-dump") == 0) {
dump = 1;
} else
- if (SDL_strcmp(argv[i], "-fgcol") == 0) {
+ if (SDL_strcmp(argv[i], "-fgcol") == 0 && argv[i+1]) {
int r, g, b, a = SDL_ALPHA_OPAQUE;
if (SDL_sscanf(argv[++i], "%d,%d,%d,%d", &r, &g, &b, &a) < 3) {
SDL_Log(TTF_SHOWFONT_USAGE, argv0);
@@ -171,7 +227,7 @@ int main(int argc, char *argv[])
forecol->b = (Uint8)b;
forecol->a = (Uint8)a;
} else
- if (SDL_strcmp(argv[i], "-bgcol") == 0) {
+ if (SDL_strcmp(argv[i], "-bgcol") == 0 && argv[i+1]) {
int r, g, b, a = SDL_ALPHA_OPAQUE;
if (SDL_sscanf(argv[++i], "%d,%d,%d,%d", &r, &g, &b, &a) < 3) {
SDL_Log(TTF_SHOWFONT_USAGE, argv0);
@@ -242,8 +298,28 @@ int main(int argc, char *argv[])
}
/* Create a window */
- if (!SDL_CreateWindowAndRenderer("showfont demo", WIDTH, HEIGHT, 0, &window, &renderer)) {
- SDL_Log("SDL_CreateWindowAndRenderer() failed: %s\n", SDL_GetError());
+ scene.window = SDL_CreateWindow("showfont demo", WIDTH, HEIGHT, 0);
+ if (!scene.window) {
+ SDL_Log("SDL_CreateWindow() failed: %s\n", SDL_GetError());
+ cleanup(2);
+ }
+ if (scene.textEngine == TextEngineSurface) {
+ scene.window_surface = SDL_GetWindowSurface(scene.window);
+ if (!scene.window_surface) {
+ SDL_Log("SDL_CreateWindowSurface() failed: %s\n", SDL_GetError());
+ cleanup(2);
+ }
+ SDL_SetWindowSurfaceVSync(scene.window, 1);
+
+ scene.renderer = SDL_CreateSoftwareRenderer(scene.window_surface);
+ } else {
+ scene.renderer = SDL_CreateRenderer(scene.window, NULL);
+ if (scene.renderer) {
+ SDL_SetRenderVSync(scene.renderer, 1);
+ }
+ }
+ if (!scene.renderer) {
+ SDL_Log("SDL_CreateRenderer() failed: %s\n", SDL_GetError());
cleanup(2);
}
@@ -262,7 +338,7 @@ int main(int argc, char *argv[])
scene.captionRect.y = 4.0f;
scene.captionRect.w = (float)text->w;
scene.captionRect.h = (float)text->h;
- scene.caption = SDL_CreateTextureFromSurface(renderer, text);
+ scene.caption = SDL_CreateTextureFromSurface(scene.renderer, text);
SDL_DestroySurface(text);
}
@@ -297,39 +373,75 @@ int main(int argc, char *argv[])
scene.messageRect.y = (float)((HEIGHT - text->h)/2);
scene.messageRect.w = (float)text->w;
scene.messageRect.h = (float)text->h;
- scene.message = SDL_CreateTextureFromSurface(renderer, text);
+ scene.message = SDL_CreateTextureFromSurface(scene.renderer, text);
SDL_Log("Font is generally %d big, and string is %d big\n",
TTF_GetFontHeight(font), text->h);
- draw_scene(renderer, &scene);
+ switch (scene.textEngine) {
+ case TextEngineSurface:
+ engine = TTF_CreateSurfaceTextEngine();
+ if (!engine) {
+ SDL_Log("Couldn't create surface text engine: %s\n", SDL_GetError());
+ }
+ break;
+ case TextEngineRenderer:
+ engine = TTF_CreateRendererTextEngine(scene.renderer);
+ if (!engine) {
+ SDL_Log("Couldn't create renderer text engine: %s\n", SDL_GetError());
+ }
+ break;
+ default:
+ break;
+ }
+ if (engine) {
+ if (wrap) {
+ scene.text = TTF_CreateText_Wrapped(engine, font, message, 0, 0);
+ } else {
+ scene.text = TTF_CreateText(engine, font, message, 0);
+ }
+ if (scene.text) {
+ scene.text->color.r = forecol->r / 255.0f;
+ scene.text->color.g = forecol->g / 255.0f;
+ scene.text->color.b = forecol->b / 255.0f;
+ scene.text->color.a = forecol->a / 255.0f;
+ }
+ }
/* Wait for a keystroke, and blit text on mouse press */
done = 0;
while (!done) {
- if (!SDL_WaitEvent(&event)) {
- SDL_Log("SDL_PullEvent() error: %s\n", SDL_GetError());
- done = 1;
- continue;
- }
- switch (event.type) {
- case SDL_EVENT_MOUSE_BUTTON_DOWN:
- scene.messageRect.x = (float)(event.button.x - text->w/2);
- scene.messageRect.y = (float)(event.button.y - text->h/2);
- scene.messageRect.w = (float)text->w;
- scene.messageRect.h = (float)text->h;
- draw_scene(renderer, &scene);
- break;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ scene.messageRect.x = (float)(event.button.x - text->w/2);
+ scene.messageRect.y = (float)(event.button.y - text->h/2);
+ scene.messageRect.w = (float)text->w;
+ scene.messageRect.h = (float)text->h;
+ break;
- case SDL_EVENT_KEY_DOWN:
- case SDL_EVENT_QUIT:
- done = 1;
- break;
- default:
- break;
+ case SDL_EVENT_KEY_DOWN:
+ case SDL_EVENT_QUIT:
+ done = 1;
+ break;
+ default:
+ break;
+ }
}
+ draw_scene(&scene);
}
SDL_DestroySurface(text);
TTF_CloseFont(font);
+ TTF_DestroyText(scene.text);
+ switch (scene.textEngine) {
+ case TextEngineSurface:
+ TTF_DestroySurfaceTextEngine(engine);
+ break;
+ case TextEngineRenderer:
+ TTF_DestroyRendererTextEngine(engine);
+ break;
+ default:
+ break;
+ }
SDL_DestroyTexture(scene.caption);
SDL_DestroyTexture(scene.message);
cleanup(0);
diff --git a/include/SDL3_ttf/SDL_textengine.h b/include/SDL3_ttf/SDL_textengine.h
new file mode 100644
index 00000000..b3e32ba4
--- /dev/null
+++ b/include/SDL3_ttf/SDL_textengine.h
@@ -0,0 +1,147 @@
+/*
+ SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts
+ Copyright (C) 2001-2024 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+
+/**
+ * \file SDL_textengine.h
+ *
+ * Definitions for implementations of the TTF_TextEngine interface.
+ */
+#ifndef SDL_TTF_TEXTENGINE_H_
+#define SDL_TTF_TEXTENGINE_H_
+
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_begin_code.h>
+
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Text created with the text engine */
+typedef struct TTF_Text TTF_Text;
+
+/**
+ * A font atlas draw command.
+ *
+ * \since This enum is available since SDL_ttf 3.0.0.
+ */
+typedef enum TTF_DrawCommand
+{
+ TTF_DRAW_COMMAND_NOOP,
+ TTF_DRAW_COMMAND_FILL,
+ TTF_DRAW_COMMAND_COPY
+} TTF_DrawCommand;
+
+/**
+ * A filled rectangle draw operation.
+ *
+ * \since This struct is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_DrawOperation
+ */
+typedef struct TTF_FillOperation
+{
+ TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_FILL */
+ SDL_Rect rect; /**< The rectangle to fill, 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. */
+} TTF_FillOperation;
+
+/**
+ * A texture copy draw operation.
+ *
+ * \since This struct is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_DrawOperation
+ */
+typedef struct TTF_CopyOperation
+{
+ TTF_DrawCommand cmd; /**< TTF_DRAW_COMMAND_COPY */
+ Uint32 glyph_index; /**< The glyph index of the glyph to be drawn, can be passed to TTF_GetGlyphForIndex() */
+ 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;
+} TTF_CopyOperation;
+
+/**
+ * A text engine draw operation.
+ *
+ * \since This struct is available since SDL_ttf 3.0.0.
+ */
+typedef union TTF_DrawOperation
+{
+ TTF_DrawCommand cmd;
+ TTF_FillOperation fill;
+ TTF_CopyOperation copy;
+} TTF_DrawOperation;
+
+/**
+ * A text engine interface.
+ *
+ * This structure should be initialized using SDL_INIT_INTERFACE()
+ *
+ * \since This struct is available since SDL_ttf 3.0.0.
+ *
+ * \sa SDL_INIT_INTERFACE
+ */
+typedef struct TTF_TextEngine
+{
+ Uint32 version; /**< The version of this interface */
+
+ void *userdata; /**< User data pointer passed to callbacks */
+
+ /* Create a text representation from draw instructions.
+ *
+ * All fields of `text` except `internal` will already be filled out.
+ *
+ * \param userdata the userdata pointer in this interface.
+ * \param font the font being used.
+ * \param font_generation the unique ID of the font generation being used. This changes whenever the font changes size or style and needs new glyphs, and is unique across all fonts.
+ * \param ops the text drawing operations
+ * \param num_ops the number of text drawing operations
+ */
+ bool (SDLCALL *CreateText)(void *userdata, TTF_Font *font, Uint32 font_generation, TTF_Text *text, TTF_DrawOperation *ops, int num_ops);
+
+ /**
+ * Destroy a text representation.
+ */
+ void (SDLCALL *DestroyText)(void *userdata, TTF_Text *text);
+
+} TTF_TextEngine;
+
+/* Check the size of TTF_TextEngine
+ *
+ * If this assert fails, either the compiler is padding to an unexpected size,
+ * or the interface has been updated and this should be updated to match and
+ * the code using this interface should be updated to handle the old version.
+ */
+SDL_COMPILE_TIME_ASSERT(TTF_TextEngine_SIZE,
+ (sizeof(void *) == 4 && sizeof(TTF_TextEngine) == 16) ||
+ (sizeof(void *) == 8 && sizeof(TTF_TextEngine) == 32));
+
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include <SDL3/SDL_close_code.h>
+
+#endif /* SDL_TTF_TEXTENGINE_H_ */
+
diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index 14d9fef1..25982f55 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -788,6 +788,38 @@ extern SDL_DECLSPEC bool TTF_SetFontLanguage(TTF_Font *font, const char *languag
*/
extern SDL_DECLSPEC bool SDLCALL TTF_FontHasGlyph(TTF_Font *font, Uint32 ch);
+/**
+ * Get the pixel image for a UNICODE codepoint.
+ *
+ * \param font the font to query.
+ * \param ch the codepoint to check.
+ * \returns an SDL_Surface containing the glyph, or NULL 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 SDL_Surface * SDLCALL TTF_GetGlyphImage(TTF_Font *font, Uint32 ch);
+
+/**
+ * Get the pixel image for a character index.
+ *
+ * This is useful for text engine implementations, which can call this with the `glyph_index` in a TTF_CopyOperation
+ *
+ * \param font the font to query.
+ * \param glyph_index the index of the glyph to return.
+ * \returns an SDL_Surface containing the glyph, or NULL 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 SDL_Surface * SDLCALL TTF_GetGlyphImageForIndex(TTF_Font *font, Uint32 glyph_index);
+
/**
* Query the metrics (dimensions) of a font's glyph for a UNICODE codepoint.
*
@@ -1190,6 +1222,200 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderText_LCD_Wrapped(TTF_Font *f
extern SDL_DECLSPEC SDL_Surface * SDLCALL TTF_RenderGlyph_LCD(TTF_Font *font, Uint32 ch, SDL_Color fg, SDL_Color bg);
+/* A text engine used to create text objects
+ *
+ * This is a public interface that can be used by applications and libraries. See <SDL3_ttf/SDL_textengine.h> for details.
+ */
+typedef struct TTF_TextEngine TTF_TextEngine;
+
+/**
+ * Text created with TTF_CreateText()
+ *
+ * \since This struct is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_CreateText
+ * \sa TTF_CreateText_Wrapped
+ * \sa TTF_DestroyText
+ */
+typedef struct TTF_Text
+{
+ char *label; /**< A label that you can allocate with SDL_strdup() for debugging purposes, and will be automatically freed in TTF_DestroyText(). */
+ int w; /**< The width of this text, in pixels, read-only. */
+ int h; /**< The height of this text, in pixels, read-only. */
+ SDL_FColor color; /**< The color of the text, read-write. You can change this anytime. */
+ TTF_TextEngine *engine; /**< The engine used to create this text, read-only. */
+ void *internal; /**< The internal representation of this text, read-only */
+} TTF_Text;
+
+/**
+ * Create a text engine for drawing text on SDL surfaces.
+ *
+ * \returns a TTF_TextEngine object or NULL on failure; call SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_DestroySurfaceTextEngine
+ * \sa TTF_DrawSurfaceText
+ */
+extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateSurfaceTextEngine(void);
+
+/**
+ * Draw text to an SDL surface.
+ *
+ * `text` must have been created using a TTF_TextEngine from TTF_CreateSurfaceTextEngine().
+ *
+ * \param text the text to draw.
+ * \param x the x coordinate in pixels, positive from the left edge towards the right.
+ * \param y the y coordinate in pixels, positive from the top edge towards the bottom.
+ * \param surface the surface to draw on.
+ * \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 text.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_CreateSurfaceTextEngine
+ * \sa TTF_CreateText
+ * \sa TTF_CreateText_Wrapped
+ */
+extern SDL_DECLSPEC bool SDLCALL TTF_DrawSurfaceText(TTF_Text *text, int x, int y, SDL_Surface *surface);
+
+/**
+ * Destroy a text engine created for drawing text on SDL surfaces.
+ *
+ * All text created by this engine should be destroyed before calling this function.
+ *
+ * \param engine a TTF_TextEngine object created with TTF_CreateSurfaceTextEngine().
+ *
+ * \threadsafety This function should be called on the thread that created the engine.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_CreateSurfaceTextEngine
+ */
+extern SDL_DECLSPEC void SDLCALL TTF_DestroySurfaceTextEngine(TTF_TextEngine *engine);
+
+/**
+ * Create a text engine for drawing text on an SDL renderer.
+ *
+ * \param renderer the renderer to use for creating textures and drawing text.
+ * \returns a TTF_TextEngine object or NULL on failure; call SDL_GetError() for more information.
+ *
+ * \threadsafety This function should be called on the thread that created the renderer.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_DestroyRendererTextEngine
+ */
+extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateRendererTextEngine(SDL_Renderer *renderer);
+
+/**
+ * Draw text to an SDL renderer.
+ *
+ * `text` must have been created using a TTF_TextEngine from TTF_CreateRendererTextEngine(), and will draw using the renderer passed to that function.
+ *
+ * \param text the text to draw.
+ * \param x the x coordinate in pixels, positive from the left edge towards the right.
+ * \param y the y coordinate in pixels, positive from the top edge towards the bottom.
+ * \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 text.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_CreateRendererTextEngine
+ * \sa TTF_CreateText
+ * \sa TTF_CreateText_Wrapped
+ */
+extern SDL_DECLSPEC bool SDLCALL TTF_DrawRendererText(TTF_Text *text, float x, float y);
+
+/**
+ * Destroy a text engine created for drawing text on an SDL renderer.
+ *
+ * All text created by this engine should be destroyed before calling this function.
+ *
+ * \param engine a TTF_TextEngine object created with TTF_CreateRendererTextEngine().
+ *
+ * \threadsafety This function should be called on the thread that created the engine.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_CreateRendererTextEngine
+ */
+extern SDL_DECLSPEC void SDLCALL TTF_DestroyRendererTextEngine(TTF_TextEngine *engine);
+
+/**
+ * Create a text object from UTF-8 text and a text engine.
+ *
+ * This will not word-wrap the string; you'll get a surface with a single line
+ * of text, as long as the string requires. You can use
+ * TTF_CreateText_Wrapped() instead if you need to wrap the output to
+ * multiple lines.
+ *
+ * This will not wrap on newline characters.
+ *
+ * \param engine the text engine to use when creating the text object.
+ * \param font the font to render with.
+ * \param text the text to use, in UTF-8 encoding.
+ * \param length the length of the text, in bytes, or 0 for null terminated
+ * text.
+ * \returns a TTF_Text object or NULL on failure; call SDL_GetError() for more information.
+ *
+ * \threadsafety This function should be called on the thread that created the
+ * font and text engine.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_CreateText_Wrapped
+ * \sa TTF_DestroyText
+ */
+extern SDL_DECLSPEC TTF_Text * SDLCALL TTF_CreateText(TTF_TextEngine *engine, TTF_Font *font, const char *text, size_t length);
+
+/**
+ * Create a text object from word-wrapped UTF-8 text and a text engine.
+ *
+ * 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 engine the text engine to use when creating the text object.
+ * \param font the font to render with.
+ * \param text the text to use, in UTF-8 encoding.
+ * \param length the length of the text, in bytes, or 0 for null terminated
+ * text.
+ * \param wrapLength the maximum width of the text surface or 0 to wrap on
+ * newline characters.
+ * \returns a TTF_Text object or NULL 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.
+ *
+ * \sa TTF_CreateText
+ * \sa TTF_DestroyText
+ */
+extern SDL_DECLSPEC TTF_Text * SDLCALL TTF_CreateText_Wrapped(TTF_TextEngine *engine, TTF_Font *font, const char *text, size_t length, int wrapLength);
+
+/**
+ * Destroy a text object created by a text engine.
+ *
+ * \param text the text to destroy.
+ *
+ * \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_CreateText
+ * \sa TTF_CreateText_Wrapped
+ */
+extern SDL_DECLSPEC void SDLCALL TTF_DestroyText(TTF_Text *text);
+
/**
* Dispose of a previously-created font.
*
@@ -1275,4 +1501,3 @@ extern SDL_DECLSPEC int SDLCALL TTF_WasInit(void);
#endif /* SDL_TTF_H_ */
-/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/SDL_hashtable.c b/src/SDL_hashtable.c
new file mode 100644
index 00000000..797264a2
--- /dev/null
+++ b/src/SDL_hashtable.c
@@ -0,0 +1,558 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distributi
(Patch may be truncated, please check the link at the top of this post.)