From e055535be92fb3db5bf19b949d2aada4057d1d59 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Thu, 26 May 2022 17:31:09 +0100 Subject: [PATCH 1/2] Use global direction and script if per-font values were not set This fixes a regression for the py-sdl2 test suite, which exercises the (now-deprecated) TTF_SetDirection() and TTF_SetScript(), and expects setting a new global value to have an effect on pre-existing font objects. Resolves: https://github.com/libsdl-org/SDL_ttf/issues/221 Signed-off-by: Simon McVittie --- SDL_ttf.c | 35 ++++++++++++++++++++++++++++------- SDL_ttf.h | 5 ++++- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/SDL_ttf.c b/SDL_ttf.c index 8a4bb3d8..f770befc 100644 --- a/SDL_ttf.c +++ b/SDL_ttf.c @@ -290,7 +290,9 @@ struct _TTF_Font { int render_subpixel; #if TTF_USE_HARFBUZZ hb_font_t *hb_font; + /* If HB_SCRIPT_INVALID, use global default script */ hb_script_t hb_script; + /* If HB_DIRECTION_INVALID, use global default direction */ hb_direction_t hb_direction; #endif int render_sdf; @@ -1002,10 +1004,15 @@ static void Draw_Line(TTF_Font *font, const SDL_Surface *textbuf, int column, in int tmp = row + line_thickness - textbuf->h; int x_offset = column * textbuf->format->BytesPerPixel; Uint8 *dst = (Uint8 *)textbuf->pixels + row * textbuf->pitch + x_offset; - #if TTF_USE_HARFBUZZ + hb_direction_t hb_direction = font->hb_direction; + + if (hb_direction == HB_DIRECTION_INVALID) { + hb_direction = g_hb_direction; + } + /* No Underline/Strikethrough style if direction is vertical */ - if (font->hb_direction == HB_DIRECTION_TTB || font->hb_direction == HB_DIRECTION_BTT) { + if (hb_direction == HB_DIRECTION_TTB || hb_direction == HB_DIRECTION_BTT) { return; } #endif @@ -1873,9 +1880,9 @@ TTF_Font* TTF_OpenFontIndexDPIRW(SDL_RWops *src, int freesrc, int ptsize, long i * you will get mismatching advances and raster. */ hb_ft_font_set_load_flags(font->hb_font, FT_LOAD_DEFAULT | font->ft_load_target); - /* Default value script / direction */ - font->hb_script = g_hb_script; - font->hb_direction = g_hb_direction; + /* By default the script / direction are inherited from global variables */ + font->hb_script = HB_SCRIPT_INVALID; + font->hb_direction = HB_DIRECTION_INVALID; #endif if (TTF_SetFontSizeDPI(font, ptsize, hdpi, vdpi) < 0) { @@ -3116,6 +3123,8 @@ static int TTF_Size_Internal(TTF_Font *font, Uint8 *utf8_alloc = NULL; c_glyph *glyph; #if TTF_USE_HARFBUZZ + hb_direction_t hb_direction; + hb_script_t hb_script; hb_buffer_t *hb_buffer = NULL; unsigned int g; unsigned int glyph_count; @@ -3171,9 +3180,21 @@ static int TTF_Size_Internal(TTF_Font *font, goto failure; } + + hb_direction = font->hb_direction; + hb_script = font->hb_script; + + if (hb_script == HB_SCRIPT_INVALID) { + hb_script = g_hb_script; + } + + if (hb_direction == HB_DIRECTION_INVALID) { + hb_direction = g_hb_direction; + } + /* Set global configuration */ - hb_buffer_set_direction(hb_buffer, font->hb_direction); - hb_buffer_set_script(hb_buffer, font->hb_script); + hb_buffer_set_direction(hb_buffer, hb_direction); + hb_buffer_set_script(hb_buffer, hb_script); /* Layout the text */ hb_buffer_add_utf8(hb_buffer, text, -1, 0, -1); diff --git a/SDL_ttf.h b/SDL_ttf.h index 1a4d80b9..044cd08c 100644 --- a/SDL_ttf.h +++ b/SDL_ttf.h @@ -439,7 +439,10 @@ typedef enum extern DECLSPEC int SDLCALL TTF_SetDirection(int direction); /* hb_direction_t */ extern DECLSPEC int SDLCALL TTF_SetScript(int script); /* hb_script_t */ -/* Set direction and script per font. +/* Set direction and script per font, overriding the global direction + and script set with the deprecated TTF_SetDirection() and + TTF_SetScript(). + 'script' is null terminated string of exactly 4 characters. These functions return 0, or -1 if SDL_ttf is not compiled with HarfBuzz or invalid parameter */ From 81e54a31283969400187f6b74bad2b23391416bd Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Thu, 26 May 2022 18:18:11 +0100 Subject: [PATCH 2/2] Add a semi-automatic test for #221 This needs to be given a valid font as a command-line argument, but is otherwise automatic. Signed-off-by: Simon McVittie --- Makefile.am | 6 +- test/main.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 test/main.c diff --git a/Makefile.am b/Makefile.am index a75ac4a7..6579cd27 100644 --- a/Makefile.am +++ b/Makefile.am @@ -147,7 +147,11 @@ EXTRA_DIST = CHANGES.txt COPYING.txt README.txt \ SDL2_ttfConfig.cmake.in \ autogen.sh gcc-fat.sh -noinst_PROGRAMS = showfont glfont +noinst_PROGRAMS = showfont glfont testfont + +testfont_CPPFLAGS = $(TTF_CFLAGS) $(LOCAL_HB_FLAGS) +testfont_SOURCES = test/main.c +testfont_LDADD = libSDL2_ttf.la -lSDL2_test showfont_LDADD = libSDL2_ttf.la glfont_LDADD = libSDL2_ttf.la @GL_LIBS@ @MATHLIB@ diff --git a/test/main.c b/test/main.c new file mode 100644 index 00000000..79d7efb6 --- /dev/null +++ b/test/main.c @@ -0,0 +1,236 @@ +/* + Copyright 1997-2022 Sam Lantinga + Copyright 2022 Collabora Ltd. + + 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. +*/ + +#include "SDL_ttf.h" +#include "SDL_test.h" + +#if TTF_USE_HARFBUZZ +#include +#endif + +static SDLTest_CommonState *state; +static const char *fontPath = NULL; + +static int +TestDirection(void *arg) +{ + static const char hello[] = "Hello, world!"; + TTF_Font *font = NULL; + const char *error; + int ltr_w, ltr_h; +#if TTF_USE_HARFBUZZ + TTF_Font *font2 = NULL; + int ttb_w, ttb_h; + int w, h; +#endif + + error = (TTF_Init() == 0) ? NULL : TTF_GetError(); + if (!SDLTest_AssertCheck(error == NULL, + "Init: %s", error ? error : "successful")) { + goto out; + } + + font = TTF_OpenFont(fontPath, 12); + error = (font != NULL) ? "successful" : TTF_GetError(); + if (!SDLTest_AssertCheck(font != NULL, + "Load font %s: %s", fontPath, error)) { + goto out; + } + + TTF_SizeUTF8(font, hello, <r_w, <r_h); + SDLTest_AssertCheck(ltr_w > ltr_h, + "Default text direction is horizontal: %d > %d", + ltr_w, ltr_h); + +#if TTF_USE_HARFBUZZ + SDLTest_AssertCheck(TTF_SetDirection(HB_DIRECTION_TTB) == 0, + "Set global direction"); + TTF_SizeUTF8(font, hello, &ttb_w, &ttb_h); + SDLTest_AssertCheck(ttb_w < ttb_h, + "Changing global direction works: %d < %d", + ttb_w, ttb_h); + SDLTest_AssertCheck(TTF_SetDirection(HB_DIRECTION_LTR) == 0, + "Set global direction"); + TTF_SizeUTF8(font, hello, &w, &h); + SDLTest_AssertCheck(w == ltr_w && h == ltr_h, + "Changing global direction back works: %dx%d", + w, h); + + SDLTest_AssertCheck(TTF_SetFontDirection(font, TTF_DIRECTION_TTB) == 0, + "Set font direction"); + TTF_SizeUTF8(font, hello, &w, &h); + SDLTest_AssertCheck(w == ttb_w && h == ttb_h, + "Can change per-font direction: %dx%d", w, h); + + SDLTest_AssertCheck(TTF_SetFontDirection(font, TTF_DIRECTION_LTR) == 0, + "Set font direction"); + SDLTest_AssertCheck(TTF_SetDirection(HB_DIRECTION_TTB) == 0, + "Set global direction"); + TTF_SizeUTF8(font, hello, &w, &h); + SDLTest_AssertCheck(w == ltr_w && h == ltr_h, + "Changing font direction works: %dx%d", w, h); + + font2 = TTF_OpenFont(fontPath, 12); + error = (font2 != NULL) ? "successful" : TTF_GetError(); + if (!SDLTest_AssertCheck(font2 != NULL, + "Load font %s: %s", fontPath, error)) { + goto out; + } + + TTF_SizeUTF8(font2, hello, &w, &h); + SDLTest_AssertCheck(w == ttb_w && h == ttb_h, + "Global direction is used for new font: %dx%d", + w, h); +#endif + +out: + if (font != NULL) { + TTF_CloseFont(font); + } + if (TTF_WasInit()) { + TTF_Quit(); + } + return TEST_COMPLETED; +} + +static const SDLTest_TestCaseReference directionTestCase = { + TestDirection, "Direction", "Render text directionally", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference *testCases[] = { + &directionTestCase, + NULL +}; +static SDLTest_TestSuiteReference testSuite = { + "ttf", + NULL, + testCases, + NULL +}; +static SDLTest_TestSuiteReference *testSuites[] = { + &testSuite, + NULL +}; + +/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ +static void +quit(int rc) +{ + SDLTest_CommonQuit(state); + exit(rc); +} + +int +main(int argc, char *argv[]) +{ + int result; + int testIterations = 1; + Uint64 userExecKey = 0; + char *userRunSeed = NULL; + char *filter = NULL; + int i, done; + SDL_Event event; + + /* Initialize test framework */ + state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); + if (!state) { + return 1; + } + + /* Parse commandline */ + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + if (consumed == 0) { + consumed = -1; + if (SDL_strcasecmp(argv[i], "--iterations") == 0) { + if (argv[i + 1]) { + testIterations = SDL_atoi(argv[i + 1]); + if (testIterations < 1) testIterations = 1; + consumed = 2; + } + } + else if (SDL_strcasecmp(argv[i], "--execKey") == 0) { + if (argv[i + 1]) { + SDL_sscanf(argv[i + 1], "%"SDL_PRIu64, &userExecKey); + consumed = 2; + } + } + else if (SDL_strcasecmp(argv[i], "--seed") == 0) { + if (argv[i + 1]) { + userRunSeed = SDL_strdup(argv[i + 1]); + consumed = 2; + } + } + else if (SDL_strcasecmp(argv[i], "--filter") == 0) { + if (argv[i + 1]) { + filter = SDL_strdup(argv[i + 1]); + consumed = 2; + } + } + else if (argv[i][0] != '-') { + if (fontPath == NULL) { + fontPath = argv[i]; + consumed = 1; + } + } + } + if (consumed < 0) { + static const char *options[] = { "[--iterations #]", "[--execKey #]", "[--seed string]", "[--filter suite_name|test_name] font_path", NULL }; + SDLTest_CommonLogUsage(state, argv[0], options); + quit(1); + } + + i += consumed; + } + + if (fontPath == NULL) { + SDLTest_LogError("A font is required"); + quit(1); + } + + /* Initialize common state */ + if (!SDLTest_CommonInit(state)) { + quit(2); + } + + /* Create the windows, initialize the renderers */ + for (i = 0; i < state->num_windows; ++i) { + SDL_Renderer *renderer = state->renderers[i]; + SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); + SDL_RenderClear(renderer); + } + + /* Call Harness */ + result = SDLTest_RunSuites(testSuites, (const char *)userRunSeed, userExecKey, (const char *)filter, testIterations); + + /* Empty event queue */ + done = 0; + for (i=0; i<100; i++) { + while (SDL_PollEvent(&event)) { + SDLTest_CommonEvent(state, &event, &done); + } + SDL_Delay(10); + } + + /* Clean up */ + SDL_free(userRunSeed); + SDL_free(filter); + + /* Shutdown everything */ + quit(result); + return(result); +} + +/* vi: set ts=4 sw=4 expandtab: */