From c2429e85ec4a1ebccaf1aaf4ee967bde6f62ae2f Mon Sep 17 00:00:00 2001 From: eafton Date: Sat, 4 Oct 2025 05:08:24 +0300 Subject: [PATCH] X11TK: Add Arabic/Hebrew/Farsi support by increasing font size slightly and using FriBidi. (#14134) --- CMakeLists.txt | 4 +- cmake/sdlchecks.cmake | 29 ++ include/build_config/SDL_build_config.h.cmake | 2 + src/core/unix/SDL_fribidi.c | 160 +++++++++++ src/core/unix/SDL_fribidi.h | 58 ++++ src/video/x11/SDL_x11sym.h | 2 + src/video/x11/SDL_x11toolkit.c | 259 ++++++++++++++++-- src/video/x11/SDL_x11toolkit.h | 10 +- test/testmessage.c | 9 + 9 files changed, 503 insertions(+), 30 deletions(-) create mode 100644 src/core/unix/SDL_fribidi.c create mode 100644 src/core/unix/SDL_fribidi.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a38e5f8c34..77892a2203 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -347,6 +347,8 @@ dep_option(SDL_X11_XSCRNSAVER "Enable Xscrnsaver support" ON SDL_X11 OFF) dep_option(SDL_X11_XSHAPE "Enable XShape support" ON SDL_X11 OFF) dep_option(SDL_X11_XSYNC "Enable Xsync support" ON SDL_X11 OFF) dep_option(SDL_X11_XTEST "Enable XTest support" ON SDL_X11 OFF) +dep_option(SDL_FRIBIDI "Enable Fribidi support" ON SDL_X11 OFF) +dep_option(SDL_FRIBIDI_SHARED "Dynamically load Fribidi support" ON "SDL_FRIBIDI;SDL_DEPS_SHARED" OFF) dep_option(SDL_WAYLAND "Use Wayland video driver" ${UNIX_SYS} "SDL_VIDEO" OFF) dep_option(SDL_WAYLAND_SHARED "Dynamically load Wayland support" ON "SDL_WAYLAND;SDL_DEPS_SHARED" OFF) dep_option(SDL_WAYLAND_LIBDECOR "Use client-side window decorations on Wayland" ON "SDL_WAYLAND" OFF) @@ -1776,6 +1778,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) # Need to check for ROCKCHIP platform and get rid of "Can't window GBM/EGL surfaces on window creation." CheckROCKCHIP() CheckX11() + CheckFribidi() # Need to check for EGL first because KMSDRM and Wayland depend on it. CheckEGL() CheckKMSDRM() @@ -1925,7 +1928,6 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) set(HAVE_LIBURING TRUE) endif() endif() - if((FREEBSD OR NETBSD) AND NOT HAVE_INOTIFY) set(LibInotify_PKG_CONFIG_SPEC libinotify) pkg_check_modules(PC_LIBINOTIFY IMPORTED_TARGET ${LibInotify_PKG_CONFIG_SPEC}) diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake index c5a51edd1d..27c007f35a 100644 --- a/cmake/sdlchecks.cmake +++ b/cmake/sdlchecks.cmake @@ -511,6 +511,35 @@ macro(CheckX11) cmake_pop_check_state() endmacro() +macro(CheckFribidi) + if(SDL_FRIBIDI) + set(FRIBIDI_PKG_CONFIG_SPEC fribidi) + set(PC_FRIBIDI_FOUND FALSE) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FRIBIDI IMPORTED_TARGET ${FRIBIDI_PKG_CONFIG_SPEC}) + endif() + if(PC_FRIBIDI_FOUND) + set(HAVE_FRIBIDI TRUE) + set(HAVE_FRIBIDI_H 1) + sdl_sources( + "${SDL3_SOURCE_DIR}/src/core/unix/SDL_fribidi.c" + "${SDL3_SOURCE_DIR}/src/core/unix/SDL_fribidi.h" + ) + if(SDL_FRIBIDI_SHARED AND NOT HAVE_SDL_LOADSO) + message(WARNING "You must have SDL_LoadObject() support for dynamic fribidi loading") + endif() + FindLibraryAndSONAME("fribidi" LIBDIRS ${PC_FRIBIDI_LIBRARY_DIRS}) + if(SDL_FRIBIDI_SHARED AND FRIBIDI_LIB AND HAVE_SDL_LOADSO) + set(SDL_FRIBIDI_DYNAMIC "\"${FRIBIDI_LIB_SONAME}\"") + set(HAVE_FRIBIDI_SHARED TRUE) + sdl_include_directories(PRIVATE SYSTEM $) + else() + sdl_link_dependency(fribidi LIBS PkgConfig::PC_FRIBIDI PKG_CONFIG_PREFIX PC_FRIBIDI PKG_CONFIG_SPECS ${FRIBIDI_PKG_CONFIG_SPEC}) + endif() + endif() + endif() +endmacro() + macro(WaylandProtocolGen _SCANNER _CODE_MODE _XML _PROTL) set(_WAYLAND_PROT_C_CODE "${CMAKE_CURRENT_BINARY_DIR}/wayland-generated-protocols/${_PROTL}-protocol.c") set(_WAYLAND_PROT_H_CODE "${CMAKE_CURRENT_BINARY_DIR}/wayland-generated-protocols/${_PROTL}-client-protocol.h") diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 10b39b6892..36d6ef988d 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -216,6 +216,8 @@ #cmakedefine HAVE_LIBUDEV_H 1 #cmakedefine HAVE_LIBDECOR_H 1 #cmakedefine HAVE_LIBURING_H 1 +#cmakedefine HAVE_FRIBIDI_H 1 +#cmakedefine SDL_FRIBIDI_DYNAMIC @SDL_FRIBIDI_DYNAMIC@ #cmakedefine HAVE_DDRAW_H 1 #cmakedefine HAVE_DSOUND_H 1 diff --git a/src/core/unix/SDL_fribidi.c b/src/core/unix/SDL_fribidi.c new file mode 100644 index 0000000000..1c7f863fe2 --- /dev/null +++ b/src/core/unix/SDL_fribidi.c @@ -0,0 +1,160 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + 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. +*/ + +#ifdef HAVE_FRIBIDI_H + +#include "SDL_internal.h" +#include "SDL_fribidi.h" +#include + +SDL_FriBidi *SDL_FriBidi_Create(void) { + SDL_FriBidi *fribidi; + + fribidi = (SDL_FriBidi *)SDL_malloc(sizeof(SDL_FriBidi)); + if (!fribidi) { + return NULL; + } + +#ifdef SDL_FRIBIDI_DYNAMIC + #define SDL_FRIBIDI_LOAD_SYM(x, n, t) x = ((t)SDL_LoadFunction(fribidi->lib, n)); if (!x) { SDL_UnloadObject(fribidi->lib); SDL_free(fribidi); return NULL; } + + fribidi->lib = SDL_LoadObject(SDL_FRIBIDI_DYNAMIC); + if (!fribidi->lib) { + SDL_free(fribidi); + return NULL; + } + + SDL_FRIBIDI_LOAD_SYM(fribidi->unicode_to_charset, "fribidi_unicode_to_charset", SDL_FriBidiUnicodeToCharset); + SDL_FRIBIDI_LOAD_SYM(fribidi->charset_to_unicode, "fribidi_charset_to_unicode", SDL_FriBidiCharsetToUnicode); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_bidi_types, "fribidi_get_bidi_types", SDL_FriBidiGetBidiTypes); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_par_direction, "fribidi_get_par_direction", SDL_FriBidiGetParDirection); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_par_embedding_levels, "fribidi_get_par_embedding_levels", SDL_FriBidiGetParEmbeddingLevels); + SDL_FRIBIDI_LOAD_SYM(fribidi->get_joining_types, "fribidi_get_joining_types", SDL_FriBidiGetJoiningTypes); + SDL_FRIBIDI_LOAD_SYM(fribidi->join_arabic, "fribidi_join_arabic", SDL_FriBidiJoinArabic); + SDL_FRIBIDI_LOAD_SYM(fribidi->shape, "fribidi_shape", SDL_FriBidiShape); + SDL_FRIBIDI_LOAD_SYM(fribidi->reorder_line, "fribidi_reorder_line", SDL_FriBidiReorderLine); +#else + fribidi->unicode_to_charset = fribidi_unicode_to_charset; + fribidi->charset_to_unicode = fribidi_charset_to_unicode; + fribidi->get_bidi_types = fribidi_get_bidi_types; + fribidi->get_par_direction = fribidi_get_par_direction; + fribidi->get_par_embedding_levels = fribidi_get_par_embedding_levels; + fribidi->get_joining_types = fribidi_get_joining_types; + fribidi->join_arabic = fribidi_join_arabic; + fribidi->shape = fribidi_shape; + fribidi->reorder_line = fribidi_reorder_line; +#endif + + return fribidi; +} + +char *SDL_FriBidi_Process(SDL_FriBidi *fribidi, char *utf8, ssize_t utf8_len, bool shaping, FriBidiParType *out_par_type) { + FriBidiCharType *types; + FriBidiLevel *levels; + FriBidiArabicProp *props; + FriBidiChar *str; + char *result; + FriBidiStrIndex len; + FriBidiLevel max_level; + FriBidiLevel start; + FriBidiLevel end; + FriBidiParType direction; + FriBidiParType str_direction; + unsigned int i; + unsigned int c; + + if (!fribidi || !utf8) { + return NULL; + } + + /* Convert to UTF32 */ + if (utf8_len < 0) { + utf8_len = SDL_strlen(utf8); + } + str = SDL_calloc(SDL_utf8strnlen(utf8, utf8_len), sizeof(FriBidiChar)); + len = fribidi->charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, utf8, utf8_len, str); + + /* Setup various BIDI structures */ + direction = FRIBIDI_PAR_LTR; + types = NULL; + levels = NULL; + props = SDL_calloc(len + 1, sizeof(FriBidiArabicProp)); + levels = SDL_calloc(len + 1, sizeof(FriBidiLevel)); + types = SDL_calloc(len + 1, sizeof(FriBidiCharType)); + + /* Shape */ + fribidi->get_bidi_types(str, len, types); + str_direction = fribidi->get_par_direction(types, len); + max_level = fribidi->get_par_embedding_levels(types, len, &direction, levels); + if (shaping) { + fribidi->get_joining_types(str, len, props); + fribidi->join_arabic(types, len, levels, props); + fribidi->shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC, levels, len, props, str); + } + + /* BIDI */ + for (end = 0, start = 0; end < len; end++) { + if (str[end] == '\n' || str[end] == '\r' || str[end] == '\f' || str[end] == '\v' || end == len - 1) { + max_level = fribidi->reorder_line(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC, types, end - start + 1, start, direction, levels, str, NULL); + start = end + 1; + } + } + + /* Silence warning */ + (void)max_level; + + /* Remove fillers */ + for (i = 0, c = 0; i < len; i++) { + if (str[i] != FRIBIDI_CHAR_FILL) { + str[c++] = str[i]; + } + } + len = c; + + /* Convert back to UTF8 */ + result = SDL_malloc(len * 4 + 1); + fribidi->unicode_to_charset(FRIBIDI_CHAR_SET_UTF8, str, len, result); + + /* Cleanup */ + SDL_free(levels); + SDL_free(props); + SDL_free(types); + + /* Return */ + if (out_par_type) { + *out_par_type = str_direction; + } + return result; +} + +void SDL_FriBidi_Destroy(SDL_FriBidi *fribidi) { + if (!fribidi) { + return; + } + +#ifdef SDL_FRIBIDI_DYNAMIC + SDL_UnloadObject(fribidi->lib); +#endif + + SDL_free(fribidi); +} + +#endif diff --git a/src/core/unix/SDL_fribidi.h b/src/core/unix/SDL_fribidi.h new file mode 100644 index 0000000000..27668812a6 --- /dev/null +++ b/src/core/unix/SDL_fribidi.h @@ -0,0 +1,58 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + 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. +*/ + +#include "SDL_internal.h" + +#ifdef HAVE_FRIBIDI_H +#include + +#ifndef SDL_fribidi_h_ +#define SDL_fribidi_h_ + +typedef FriBidiStrIndex (*SDL_FriBidiUnicodeToCharset)(FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); +typedef FriBidiStrIndex (*SDL_FriBidiCharsetToUnicode)(FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); +typedef void (*SDL_FriBidiGetBidiTypes)(const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); +typedef FriBidiParType (*SDL_FriBidiGetParDirection)(const FriBidiCharType *, const FriBidiStrIndex); +typedef FriBidiLevel (*SDL_FriBidiGetParEmbeddingLevels)(const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, FriBidiLevel *); +typedef void (*SDL_FriBidiGetJoiningTypes)(const FriBidiChar *, const FriBidiStrIndex, FriBidiJoiningType *); +typedef void (*SDL_FriBidiJoinArabic)(const FriBidiCharType *, const FriBidiStrIndex, const FriBidiLevel *, FriBidiArabicProp *); +typedef void (*SDL_FriBidiShape)(FriBidiFlags flags, const FriBidiLevel *, const FriBidiStrIndex, FriBidiArabicProp *, FriBidiChar *str); +typedef FriBidiLevel (*SDL_FriBidiReorderLine)(FriBidiFlags flags, const FriBidiCharType *, const FriBidiStrIndex, const FriBidiStrIndex, const FriBidiParType, FriBidiLevel *, FriBidiChar *, FriBidiStrIndex *); + +typedef struct SDL_FriBidi { + SDL_SharedObject *lib; + SDL_FriBidiUnicodeToCharset unicode_to_charset; + SDL_FriBidiCharsetToUnicode charset_to_unicode; + SDL_FriBidiGetBidiTypes get_bidi_types; + SDL_FriBidiGetParDirection get_par_direction; + SDL_FriBidiGetParEmbeddingLevels get_par_embedding_levels; + SDL_FriBidiGetJoiningTypes get_joining_types; + SDL_FriBidiJoinArabic join_arabic; + SDL_FriBidiShape shape; + SDL_FriBidiReorderLine reorder_line; +} SDL_FriBidi; + +extern SDL_FriBidi *SDL_FriBidi_Create(void); +extern char *SDL_FriBidi_Process(SDL_FriBidi *fribidi, char *utf8, ssize_t utf8_len, bool shaping, FriBidiParType *out_par_type); +extern void SDL_FriBidi_Destroy(SDL_FriBidi *fribidi); +#endif + +#endif // SDL_fribidi_h_ diff --git a/src/video/x11/SDL_x11sym.h b/src/video/x11/SDL_x11sym.h index ea0b42aae6..ba11faedee 100644 --- a/src/video/x11/SDL_x11sym.h +++ b/src/video/x11/SDL_x11sym.h @@ -235,8 +235,10 @@ SDL_X11_SYM(int,Xutf8TextExtents,(XFontSet a, _Xconst char* b, int c, XRectangle SDL_X11_SYM(char*,XSetLocaleModifiers,(const char *a)) SDL_X11_SYM(char*,Xutf8ResetIC,(XIC a)) SDL_X11_SYM(XFontSetExtents*,XExtentsOfFontSet,(XFontSet a)) +SDL_X11_SYM(Bool,XContextDependentDrawing,(XFontSet a)) #endif + #ifndef NO_SHARED_MEMORY SDL_X11_MODULE(SHM) SDL_X11_SYM(Status,XShmAttach,(Display* a,XShmSegmentInfo* b)) diff --git a/src/video/x11/SDL_x11toolkit.c b/src/video/x11/SDL_x11toolkit.c index 1507d10730..3f439cc393 100644 --- a/src/video/x11/SDL_x11toolkit.c +++ b/src/video/x11/SDL_x11toolkit.c @@ -28,6 +28,9 @@ #ifdef SDL_USE_LIBDBUS #include "../../core/linux/SDL_system_theme.h" #endif +#ifdef HAVE_FRIBIDI_H +#include "../../core/unix/SDL_fribidi.h" +#endif #include "SDL_x11dyn.h" #include "SDL_x11toolkit.h" #include "SDL_x11settings.h" @@ -70,12 +73,16 @@ typedef struct SDL_ToolkitButtonControlX11 /* Data */ const SDL_MessageBoxButtonData *data; - + /* Text */ SDL_Rect text_rect; int text_a; int text_d; int str_sz; +#ifdef HAVE_FRIBIDI_H + char *text; + bool free_text; +#endif /* Callback */ void *cb_data; @@ -90,6 +97,12 @@ typedef struct SDL_ToolkitLabelControlX11 int *y; size_t *szs; size_t sz; +#ifdef HAVE_FRIBIDI_H + int *x; + int *w; + bool *free_lines; + FriBidiParType *par_types; +#endif } SDL_ToolkitLabelControlX11; typedef struct SDL_ToolkitMenuBarControlX11 @@ -109,7 +122,7 @@ typedef struct SDL_ToolkitMenuControlX11 /* Font for icon control */ static const char *g_IconFont = "-*-*-bold-r-normal-*-%d-*-*-*-*-*-iso8859-1[33 88 105]"; -#define G_ICONFONT_SIZE 18 +#define G_ICONFONT_SIZE 22 /* General UI font */ static const char g_ToolkitFontLatin1[] = @@ -128,7 +141,7 @@ static const char *g_ToolkitFont[] = { "-*-*-*-*-*--*-*-*-*-*-*-iso8859-1,*", // just give me anything latin1. NULL }; -#define G_TOOLKITFONT_SIZE 120 +#define G_TOOLKITFONT_SIZE 140 static const SDL_MessageBoxColor g_default_colors[SDL_MESSAGEBOX_COLOR_COUNT] = { { 191, 184, 191 }, // SDL_MESSAGEBOX_COLOR_BACKGROUND, @@ -399,6 +412,10 @@ static void X11Toolkit_InitWindowFonts(SDL_ToolkitWindowX11 *window) if (!window->font_set) { goto load_font_traditional; + } else { +#ifdef HAVE_FRIBIDI_H + window->do_shaping = !X11_XContextDependentDrawing(window->font_set); +#endif } } else #endif @@ -549,27 +566,40 @@ static void X11Toolkit_GetTextWidthHeightForFont(XFontStruct *font, const char * *ascent = text_structure.ascent; } -static void X11Toolkit_GetTextWidthHeight(SDL_ToolkitWindowX11 *data, const char *str, int nbytes, int *pwidth, int *pheight, int *ascent, int *font_descent) +static void X11Toolkit_GetTextWidthHeight(SDL_ToolkitWindowX11 *data, const char *str, int nbytes, int *pwidth, int *pheight, int *ascent, int *descent, int *font_height) { #ifdef X_HAVE_UTF8_STRING if (data->utf8) { XRectangle overall_ink, overall_logical; + X11_Xutf8TextExtents(data->font_set, str, nbytes, &overall_ink, &overall_logical); *pwidth = overall_logical.width; *pheight = overall_logical.height; *ascent = -overall_logical.y; - *font_descent = overall_logical.height - *ascent; + *descent = overall_logical.height - *ascent; + + if (font_height) { + XFontSetExtents *extents; + + extents = X11_XExtentsOfFontSet(data->font_set); + *font_height = extents->max_logical_extent.height; + } } else #endif { XCharStruct text_structure; - int font_direction, font_ascent; + int font_direction, font_ascent, font_descent; X11_XTextExtents(data->font_struct, str, nbytes, - &font_direction, &font_ascent, font_descent, + &font_direction, &font_ascent, &font_descent, &text_structure); *pwidth = text_structure.width; *pheight = text_structure.ascent + text_structure.descent; *ascent = text_structure.ascent; + *descent = text_structure.descent; + + if (font_height) { + *font_height = font_ascent + font_descent; + } } } @@ -778,7 +808,10 @@ SDL_ToolkitWindowX11 *X11Toolkit_CreateWindowStruct(SDL_Window *parent, SDL_Tool /* Menu windows */ window->popup_windows = NULL; - + +#ifdef HAVE_FRIBIDI_H + window->fribidi = SDL_FriBidi_Create(); +#endif return window; } @@ -1653,7 +1686,7 @@ static void X11Toolkit_CalculateButtonControl(SDL_ToolkitControlX11 *control) { int text_d; button_control = (SDL_ToolkitButtonControlX11 *)control; - X11Toolkit_GetTextWidthHeight(control->window, button_control->data->text, button_control->str_sz, &button_control->text_rect.w, &button_control->text_rect.h, &button_control->text_a, &text_d); + X11Toolkit_GetTextWidthHeight(control->window, button_control->data->text, button_control->str_sz, &button_control->text_rect.w, &button_control->text_rect.h, &button_control->text_a, &text_d, NULL); if (control->do_size) { control->rect.w = SDL_TOOLKIT_X11_ELEMENT_PADDING_3 * 2 * control->window->iscale + button_control->text_rect.w; control->rect.h = SDL_TOOLKIT_X11_ELEMENT_PADDING_3 * 2 * control->window->iscale + button_control->text_rect.h; @@ -1665,8 +1698,16 @@ static void X11Toolkit_CalculateButtonControl(SDL_ToolkitControlX11 *control) { static void X11Toolkit_DrawButtonControl(SDL_ToolkitControlX11 *control) { SDL_ToolkitButtonControlX11 *button_control; - + char *text; + button_control = (SDL_ToolkitButtonControlX11 *)control; + +#ifdef HAVE_FRIBIDI_H + text = button_control->text; +#else + text = (char *)button_control->data->text; +#endif + X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); /* Draw bevel */ if (control->state == SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED || control->state == SDL_TOOLKIT_CONTROL_STATE_X11_PRESSED_HELD) { @@ -1754,13 +1795,13 @@ static void X11Toolkit_DrawButtonControl(SDL_ToolkitControlX11 *control) { X11_Xutf8DrawString(control->window->display, control->window->drawable, control->window->font_set, control->window->ctx, control->rect.x + button_control->text_rect.x, control->rect.y + button_control->text_rect.y, - button_control->data->text, button_control->str_sz); + text, button_control->str_sz); } else #endif { X11_XDrawString(control->window->display, control->window->drawable, control->window->ctx, control->rect.x + button_control->text_rect.x, control->rect.y + button_control->text_rect.y, - button_control->data->text, button_control->str_sz); + text, button_control->str_sz); } } @@ -1773,7 +1814,15 @@ static void X11Toolkit_OnButtonControlStateChange(SDL_ToolkitControlX11 *control } } -static void X11Toolkit_DestroyGenericControl(SDL_ToolkitControlX11 *control) { +static void X11Toolkit_DestroyButtonControl(SDL_ToolkitControlX11 *control) { +#ifdef HAVE_FRIBIDI_H + SDL_ToolkitButtonControlX11 *button_control; + + button_control = (SDL_ToolkitButtonControlX11 *)control; + if (button_control->free_text) { + SDL_free(button_control->text); + } +#endif SDL_free(control); } @@ -1793,7 +1842,7 @@ SDL_ToolkitControlX11 *X11Toolkit_CreateButtonControl(SDL_ToolkitWindowX11 *wind base_control->func_calc_size = X11Toolkit_CalculateButtonControl; base_control->func_draw = X11Toolkit_DrawButtonControl; base_control->func_on_state_change = X11Toolkit_OnButtonControlStateChange; - base_control->func_free = X11Toolkit_DestroyGenericControl; + base_control->func_free = X11Toolkit_DestroyButtonControl; base_control->func_on_scale_change = NULL; base_control->state = SDL_TOOLKIT_CONTROL_STATE_X11_NORMAL; base_control->selected = false; @@ -1810,8 +1859,23 @@ SDL_ToolkitControlX11 *X11Toolkit_CreateButtonControl(SDL_ToolkitWindowX11 *wind base_control->do_size = false; control->data = data; control->str_sz = SDL_strlen(control->data->text); +#ifdef HAVE_FRIBIDI_H + if (base_control->window->fribidi) { + control->text = SDL_FriBidi_Process(base_control->window->fribidi, (char *)control->data->text, control->str_sz, base_control->window->do_shaping, NULL); + if (control->text) { + control->free_text = true; + control->str_sz = SDL_strlen(control->text); + } else { + control->text = (char *)control->data->text; + control->free_text = false; + } + } else { + control->text = (char *)control->data->text; + control->free_text = false; + } +#endif control->cb = NULL; - X11Toolkit_GetTextWidthHeight(base_control->window, control->data->text, control->str_sz, &control->text_rect.w, &control->text_rect.h, &control->text_a, &text_d); + X11Toolkit_GetTextWidthHeight(base_control->window, control->data->text, control->str_sz, &control->text_rect.w, &control->text_rect.h, &control->text_a, &text_d, NULL); base_control->rect.w = SDL_TOOLKIT_X11_ELEMENT_PADDING_3 * 2 * window->iscale + control->text_rect.w; base_control->rect.h = SDL_TOOLKIT_X11_ELEMENT_PADDING_3 * 2 * window->iscale + control->text_rect.h; control->text_rect.x = control->text_rect.y = 0; @@ -1923,6 +1987,10 @@ void X11Toolkit_DestroyWindow(SDL_ToolkitWindowX11 *data) { } #endif +#ifdef HAVE_FRIBIDI_H + SDL_FriBidi_Destroy(data->fribidi); +#endif + SDL_free(data); } @@ -1940,20 +2008,27 @@ static int X11Toolkit_CountLinesOfText(const char *text) static void X11Toolkit_DrawLabelControl(SDL_ToolkitControlX11 *control) { SDL_ToolkitLabelControlX11 *label_control; int i; - + int x; + label_control = (SDL_ToolkitLabelControlX11 *)control; X11_XSetForeground(control->window->display, control->window->ctx, control->window->xcolor[SDL_MESSAGEBOX_COLOR_TEXT].pixel); for (i = 0; i < label_control->sz; i++) { + x = control->rect.x; +#ifdef HAVE_FRIBIDI_H + if (control->window->fribidi) { + x += label_control->x[i]; + } +#endif #ifdef X_HAVE_UTF8_STRING if (control->window->utf8) { X11_Xutf8DrawString(control->window->display, control->window->drawable, control->window->font_set, control->window->ctx, - control->rect.x, control->rect.y + label_control->y[i], + x, control->rect.y + label_control->y[i], label_control->lines[i], label_control->szs[i]); } else #endif { X11_XDrawString(control->window->display, control->window->drawable, control->window->ctx, - control->rect.x, control->rect.y + label_control->y[i], + x, control->rect.y + label_control->y[i], label_control->lines[i], label_control->szs[i]); } } @@ -1963,6 +2038,21 @@ static void X11Toolkit_DestroyLabelControl(SDL_ToolkitControlX11 *control) { SDL_ToolkitLabelControlX11 *label_control; label_control = (SDL_ToolkitLabelControlX11 *)control; +#ifdef HAVE_FRIBIDI_H + if (control->window->fribidi) { + int i; + + for (i = 0; i < label_control->sz; i++) { + if (label_control->free_lines[i]) { + SDL_free(label_control->lines[i]); + } + } + SDL_free(label_control->x); + SDL_free(label_control->free_lines); + SDL_free(label_control->w); + SDL_free(label_control->par_types); + } +#endif SDL_free(label_control->lines); SDL_free(label_control->szs); SDL_free(label_control->y); @@ -1971,20 +2061,26 @@ static void X11Toolkit_DestroyLabelControl(SDL_ToolkitControlX11 *control) { static void X11Toolkit_CalculateLabelControl(SDL_ToolkitControlX11 *base_control) { SDL_ToolkitLabelControlX11 *control; - int last_h; + int last_h; int ascent; int descent; + int font_h; int w; int h; int i; - +#ifdef HAVE_FRIBIDI_H + FriBidiParType first_ndn_dir; + int last_ndn; +#endif + last_h = 0; control = (SDL_ToolkitLabelControlX11 *)base_control; for (i = 0; i < control->sz; i++) { - X11Toolkit_GetTextWidthHeight(base_control->window, control->lines[i], control->szs[i], &w, &h, &ascent, &descent); + X11Toolkit_GetTextWidthHeight(base_control->window, control->lines[i], control->szs[i], &w, &h, &ascent, &descent, &font_h); + base_control->rect.w = SDL_max(base_control->rect.w, w); if (i > 0) { - control->y[i] = ascent + descent + control->y[i-1]; + control->y[i] = font_h + control->y[i-1]; } else { control->y[i] = ascent; } @@ -1992,12 +2088,56 @@ static void X11Toolkit_CalculateLabelControl(SDL_ToolkitControlX11 *base_control last_h = h; } base_control->rect.h = control->y[control->sz -1] + last_h; + +#ifdef HAVE_FRIBIDI_H + if (base_control->window->fribidi) { + first_ndn_dir = FRIBIDI_PAR_LTR; + for (i = 0; i < control->sz; i++) { + if (control->par_types[i] != FRIBIDI_PAR_ON) { + first_ndn_dir = control->par_types[i]; + } + } + + last_ndn = -1; + for (i = 0; i < control->sz; i++) { + switch (control->par_types[i]) { + case FRIBIDI_PAR_LTR: + control->x[i] = 0; + last_ndn = i; + break; + case FRIBIDI_PAR_RTL: + control->x[i] = base_control->rect.w - control->w[i]; + last_ndn = i; + break; + default: + if (last_ndn != -1) { + if (control->par_types[last_ndn] == FRIBIDI_PAR_RTL) { + control->x[i] = base_control->rect.w - control->w[i]; + } else { + control->x[i] = 0; + } + } else { + if (first_ndn_dir == FRIBIDI_PAR_RTL) { + control->x[i] = base_control->rect.w - control->w[i]; + } else { + control->x[i] = 0; + } + } + } + } + } +#endif } SDL_ToolkitControlX11 *X11Toolkit_CreateLabelControl(SDL_ToolkitWindowX11 *window, char *utf8) { SDL_ToolkitLabelControlX11 *control; SDL_ToolkitControlX11 *base_control; - int last_h; +#ifdef HAVE_FRIBIDI_H + FriBidiParType first_ndn_dir; + int last_ndn; +#endif + int font_h; + int last_h; int ascent; int descent; int i; @@ -2028,6 +2168,14 @@ SDL_ToolkitControlX11 *X11Toolkit_CreateLabelControl(SDL_ToolkitWindowX11 *windo control->lines = (char **)SDL_malloc(sizeof(char *) * control->sz); control->y = (int *)SDL_calloc(control->sz, sizeof(int)); control->szs = (size_t *)SDL_calloc(control->sz, sizeof(size_t)); +#ifdef HAVE_FRIBIDI_H + if (base_control->window->fribidi) { + control->x = (int *)SDL_calloc(control->sz, sizeof(int)); + control->free_lines = (bool *)SDL_calloc(control->sz, sizeof(bool)); + control->par_types = (FriBidiParType *)SDL_calloc(control->sz, sizeof(FriBidiParType)); + control->w = (int *)SDL_calloc(control->sz, sizeof(int)); + } +#endif last_h = 0; for (i = 0; i < control->sz; i++) { const char *lf = SDL_strchr(utf8, '\n'); @@ -2035,17 +2183,34 @@ SDL_ToolkitControlX11 *X11Toolkit_CreateLabelControl(SDL_ToolkitWindowX11 *windo int w; int h; - control->lines[i] = utf8; - X11Toolkit_GetTextWidthHeight(window, utf8, length, &w, &h, &ascent, &descent); +#ifdef HAVE_FRIBIDI_H + if (base_control->window->fribidi) { + control->lines[i] = SDL_FriBidi_Process(base_control->window->fribidi, utf8, length, base_control->window->do_shaping, &control->par_types[i]); + control->szs[i] = SDL_strlen(control->lines[i]); + control->free_lines[i] = true; + } else +#endif + { + control->lines[i] = utf8; + control->szs[i] = length; +#ifdef HAVE_FRIBIDI_H + control->free_lines[i] = false; +#endif + } + X11Toolkit_GetTextWidthHeight(window, control->lines[i], control->szs[i], &w, &h, &ascent, &descent, &font_h); +#ifdef HAVE_FRIBIDI_H + if (base_control->window->fribidi) { + control->w[i] = w; + } +#endif base_control->rect.w = SDL_max(base_control->rect.w, w); - control->szs[i] = length; - if (lf && (lf > utf8) && (lf[-1] == '\r')) { + if (lf && (lf > control->lines[i]) && (lf[-1] == '\r')) { control->szs[i]--; } if (i > 0) { - control->y[i] = ascent + descent + control->y[i-1]; + control->y[i] = font_h + control->y[i-1]; } else { control->y[i] = ascent; } @@ -2057,6 +2222,44 @@ SDL_ToolkitControlX11 *X11Toolkit_CreateLabelControl(SDL_ToolkitWindowX11 *windo } } base_control->rect.h = control->y[control->sz -1] + last_h; +#ifdef HAVE_FRIBIDI_H + if (base_control->window->fribidi) { + first_ndn_dir = FRIBIDI_PAR_LTR; + for (i = 0; i < control->sz; i++) { + if (control->par_types[i] != FRIBIDI_PAR_ON) { + first_ndn_dir = control->par_types[i]; + } + } + + last_ndn = -1; + for (i = 0; i < control->sz; i++) { + switch (control->par_types[i]) { + case FRIBIDI_PAR_LTR: + control->x[i] = 0; + last_ndn = i; + break; + case FRIBIDI_PAR_RTL: + control->x[i] = base_control->rect.w - control->w[i]; + last_ndn = i; + break; + default: + if (last_ndn != -1) { + if (control->par_types[last_ndn] == FRIBIDI_PAR_RTL) { + control->x[i] = base_control->rect.w - control->w[i]; + } else { + control->x[i] = 0; + } + } else { + if (first_ndn_dir == FRIBIDI_PAR_RTL) { + control->x[i] = base_control->rect.w - control->w[i]; + } else { + control->x[i] = 0; + } + } + } + } + } +#endif X11Toolkit_AddControlToWindow(window, base_control); diff --git a/src/video/x11/SDL_x11toolkit.h b/src/video/x11/SDL_x11toolkit.h index 74421a24ef..669a7cebf3 100644 --- a/src/video/x11/SDL_x11toolkit.h +++ b/src/video/x11/SDL_x11toolkit.h @@ -29,6 +29,9 @@ #include "SDL_x11settings.h" #include "SDL_x11toolkit.h" #include "xsettings-client.h" +#ifdef HAVE_FRIBIDI_H +#include "../../core/unix/SDL_fribidi.h" +#endif #ifdef SDL_VIDEO_DRIVER_X11 @@ -95,7 +98,6 @@ typedef struct SDL_ToolkitWindowX11 Bool shm_pixmap; #endif bool utf8; - /* Atoms */ Atom wm_protocols; Atom wm_delete_message; @@ -155,6 +157,12 @@ typedef struct SDL_ToolkitWindowX11 bool draw; bool close; long event_mask; + +#ifdef HAVE_FRIBIDI_H + /* BIDI engine */ + SDL_FriBidi *fribidi; + bool do_shaping; +#endif } SDL_ToolkitWindowX11; typedef enum SDL_ToolkitControlStateX11 diff --git a/test/testmessage.c b/test/testmessage.c index 02db7df1bb..d46012e255 100644 --- a/test/testmessage.c +++ b/test/testmessage.c @@ -213,6 +213,15 @@ int main(int argc, char *argv[]) quit(1); } + success = SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, + "Arabic (multi line)", + "سطر طويل جدًا من النص\nخط قصير\nسطر طويل للغاية من النص مذهل بشكل لا يصدق في اللغة العربية التي يتم التحدث بها في منطقة الشرق الأوسط وشمال أفريقيا", + NULL); + if (!success) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error Presenting MessageBox: %s", SDL_GetError()); + quit(1); + } + success = SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Cyrillic (Ukranian)", "Для запису людської мови використовуються системи письма.",