From a9e5f40173eafe5a746a3c46007b64f58c7c0da2 Mon Sep 17 00:00:00 2001 From: Ankith Date: Mon, 3 Feb 2025 14:13:31 +0530 Subject: [PATCH] Port font to SDL3(_ttf) --- .github/workflows/build-sdl3.yml | 11 +++ buildconfig/download_win_prebuilt.py | 16 ++++ dev.py | 1 - meson.build | 2 +- src_c/font.c | 125 +++++++++++++++++++++++---- src_c/font.h | 9 +- src_c/meson.build | 6 +- 7 files changed, 148 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build-sdl3.yml b/.github/workflows/build-sdl3.yml index 7b2cfe9987..ba21795d45 100644 --- a/.github/workflows/build-sdl3.yml +++ b/.github/workflows/build-sdl3.yml @@ -97,6 +97,17 @@ jobs: cmake --build . --config Release --parallel sudo cmake --install . --config Release + - name: Install SDL3_ttf + if: matrix.os != 'windows-latest' + run: | + git clone https://github.com/libsdl-org/SDL_ttf + cd SDL_ttf + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + cmake --build . --config Release --parallel + sudo cmake --install . --config Release + - name: Build with SDL3 run: python3 dev.py build --sdl3 diff --git a/buildconfig/download_win_prebuilt.py b/buildconfig/download_win_prebuilt.py index 2c4d3076ae..8edaefe81b 100644 --- a/buildconfig/download_win_prebuilt.py +++ b/buildconfig/download_win_prebuilt.py @@ -98,6 +98,10 @@ def get_urls(x86=True, x64=True): '2d18b9a4fc2ec0eee80de2a946b088d4e6efd0ee' ], [ + 'https://github.com/libsdl-org/SDL_ttf/releases/download/preview-3.1.0/SDL3_ttf-devel-3.1.0-VC.zip', + '34bb4a03c6f0f6c9de3658bac98adc7029830578' + ], + [ 'https://github.com/libsdl-org/SDL_mixer/releases/download/release-2.8.0/SDL2_mixer-devel-2.8.0-VC.zip', 'a10411644e08cd94f29712f430c7b71c407ae76d', ], @@ -251,6 +255,18 @@ def copy(src, dst): 'SDL2_ttf-2.24.0' ) ) + copy( + os.path.join( + temp_dir, + 'SDL3_ttf-devel-3.1.0-VC/SDL3_ttf-3.1.0' + ), + os.path.join( + move_to_dir, + prebuilt_dir, + 'SDL3_ttf-3.1.0' + ) + ) + copy( os.path.join( temp_dir, diff --git a/dev.py b/dev.py index 0b1b96ce42..d53bca86df 100644 --- a/dev.py +++ b/dev.py @@ -27,7 +27,6 @@ SDL3_ARGS = [ "-Csetup-args=-Dsdl_api=3", "-Csetup-args=-Dmixer=disabled", - "-Csetup-args=-Dfont=disabled", ] COVERAGE_ARGS = ["-Csetup-args=-Dcoverage=true"] diff --git a/meson.build b/meson.build index eb966ef0e4..13c5ebf54c 100644 --- a/meson.build +++ b/meson.build @@ -112,7 +112,7 @@ if plat == 'win' and host_machine.cpu_family().startswith('x86') sdl_ver = (sdl_api == 3) ? '3.2.2' : '2.30.12' sdl_image_ver = (sdl_api == 3) ? '3.1.1' : '2.8.4' sdl_mixer_ver = '2.8.0' - sdl_ttf_ver = '2.24.0' + sdl_ttf_ver = (sdl_api == 3) ? '3.1.0' : '2.24.0' dlls = [] diff --git a/src_c/font.c b/src_c/font.c index 8530bcd3e1..6243b05184 100644 --- a/src_c/font.c +++ b/src_c/font.c @@ -169,7 +169,11 @@ font_get_height(PyObject *self, PyObject *_null) } TTF_Font *font = PyFont_AsFont(self); +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + return PyLong_FromLong(TTF_GetFontHeight(font)); +#else return PyLong_FromLong(TTF_FontHeight(font)); +#endif } static PyObject * @@ -180,7 +184,11 @@ font_get_descent(PyObject *self, PyObject *_null) } TTF_Font *font = PyFont_AsFont(self); +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + return PyLong_FromLong(TTF_GetFontDescent(font)); +#else return PyLong_FromLong(TTF_FontDescent(font)); +#endif } static PyObject * @@ -191,7 +199,11 @@ font_get_ascent(PyObject *self, PyObject *_null) } TTF_Font *font = PyFont_AsFont(self); +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + return PyLong_FromLong(TTF_GetFontAscent(font)); +#else return PyLong_FromLong(TTF_FontAscent(font)); +#endif } static PyObject * @@ -202,7 +214,11 @@ font_get_linesize(PyObject *self, PyObject *_null) } TTF_Font *font = PyFont_AsFont(self); +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + return PyLong_FromLong(TTF_GetFontLineSkip(font)); +#else return PyLong_FromLong(TTF_FontLineSkip(font)); +#endif } static PyObject * @@ -453,7 +469,10 @@ font_getter_align(PyObject *self, void *closure) return RAISE_FONT_QUIT_ERROR(); } -#if SDL_TTF_VERSION_ATLEAST(2, 20, 0) +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + TTF_Font *font = PyFont_AsFont(self); + return PyLong_FromLong(TTF_GetFontWrapAlignment(font)); +#elif SDL_TTF_VERSION_ATLEAST(2, 20, 0) TTF_Font *font = PyFont_AsFont(self); return PyLong_FromLong(TTF_GetFontWrappedAlign(font)); #else @@ -492,7 +511,11 @@ font_setter_align(PyObject *self, PyObject *value, void *closure) return -1; } +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + TTF_SetFontWrapAlignment(font, val); +#else TTF_SetFontWrappedAlign(font, val); +#endif return 0; #else PyErr_SetString(pgExc_SDLError, @@ -611,12 +634,19 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) length string */ if (strlen(astring) == 0) { /* special 0 string case */ +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + int height = TTF_GetFontHeight(font); +#else int height = TTF_FontHeight(font); +#endif surf = PG_CreateSurface(0, height, SDL_PIXELFORMAT_XRGB8888); } else { /* normal case */ if (antialias && bg_rgba_obj == Py_None) { -#if SDL_TTF_VERSION_ATLEAST(2, 0, 18) +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + surf = TTF_RenderText_Blended_Wrapped(font, astring, 0, foreg, + wraplength); +#elif SDL_TTF_VERSION_ATLEAST(2, 0, 18) surf = TTF_RenderUTF8_Blended_Wrapped(font, astring, foreg, wraplength); #else @@ -624,7 +654,10 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) #endif } else if (antialias) { -#if SDL_TTF_VERSION_ATLEAST(2, 0, 18) +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + surf = TTF_RenderText_Shaded_Wrapped(font, astring, 0, foreg, + backg, wraplength); +#elif SDL_TTF_VERSION_ATLEAST(2, 0, 18) surf = TTF_RenderUTF8_Shaded_Wrapped(font, astring, foreg, backg, wraplength); #else @@ -632,7 +665,10 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) #endif } else { -#if SDL_TTF_VERSION_ATLEAST(2, 0, 18) +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + surf = TTF_RenderText_Solid_Wrapped(font, astring, 0, foreg, + wraplength); +#elif SDL_TTF_VERSION_ATLEAST(2, 0, 18) surf = TTF_RenderUTF8_Solid_Wrapped(font, astring, foreg, wraplength); #else @@ -642,9 +678,16 @@ font_render(PyObject *self, PyObject *args, PyObject *kwds) resolve to Render_Solid, that needs to be explicitly handled. */ if (surf != NULL && bg_rgba_obj != Py_None) { SDL_SetColorKey(surf, 0, 0); - surf->format->palette->colors[0].r = backg.r; - surf->format->palette->colors[0].g = backg.g; - surf->format->palette->colors[0].b = backg.b; +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + SDL_Palette *palette = SDL_GetSurfacePalette(surf); +#else + SDL_Palette *palette = surf->format->palette; +#endif + if (palette) { + palette->colors[0].r = backg.r; + palette->colors[0].g = backg.g; + palette->colors[0].b = backg.b; + } } } } @@ -683,7 +726,11 @@ font_size(PyObject *self, PyObject *text) return NULL; } string = PyBytes_AS_STRING(bytes); +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + ecode = TTF_GetStringSize(font, string, 0, &w, &h); +#else ecode = TTF_SizeUTF8(font, string, &w, &h); +#endif Py_DECREF(bytes); if (ecode) { return RAISE(pgExc_SDLError, TTF_GetError()); @@ -691,7 +738,12 @@ font_size(PyObject *self, PyObject *text) } else if (PyBytes_Check(text)) { string = PyBytes_AS_STRING(text); - if (TTF_SizeText(font, string, &w, &h)) { +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + if (TTF_GetStringSize(font, string, 0, &w, &h)) +#else + if (TTF_SizeText(font, string, &w, &h)) +#endif + { return RAISE(pgExc_SDLError, TTF_GetError()); } } @@ -737,7 +789,13 @@ font_setter_point_size(PyFontObject *self, PyObject *value, void *closure) return -1; } - if (TTF_SetFontSize(font, val) == -1) { +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + /* TODO: can consider supporting float in python API */ + if (!TTF_SetFontSize(font, (float)val)) +#else + if (TTF_SetFontSize(font, val) == -1) +#endif + { PyErr_SetString(pgExc_SDLError, SDL_GetError()); return -1; } @@ -786,7 +844,13 @@ font_set_ptsize(PyObject *self, PyObject *arg) "point_size cannot be equal to, or less than 0"); } - if (TTF_SetFontSize(font, val) == -1) { +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + /* TODO: can consider supporting float in python API */ + if (!TTF_SetFontSize(font, (float)val)) +#else + if (TTF_SetFontSize(font, val) == -1) +#endif + { return RAISE(pgExc_SDLError, SDL_GetError()); } ((PyFontObject *)self)->ptsize = val; @@ -806,7 +870,11 @@ font_getter_name(PyObject *self, void *closure) } TTF_Font *font = PyFont_AsFont(self); +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + const char *font_name = TTF_GetFontFamilyName(font); +#else const char *font_name = TTF_FontFaceFamilyName(font); +#endif return PyUnicode_FromString(font_name ? font_name : ""); } @@ -819,7 +887,11 @@ font_getter_style_name(PyObject *self, void *closure) } TTF_Font *font = PyFont_AsFont(self); +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + const char *font_style_name = TTF_GetFontStyleName(font); +#else const char *font_style_name = TTF_FontFaceStyleName(font); +#endif return PyUnicode_FromString(font_style_name ? font_style_name : ""); } @@ -878,13 +950,19 @@ font_metrics(PyObject *self, PyObject *textobj) for (i = 1 /* skip BOM */; i < length; i++) { ch = buffer[i]; surrogate = Py_UNICODE_IS_SURROGATE(ch); - /* TODO: - * TTF_GlyphMetrics() seems to return a value for any character, - * using the default invalid character, if the char is not found. - */ +/* TODO: + * TTF_GlyphMetrics() seems to return a value for any character, + * using the default invalid character, if the char is not found. + */ +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + if (!surrogate && /* conditional and */ + !TTF_GetGlyphMetrics(font, (Uint16)ch, &minx, &maxx, &miny, &maxy, + &advance)) { +#else if (!surrogate && /* conditional and */ !TTF_GlyphMetrics(font, (Uint16)ch, &minx, &maxx, &miny, &maxy, &advance)) { +#endif listitem = Py_BuildValue("(iiiii)", minx, maxx, miny, maxy, advance); if (!listitem) { @@ -912,6 +990,14 @@ font_metrics(PyObject *self, PyObject *textobj) return list; } +/* This is taken from the harfbuzz header file. It coverts script name in the + * format expected by sdl2 (a 4 char string) to the format expected by sdl3 + * a single uint32 tag */ +#define HB_TAG(c1, c2, c3, c4) \ + ((Uint32)((((uint32_t)(c1) & 0xFF) << 24) | \ + (((uint32_t)(c2) & 0xFF) << 16) | \ + (((uint32_t)(c3) & 0xFF) << 8) | ((uint32_t)(c4) & 0xFF))) + static PyObject * font_set_script(PyObject *self, PyObject *arg) { @@ -935,7 +1021,13 @@ font_set_script(PyObject *self, PyObject *arg) "script code must be exactly 4 characters"); } - if (TTF_SetFontScriptName(font, script_code) < 0) { +#if SDL_TTF_VERSION_ATLEAST(3, 0, 0) + if (!TTF_SetFontScript(font, HB_TAG(script_code[0], script_code[1], + script_code[2], script_code[3]))) +#else + if (TTF_SetFontScriptName(font, script_code) < 0) +#endif + { return RAISE(pgExc_SDLError, SDL_GetError()); } #else @@ -1183,7 +1275,8 @@ font_init(PyFontObject *self, PyObject *args, PyObject *kwds) Py_BEGIN_ALLOW_THREADS; #if SDL_VERSION_ATLEAST(3, 0, 0) - font = TTF_OpenFontIO(rw, 1, fontsize); + /* TODO: can consider supporting float in python API */ + font = TTF_OpenFontIO(rw, 1, (float)fontsize); #else font = TTF_OpenFontRW(rw, 1, fontsize); #endif diff --git a/src_c/font.h b/src_c/font.h index f5eedb2537..358a66abf8 100644 --- a/src_c/font.h +++ b/src_c/font.h @@ -1,8 +1,15 @@ #ifndef PGFONT_INTERNAL_H #define PGFONT_INTERNAL_H -#include +#ifdef PG_SDL3 +#include + +// SDL3_ttf uses SDL3 error reporting API +#define TTF_GetError SDL_GetError +#else +#include +#endif /* test font initialization */ #define FONT_INIT_CHECK() \ if (!(*(int *)PyFONT_C_API[2])) \ diff --git a/src_c/meson.build b/src_c/meson.build index d56d6b014d..c67962b5ca 100644 --- a/src_c/meson.build +++ b/src_c/meson.build @@ -403,9 +403,6 @@ if sdl_image_dep.found() ) endif -# TODO: support SDL3 -if sdl_api != 3 - if sdl_ttf_dep.found() font = py.extension_module( 'font', @@ -417,6 +414,9 @@ if sdl_ttf_dep.found() ) endif +# TODO: support SDL3 +if sdl_api != 3 + if sdl_mixer_dep.found() mixer = py.extension_module( 'mixer',