sync upstream xft code, add free functions for schemes and colors - libsl - draw back-ends for dwm, dmenu, etc
 (HTM) git clone git://git.codemadness.org/libsl
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
 (DIR) commit 656de2a0c190e435c4d6e0948a8ed6103c4e5e9c
 (DIR) parent a531daff4607ae443d695f81880a931e1e519be3
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Sat, 27 Sep 2025 12:06:32 +0200
       
       sync upstream xft code, add free functions for schemes and colors
       
       Diffstat:
         M xft/drw.c                           |     158 ++++++++++++++++---------------
         M xft/drw.h                           |       2 ++
       
       2 files changed, 84 insertions(+), 76 deletions(-)
       ---
 (DIR) diff --git a/xft/drw.c b/xft/drw.c
       @@ -9,54 +9,40 @@
        #include "util.h"
        
        #define UTF_INVALID 0xFFFD
       -#define UTF_SIZ     4
        
       -static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
       -static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
       -static const long utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
       -static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
       -
       -static long
       -utf8decodebyte(const char c, size_t *i)
       -{
       -        for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
       -                if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
       -                        return (unsigned char)c & ~utfmask[*i];
       -        return 0;
       -}
       -
       -static size_t
       -utf8validate(long *u, size_t i)
       +static int
       +utf8decode(const char *s_in, long *u, int *err)
        {
       -        if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
       -                *u = UTF_INVALID;
       -        for (i = 1; *u > utfmax[i]; ++i)
       -                ;
       -        return i;
       -}
       -
       -static size_t
       -utf8decode(const char *c, long *u, size_t clen)
       -{
       -        size_t i, j, len, type;
       -        long udecoded;
       -
       +        static const unsigned char lens[] = {
       +                /* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       +                /* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0,  /* invalid */
       +                /* 110XX */ 2, 2, 2, 2,
       +                /* 1110X */ 3, 3,
       +                /* 11110 */ 4,
       +                /* 11111 */ 0,  /* invalid */
       +        };
       +        static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 };
       +        static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 };
       +
       +        const unsigned char *s = (const unsigned char *)s_in;
       +        int len = lens[*s >> 3];
                *u = UTF_INVALID;
       -        if (!clen)
       -                return 0;
       -        udecoded = utf8decodebyte(c[0], &len);
       -        if (!BETWEEN(len, 1, UTF_SIZ))
       +        *err = 1;
       +        if (len == 0)
                        return 1;
       -        for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
       -                udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
       -                if (type)
       -                        return j;
       +
       +        long cp = s[0] & leading_mask[len - 1];
       +        for (int i = 1; i < len; ++i) {
       +                if (s[i] == '\0' || (s[i] & 0xC0) != 0x80)
       +                        return i;
       +                cp = (cp << 6) | (s[i] & 0x3F);
                }
       -        if (j < len)
       -                return 0;
       -        *u = udecoded;
       -        utf8validate(u, len);
       +        /* out of range, surrogate, overlong encoding */
       +        if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1])
       +                return len;
        
       +        *err = 0;
       +        *u = cp;
                return len;
        }
        
       @@ -133,19 +119,6 @@ xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
                        die("no font specified.");
                }
        
       -        /* Do not allow using color fonts. This is a workaround for a BadLength
       -         * error from Xft with color glyphs. Modelled on the Xterm workaround. See
       -         * https://bugzilla.redhat.com/show_bug.cgi?id=1498269
       -         * https://lists.suckless.org/dev/1701/30932.html
       -         * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349
       -         * and lots more all over the internet.
       -         */
       -        FcBool iscol;
       -        if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) {
       -                XftFontClose(drw->dpy, xfont);
       -                return NULL;
       -        }
       -
                font = ecalloc(1, sizeof(Fnt));
                font->xfont = xfont;
                font->pattern = pattern;
       @@ -205,8 +178,7 @@ drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
                        die("error, cannot allocate color '%s'", clrname);
        }
        
       -/* Wrapper to create color schemes. The caller has to call free(3) on the
       - * returned color scheme when done using it. */
       +/* Create color schemes. */
        Clr *
        drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
        {
       @@ -214,7 +186,7 @@ drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
                Clr *ret;
        
                /* need at least two colors for a scheme */
       -        if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
       +        if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(Clr))))
                        return NULL;
        
                for (i = 0; i < clrcount; i++)
       @@ -223,6 +195,29 @@ drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
        }
        
        void
       +drw_clr_free(Drw *drw, Clr *c)
       +{
       +        if (!drw || !c)
       +                return;
       +
       +        /* c is typedef XftColor Clr */
       +        XftColorFree(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
       +                     DefaultColormap(drw->dpy, drw->screen), c);
       +}
       +
       +void
       +drw_scm_free(Drw *drw, Clr *scm, size_t clrcount)
       +{
       +        size_t i;
       +
       +        if (!drw || !scm)
       +                return;
       +
       +        for (i = 0; i < clrcount; i++)
       +                drw_clr_free(drw, &scm[i]);
       +}
       +
       +void
        drw_setfontset(Drw *drw, Fnt *set)
        {
                if (drw)
       @@ -251,11 +246,11 @@ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int
        int
        drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
        {
       -        int i, ty, ellipsis_x = 0;
       -        unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len;
       +        int ty, ellipsis_x = 0;
       +        unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1;
                XftDraw *d = NULL;
                Fnt *usedfont, *curfont, *nextfont;
       -        int utf8strlen, utf8charlen, render = x || y || w || h;
       +        int utf8strlen, utf8charlen, utf8err, render = x || y || w || h;
                long utf8codepoint = 0;
                const char *utf8str;
                FcCharSet *fccharset;
       @@ -264,9 +259,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
                XftResult result;
                int charexists = 0, overflow = 0;
                /* keep track of a couple codepoints for which we have no match. */
       -        enum { nomatches_len = 64 };
       -        static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches;
       -        static unsigned int ellipsis_width = 0;
       +        static unsigned int nomatches[128], ellipsis_width, invalid_width;
       +        static const char invalid[] = "�";
        
                if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
                        return 0;
       @@ -276,6 +270,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
                } else {
                        XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
                        XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
       +                if (w < lpad)
       +                        return x + w;
                        d = XftDrawCreate(drw->dpy, drw->drawable,
                                          DefaultVisual(drw->dpy, drw->screen),
                                          DefaultColormap(drw->dpy, drw->screen));
       @@ -286,12 +282,14 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
                usedfont = drw->fonts;
                if (!ellipsis_width && render)
                        ellipsis_width = drw_fontset_getwidth(drw, "...");
       +        if (!invalid_width && render)
       +                invalid_width = drw_fontset_getwidth(drw, invalid);
                while (1) {
       -                ew = ellipsis_len = utf8strlen = 0;
       +                ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0;
                        utf8str = text;
                        nextfont = NULL;
                        while (*text) {
       -                        utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
       +                        utf8charlen = utf8decode(text, &utf8codepoint, &utf8err);
                                for (curfont = drw->fonts; curfont; curfont = curfont->next) {
                                        charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
                                        if (charexists) {
       @@ -313,9 +311,9 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
                                                        else
                                                                utf8strlen = ellipsis_len;
                                                } else if (curfont == usedfont) {
       -                                                utf8strlen += utf8charlen;
                                                        text += utf8charlen;
       -                                                ew += tmpw;
       +                                                utf8strlen += utf8err ? 0 : utf8charlen;
       +                                                ew += utf8err ? 0 : tmpw;
                                                } else {
                                                        nextfont = curfont;
                                                }
       @@ -323,7 +321,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
                                        }
                                }
        
       -                        if (overflow || !charexists || nextfont)
       +                        if (overflow || !charexists || nextfont || utf8err)
                                        break;
                                else
                                        charexists = 0;
       @@ -338,6 +336,12 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
                                x += ew;
                                w -= ew;
                        }
       +                if (utf8err && (!render || invalid_width < w)) {
       +                        if (render)
       +                                drw_text(drw, x, y, w, h, 0, invalid, invert);
       +                        x += invalid_width;
       +                        w -= invalid_width;
       +                }
                        if (render && overflow)
                                drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert);
        
       @@ -351,11 +355,14 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
                                 * character must be drawn. */
                                charexists = 1;
        
       -                        for (i = 0; i < nomatches_len; ++i) {
       -                                /* avoid calling XftFontMatch if we know we won't find a match */
       -                                if (utf8codepoint == nomatches.codepoint[i])
       -                                        goto no_match;
       -                        }
       +                        hash = (unsigned int)utf8codepoint;
       +                        hash = ((hash >> 16) ^ hash) * 0x21F0AAAD;
       +                        hash = ((hash >> 15) ^ hash) * 0xD35A2D97;
       +                        h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches);
       +                        h1 = (hash >> 17) % LENGTH(nomatches);
       +                        /* avoid expensive XftFontMatch call when we know we won't find a match */
       +                        if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint)
       +                                goto no_match;
        
                                fccharset = FcCharSetCreate();
                                FcCharSetAddChar(fccharset, utf8codepoint);
       @@ -368,7 +375,6 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
                                fcpattern = FcPatternDuplicate(drw->fonts->pattern);
                                FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
                                FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
       -                        FcPatternAddBool(fcpattern, FC_COLOR, FcFalse);
        
                                FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
                                FcDefaultSubstitute(fcpattern);
       @@ -385,7 +391,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
                                                curfont->next = usedfont;
                                        } else {
                                                xfont_free(usedfont);
       -                                        nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint;
       +                                        nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint;
        no_match:
                                                usedfont = drw->fonts;
                                        }
 (DIR) diff --git a/xft/drw.h b/xft/drw.h
       @@ -40,7 +40,9 @@ void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned in
        
        /* Colorscheme abstraction */
        void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
       +void drw_clr_free(Drw *drw, Clr *c);
        Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
       +void drw_scm_free(Drw *drw, Clr *scm, size_t clrcount);
        
        /* Cursor abstraction */
        Cur *drw_cur_create(Drw *drw, int shape);