From dd1add2af47ce40685e9b2bae23540a75f5525dd Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 31 Jan 2025 21:05:21 -0800
Subject: [PATCH] Added TTF_CopyFont()
---
include/SDL3_ttf/SDL_ttf.h | 26 ++++++-
src/SDL_ttf.c | 143 ++++++++++++++++++++++++++++---------
src/SDL_ttf.sym | 1 +
3 files changed, 135 insertions(+), 35 deletions(-)
diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index 551fec22..dd2cf9d5 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -186,10 +186,10 @@ extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_OpenFontIO(SDL_IOStream *src, bool cl
*
* - `TTF_PROP_FONT_CREATE_FILENAME_STRING`: the font file to open, if an
* SDL_IOStream isn't being used. This is required if
- * `TTF_PROP_FONT_CREATE_IOSTREAM_POINTER` isn't set.
+ * `TTF_PROP_FONT_CREATE_IOSTREAM_POINTER` and `TTF_PROP_FONT_CREATE_EXISTING_FONT` aren't set.
* - `TTF_PROP_FONT_CREATE_IOSTREAM_POINTER`: an SDL_IOStream containing the
* font to be opened. This should not be closed until the font is closed.
- * This is required if `TTF_PROP_FONT_CREATE_FILENAME_STRING` isn't set.
+ * This is required if `TTF_PROP_FONT_CREATE_FILENAME_STRING` and `TTF_PROP_FONT_CREATE_EXISTING_FONT` aren't set.
* - `TTF_PROP_FONT_CREATE_IOSTREAM_OFFSET_NUMBER`: the offset in the iostream
* for the beginning of the font, defaults to 0.
* - `TTF_PROP_FONT_CREATE_IOSTREAM_AUTOCLOSE_BOOLEAN`: true if closing the
@@ -206,6 +206,7 @@ extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_OpenFontIO(SDL_IOStream *src, bool cl
* - `TTF_PROP_FONT_CREATE_VERTICAL_DPI_NUMBER`: the vertical DPI to use for
* font rendering, defaults to `TTF_PROP_FONT_CREATE_HORIZONTAL_DPI_NUMBER`
* if set, or 72 otherwise.
+ * - `TTF_PROP_FONT_CREATE_EXISTING_FONT`: an optional TTF_Font that, if set, will be used as the font data source and the initial size and style of the new font.
*
* \param props the properties to use.
* \returns a valid TTF_Font, or NULL on failure; call SDL_GetError() for more
@@ -227,6 +228,27 @@ extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_OpenFontWithProperties(SDL_Properties
#define TTF_PROP_FONT_CREATE_FACE_NUMBER "SDL_ttf.font.create.face"
#define TTF_PROP_FONT_CREATE_HORIZONTAL_DPI_NUMBER "SDL_ttf.font.create.hdpi"
#define TTF_PROP_FONT_CREATE_VERTICAL_DPI_NUMBER "SDL_ttf.font.create.vdpi"
+#define TTF_PROP_FONT_CREATE_EXISTING_FONT "SDL_ttf.font.create.existing_font"
+
+/**
+ * Create a copy of an existing font.
+ *
+ * The copy will be distinct from the original, but will share the font file and have the same size and style as the original.
+ *
+ * When done with the returned TTF_Font, use TTF_CloseFont() to dispose of it.
+ *
+ * \param existing_font the font to copy.
+ * \returns a valid TTF_Font, or NULL on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should be called on the thread that created the
+ * original font.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_CloseFont
+ */
+extern SDL_DECLSPEC TTF_Font * SDLCALL TTF_CopyFont(TTF_Font *existing_font);
/**
* Get the properties associated with a font.
diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index b9c9c032..eb3f6b97 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -81,6 +81,8 @@
(unsigned long)' ' )
#endif // !FT_HAS_SVG
+#define TTF_PROP_IOSTREAM_REFCOUNT "SDL_ttf.font.src.refcount"
+
/**
* ZERO WIDTH NO-BREAKSPACE (Unicode byte order mark)
*/
@@ -282,6 +284,7 @@ struct TTF_Font {
// Freetype2 maintains all sorts of useful info itself
FT_Face face;
+ long face_index;
// Properties exposed to the application
SDL_PropertiesID props;
@@ -1963,14 +1966,27 @@ static unsigned long IOread(
return (unsigned long)SDL_ReadIO(font->src, buffer, count);
}
+static void TTF_CloseFontSource(SDL_IOStream *src)
+{
+ SDL_PropertiesID src_props = SDL_GetIOProperties(src);
+ int refcount = (int)SDL_GetNumberProperty(src_props, TTF_PROP_IOSTREAM_REFCOUNT, 0);
+ if (refcount > 0) {
+ --refcount;
+ SDL_SetNumberProperty(src_props, TTF_PROP_IOSTREAM_REFCOUNT, refcount);
+ return;
+ }
+ SDL_CloseIO(src);
+}
+
TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
{
+ TTF_Font *existing_font = SDL_GetPointerProperty(props, TTF_PROP_FONT_CREATE_EXISTING_FONT, NULL);
const char *file = SDL_GetStringProperty(props, TTF_PROP_FONT_CREATE_FILENAME_STRING, NULL);
SDL_IOStream *src = SDL_GetPointerProperty(props, TTF_PROP_FONT_CREATE_IOSTREAM_POINTER, NULL);
Sint64 src_offset = SDL_GetNumberProperty(props, TTF_PROP_FONT_CREATE_IOSTREAM_OFFSET_NUMBER, 0);
bool closeio = SDL_GetBooleanProperty(props, TTF_PROP_FONT_CREATE_IOSTREAM_AUTOCLOSE_BOOLEAN, false);
float ptsize = SDL_GetFloatProperty(props, TTF_PROP_FONT_CREATE_SIZE_FLOAT, 0);
- long index = (long)SDL_GetNumberProperty(props, TTF_PROP_FONT_CREATE_FACE_NUMBER, 0);
+ long face_index = (long)SDL_GetNumberProperty(props, TTF_PROP_FONT_CREATE_FACE_NUMBER, -1);
unsigned int hdpi = (unsigned int)SDL_GetNumberProperty(props, TTF_PROP_FONT_CREATE_HORIZONTAL_DPI_NUMBER, 0);
unsigned int vdpi = (unsigned int)SDL_GetNumberProperty(props, TTF_PROP_FONT_CREATE_VERTICAL_DPI_NUMBER, 0);
TTF_Font *font;
@@ -1990,17 +2006,32 @@ TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
return NULL;
}
- if (!src) {
- if (!file) {
- SDL_SetError("You must set either TTF_PROP_FONT_CREATE_FILENAME_STRING or TTF_PROP_FONT_CREATE_IOSTREAM_POINTER");
- return NULL;
+ if (existing_font) {
+ if (!src) {
+ src = existing_font->src;
+ src_offset = existing_font->src_offset;
+ closeio = existing_font->closeio;
+
+ if (closeio) {
+ SDL_PropertiesID src_props = SDL_GetIOProperties(src);
+ int refcount = (int)SDL_GetNumberProperty(src_props, TTF_PROP_IOSTREAM_REFCOUNT, 0);
+ ++refcount;
+ SDL_SetNumberProperty(src_props, TTF_PROP_IOSTREAM_REFCOUNT, refcount);
+ }
}
-
- src = SDL_IOFromFile(file, "rb");
+ } else {
if (!src) {
- return NULL;
+ if (!file) {
+ SDL_SetError("You must set either TTF_PROP_FONT_CREATE_FILENAME_STRING or TTF_PROP_FONT_CREATE_IOSTREAM_POINTER");
+ return NULL;
+ }
+
+ src = SDL_IOFromFile(file, "rb");
+ if (!src) {
+ return NULL;
+ }
+ closeio = true;
}
- closeio = true;
}
// Check to make sure we can seek in this stream
@@ -2008,7 +2039,7 @@ TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
if (position < 0) {
SDL_SetError("Can't seek in stream");
if (closeio) {
- SDL_CloseIO(src);
+ TTF_CloseFontSource(src);
}
return NULL;
}
@@ -2017,31 +2048,57 @@ TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
if (font == NULL) {
SDL_SetError("Out of memory");
if (closeio) {
- SDL_CloseIO(src);
+ TTF_CloseFontSource(src);
}
return NULL;
}
- if (file) {
- const char *name = SDL_strrchr(file, '/');
- if (name) {
- name += 1;
- } else {
- name = SDL_strrchr(file, '\\');
+ font->src = src;
+ font->src_offset = src_offset;
+ font->closeio = closeio;
+ font->generation = 1;
+
+ if (existing_font) {
+ if (existing_font->name) {
+ font->name = SDL_strdup(existing_font->name);
+ }
+ if (face_index == -1) {
+ face_index = existing_font->face_index;
+ }
+ if (ptsize == 0.0f) {
+ ptsize = existing_font->ptsize;
+ }
+ if (hdpi == 0) {
+ hdpi = existing_font->hdpi;
+ }
+ if (vdpi == 0) {
+ vdpi = existing_font->vdpi;
+ }
+ } else {
+ if (file) {
+ const char *name = SDL_strrchr(file, '/');
if (name) {
name += 1;
} else {
- name = file;
+ name = SDL_strrchr(file, '\\');
+ if (name) {
+ name += 1;
+ } else {
+ name = file;
+ }
}
+ font->name = SDL_strdup(name);
}
- font->name = SDL_strdup(name);
}
- font->src = src;
- font->src_offset = src_offset;
- font->closeio = closeio;
- font->generation = 1;
- font->hdpi = TTF_DEFAULT_DPI;
- font->vdpi = TTF_DEFAULT_DPI;
+ if (face_index < 0) {
+ face_index = 0;
+ }
+ if (hdpi == 0) {
+ hdpi = TTF_DEFAULT_DPI;
+ }
+ if (vdpi == 0) {
+ vdpi = TTF_DEFAULT_DPI;
+ }
font->text = SDL_CreateHashTable(NULL, 16, SDL_HashPointer, SDL_KeyMatchPointer, NULL, false, false);
if (!font->text) {
@@ -2078,14 +2135,15 @@ TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
font->args.stream = stream;
SDL_LockMutex(TTF_state.lock);
- error = FT_Open_Face(TTF_state.library, &font->args, index, &font->face);
+ error = FT_Open_Face(TTF_state.library, &font->args, face_index, &face);
SDL_UnlockMutex(TTF_state.lock);
- if (error || font->face == NULL) {
+ if (error || face == NULL) {
TTF_SetFTError("Couldn't load font file", error);
TTF_CloseFont(font);
return NULL;
}
- face = font->face;
+ font->face = face;
+ font->face_index = face_index;
// Set charmap for loaded font
found = 0;
@@ -2122,10 +2180,17 @@ TTF_Font *TTF_OpenFontWithProperties(SDL_PropertiesID props)
}
// Set the default font style
- font->style = TTF_STYLE_NORMAL;
- font->outline = 0;
- font->ft_load_target = FT_LOAD_TARGET_NORMAL;
- TTF_SetFontKerning(font, true);
+ if (existing_font) {
+ font->style = existing_font->style;
+ font->outline = existing_font->outline;
+ font->ft_load_target = existing_font->ft_load_target;
+ font->enable_kerning = existing_font->enable_kerning;
+ } else {
+ font->style = TTF_STYLE_NORMAL;
+ font->outline = 0;
+ font->ft_load_target = FT_LOAD_TARGET_NORMAL;
+ TTF_SetFontKerning(font, true);
+ }
#if TTF_USE_HARFBUZZ
font->hb_font = hb_ft_font_create(face, NULL);
@@ -2179,6 +2244,18 @@ TTF_Font *TTF_OpenFontIO(SDL_IOStream *src, bool closeio, float ptsize)
return font;
}
+TTF_Font *TTF_CopyFont(TTF_Font *existing_font)
+{
+ TTF_Font *font = NULL;
+ SDL_PropertiesID props = SDL_CreateProperties();
+ if (props) {
+ SDL_SetPointerProperty(props, TTF_PROP_FONT_CREATE_EXISTING_FONT, existing_font);
+ font = TTF_OpenFontWithProperties(props);
+ SDL_DestroyProperties(props);
+ }
+ return font;
+}
+
static bool AddFontTextReference(TTF_Font *font, TTF_Text *text)
{
return SDL_InsertIntoHashTable(font->text, text, NULL);
@@ -6056,7 +6133,7 @@ void TTF_CloseFont(TTF_Font *font)
SDL_free(font->args.stream);
}
if (font->closeio) {
- SDL_CloseIO(font->src);
+ TTF_CloseFontSource(font->src);
}
SDL_free(font->name);
SDL_free(font);
diff --git a/src/SDL_ttf.sym b/src/SDL_ttf.sym
index ae56f1c1..0720cd19 100644
--- a/src/SDL_ttf.sym
+++ b/src/SDL_ttf.sym
@@ -4,6 +4,7 @@ SDL3_ttf_0.0.0 {
TTF_AppendTextString;
TTF_ClearFallbackFonts;
TTF_CloseFont;
+ TTF_CopyFont;
TTF_CreateGPUTextEngine;
TTF_CreateGPUTextEngineWithProperties;
TTF_CreateRendererTextEngine;