Search code examples
c++winapisdlarabicsdl-ttf

How do I join up (Arabic) glyphs with SDL-TTF v2.20?


SDL_TTF 2.20.0 (with Harfbuzz enabled) introduced TTF_SetFontDirection() and TTF_SetFontScriptName() "for additional control over fonts using HarfBuzz".

I am trying to render some Arabic text, and I have set the font direction to RTL and the script name to "arSA\0", but the glyphs aren't joining up correctly.

Both TTF_SetFontDirection(ttf_font, TTF_DIRECTION_RTL) and TTF_SetFontScriptName(ttf_font, "arSA\0") return 0, which indicates success.

This is how I expected the text to appear:

joined

And this is how it is appearing instead:

enter image description here

Can anybody tell me what I'm doing wrong?

Edit: Minimal reproducible example: (Adapted from https://github.com/aminosbh/sdl2-ttf-sample/blob/master/src/main.c)

#include <SDL.h>
#include <SDL_ttf.h>
#include <string>

int main(int argc, char* argv[])
{
    // Unused argc, argv
    (void)argc;
    (void)argv;

    // Initialize SDL2
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        printf("SDL2 could not be initialized!\n"
            "SDL2 Error: %s\n", SDL_GetError());
        return 0;
    }

    // Initialize SDL2_ttf
    TTF_Init();

    // Create window
    SDL_Window* window = SDL_CreateWindow("SDL2_ttf sample",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        SCREEN_WIDTH, SCREEN_HEIGHT,
        SDL_WINDOW_SHOWN);
    if (!window)
    {
        printf("Window could not be created!\n"
            "SDL_Error: %s\n", SDL_GetError());
    }
    else
    {
        // Create renderer
        SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
        if (!renderer)
        {
            printf("Renderer could not be created!\n"
                "SDL_Error: %s\n", SDL_GetError());
        }
        else
        {
            // Declare rect of square
            SDL_Rect squareRect;

            // Square dimensions: Half of the min(SCREEN_WIDTH, SCREEN_HEIGHT)
            squareRect.w = MIN(SCREEN_WIDTH, SCREEN_HEIGHT) / 2;
            squareRect.h = MIN(SCREEN_WIDTH, SCREEN_HEIGHT) / 2;

            // Square position: In the middle of the screen
            squareRect.x = SCREEN_WIDTH / 2 - squareRect.w / 2;
            squareRect.y = SCREEN_HEIGHT / 2 - squareRect.h / 2;

            TTF_Font* font = TTF_OpenFont(FONT_PATH, 40);
            if (!font) {
                printf("Unable to load font: '%s'!\n"
                    "SDL2_ttf Error: %s\n", FONT_PATH, TTF_GetError());
                return 0;
            }
            const auto fontDirectionSuccess = TTF_SetFontDirection(font, TTF_DIRECTION_RTL);
            const auto fontScriptNameSuccess = TTF_SetFontScriptName(font, "arSA\0");

            SDL_Color textColor = { 0x00, 0x00, 0x00, 0xFF };
            SDL_Color textBackgroundColor = { 0xFF, 0xFF, 0xFF, 0xFF };
            SDL_Texture* text = NULL;
            SDL_Rect textRect;




            // Arabic text to display
            const wchar_t * wstr = L"كسول الزنجبيل القط";

            // Convert unicode to multibyte
            int wstr_len = (int)wcslen(wstr);
            int num_chars = WideCharToMultiByte(CP_UTF8, 0, wstr, wstr_len, NULL, 0, NULL, NULL);
            CHAR* strTo = (CHAR*)malloc((num_chars + 1) * sizeof(CHAR));
            if (strTo)
            {
                WideCharToMultiByte(CP_UTF8, 0, wstr, wstr_len, strTo, num_chars, NULL, NULL);
                strTo[num_chars] = '\0';
            }


            // Render text to surface
            SDL_Surface* textSurface = TTF_RenderUTF8_Blended(font, strTo, textColor);
            if (!textSurface) {
                printf("Unable to render text surface!\n"
                    "SDL2_ttf Error: %s\n", TTF_GetError());
            }
            else {
                // Create texture from surface pixels
                text = SDL_CreateTextureFromSurface(renderer, textSurface);
                if (!text) {
                    printf("Unable to create texture from rendered text!\n"
                        "SDL2 Error: %s\n", SDL_GetError());
                    return 0;
                }

                // Get text dimensions
                textRect.w = textSurface->w;
                textRect.h = textSurface->h;

                SDL_FreeSurface(textSurface);
            }

            textRect.x = (SCREEN_WIDTH - textRect.w) / 2;
            textRect.y = squareRect.y - textRect.h - 10;


            // Event loop exit flag
            bool quit = false;

            // Event loop
            while (!quit)
            {
                SDL_Event e;

                // Wait indefinitely for the next available event
                SDL_WaitEvent(&e);

                // User requests quit
                if (e.type == SDL_QUIT)
                {
                    quit = true;
                }

                // Initialize renderer color white for the background
                SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);

                // Clear screen
                SDL_RenderClear(renderer);

                // Draw text
                SDL_RenderCopy(renderer, text, NULL, &textRect);

                // Update screen
                SDL_RenderPresent(renderer);
            }

            // Destroy renderer
            SDL_DestroyRenderer(renderer);
        }

        // Destroy window
        SDL_DestroyWindow(window);
    }

    // Quit SDL2_ttf
    TTF_Quit();

    // Quit SDL
    SDL_Quit();

    return 0;
}

Edit 2: I've replaced

TTF_SetFontDirection(font, TTF_DIRECTION_RTL);
TTF_SetFontScriptName(font, "arSA\0");

with the depracated:

TTF_SetDirection(HB_DIRECTION_RTL);
TTF_SetScript(HB_SCRIPT_ARABIC);

And everything works fine. I suspect "arSA\0" might be wrong, but the only documentation I can find says it should be a "null-terminated string of exactly 4 characters", with no indication or example of which 4 characters.


Solution

  • Instead of "arSA", you should use "Arab".

    See https://github.com/harfbuzz/harfbuzz/blob/7a219ca9f0f8140906cb7fd3b879b5bf5259badc/src/hb-common.h#L514

    Credits to commenters "1.8e9-where's-my-share m" & "skink".

    I'm adding the answer here so you can accept is as answered.