Browse Source

X11TK: Add Arabic/Hebrew/Farsi support by increasing font size slightly and using FriBidi. (#14134)

eafton 4 days ago
parent
commit
c2429e85ec

+ 3 - 1
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})

+ 29 - 0
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 $<TARGET_PROPERTY:PkgConfig::PC_FRIBIDI,INTERFACE_INCLUDE_DIRECTORIES>)
+      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")

+ 2 - 0
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

+ 160 - 0
src/core/unix/SDL_fribidi.c

@@ -0,0 +1,160 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
+
+  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 <fribidi.h>
+
+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

+ 58 - 0
src/core/unix/SDL_fribidi.h

@@ -0,0 +1,58 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
+
+  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 <fribidi.h>
+
+#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_

+ 2 - 0
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))

+ 231 - 28
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);
     

+ 9 - 1
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

+ 9 - 0
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)",
                                        "Для запису людської мови використовуються системи письма.",