Search code examples
fontsxlibfreetypefallback

how to use fallback font to draw text with xlib/libxft?


How to use fallback fonts to draw text with xlib/libxft ? For example, when drawing a string, the current font does not contain certain characters, but another font does. So, how to use fallback fonts to draw characters?


Solution

  • This way work, but low efficiency:

    #include <stdio.h>
    #include <stdint.h>
    #include <locale.h>
    #include <X11/Xlib.h>
    #include <X11/Xft/Xft.h>
    
    #define ARRAY_NUM(a) (sizeof(a)/sizeof(a[0]))
    #define FONT_NAMES (const char *[]){"DejaVu Sans", "Noto Sans Devanagari", "Noto Color Emoji"}
    #define FONT_N ARRAY_NUM(FONT_NAMES)
    
    Display *display=NULL;
    Window root;
    int screen;
    Visual *visual=NULL;
    Colormap colormap;
    XftFont *fonts[FONT_N];
    XftColor color;
    
    int get_utf8_codepoint(const char *str, uint32_t *codepoint)
    {
        const uint8_t *p=(const uint8_t*)str;
        int len=0;
        
        if(*p < 0x80) // 單字節字符
            len=1, *codepoint=*p;
        else if((*p>>5) == 0x06) // 雙字節字符
            len=2, *codepoint=(*p & 0x1F)<<6 | (*(p+1) & 0x3F);
        else if((*p>>4) == 0x0E) // 三字節字符
            len=3, *codepoint=(*p & 0x0F)<<12 | (*(p+1) & 0x3F)<<6 | (*(p+2) & 0x3F);
        else if((*p>>3) == 0x1E) // 四字節字符
            len=4, *codepoint=(*p & 0x07)<<18 | (*(p+1) & 0x3F)<<12 | (*(p+2) & 0x3F)<<6 | (*(p+3) & 0x3F);
        else // 非utf8編碼字符
            len=0;
    
        return len;
    }
    
    void load_fonts(void)
    {
        for(int i=0; i<FONT_N; i++)
            fonts[i]=XftFontOpenName(display, screen, FONT_NAMES[i]);
    }
    
    void close_fonts(void)
    {
        for(int i=0; i<FONT_N; i++)
            XftFontClose(display, fonts[i]);
    }
    
    void draw_utf8_string(Drawable drawable, const char *s, int x, int y)
    {
        XGlyphInfo glyphInfo;
        int len=0;
        int32_t codepoint;
    
        XftDraw *xftDraw=XftDrawCreate(display, drawable, visual, colormap);
        while(*s)
        {
            len=get_utf8_codepoint(s, &codepoint);
            for(int i=0; i<FONT_N; i++)
                if (XftCharExists(display, fonts[i], codepoint)) {
                    XftDrawStringUtf8(xftDraw, &color, fonts[i], x, y, (XftChar8*)s, len);
                    XftTextExtentsUtf8(display, fonts[i], (XftChar8*)s, len, &glyphInfo);
                    x += glyphInfo.xOff;
                    break;
                }
            s+=len;
        }
        XftDrawDestroy(xftDraw);
    }
    
    int main(void)
    {
        const char *s="Hello🔋,你好🥲,नमस्ते🤡.";
        display=XOpenDisplay(NULL);
        root=DefaultRootWindow(display);
        screen=DefaultScreen(display);
        visual=DefaultVisual(display, screen);
        colormap=DefaultColormap(display, screen);
    
        if(!setlocale(LC_ALL, "") || !XSupportsLocale())
            fprintf(stderr, "warning: no locale support\n");
        XftColorAllocName(display, visual, colormap, "yellow", &color);
        load_fonts();
        draw_utf8_string(root, s, 100, 100);
        close_fonts();
        XCloseDisplay(display);
    
        return 0;
    }