On Mon, Feb 16, 2015 at 08:56:33PM -0800, Eric Pruitt wrote:
> Quentin's changes to the dwm drw library are straightforward, and I'm
> considering porting st's font-fallback code to his Xft-supporting drw
> library, but I would also hate to put the effort into doing that only
> to have the API changed underneath me. I think the Quentin's patch
> would make a nice addition to the official dwm repo.

I went ahead and added fallback support on top of Quentin's patch, and
I've attached it to this email. Although I took the UTF-8 code and
general methodology from st, I also looked at [urvt's Xft font code][1]
(relevant part starts at line 1759), and my fallback font logic is
slightly different from st's as a result. I will push the Xft patch
combined with my fallback font changes to the wiki in the future after
I'm sure that the change doesn't introduce any major bugs. Constructive
criticism is welcome and appreciated.

  [1]: http://cvs.schmorp.de/rxvt-unicode/src/rxvtfont.C?view=markup

Eric
Author: Eric Pruitt, https://github.com/ericpruitt/
Description: This patch adds Unicode and fallback font support on top of the
Xft patch. The _drw_utf8* functions were written by Damian Okrasa for the MIT/X
licensed (http://opensource.org/licenses/MIT) terminal emulator st
(http://st.suckless.org/). While mostly functional, this patch still needs a
little work: the logic for character-grouping can probably be simplified, a
function needs to be created for calculating the drawn width of a string that
relies on fallback fonts and the TEXTW macro updated accordingly, and the
ability to manually choose fallback fonts will likely be implemented at some
point in the future.

--- drw.c       2015-02-21 11:00:34.570272492 -0800
+++ a/drw.c     2015-02-21 10:59:05.984593174 -0800
@@ -8,6 +8,54 @@
 #include "drw.h"
 #include "util.h"

+#define UTF_INVALID 0xFFFD
+#define UTF_SIZ 4
+
+static unsigned char utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
+static unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+static long utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
+static long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+
+static long
+_drw_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
+_drw_utf8validate(long *u, size_t i) {
+       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
+_drw_utf8decode(const char *c, long *u, size_t clen) {
+       size_t i, j, len, type;
+       long udecoded;
+
+       *u = UTF_INVALID;
+       if(!clen)
+               return 0;
+       udecoded = _drw_utf8decodebyte(c[0], &len);
+       if(!BETWEEN(len, 1, UTF_SIZ))
+               return 1;
+       for(i = 1, j = 1; i < clen && j < len; ++i, ++j) {
+               udecoded = (udecoded << 6) | _drw_utf8decodebyte(c[i], &type);
+               if(type != 0)
+                       return j;
+       }
+       if(j < len)
+               return 0;
+       *u = udecoded;
+       _drw_utf8validate(u, len);
+       return len;
+}
+
 Drw *
 drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) {
        Drw *drw = (Drw *)calloc(1, sizeof(Drw));
@@ -20,6 +68,7 @@
        drw->h = h;
        drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
        drw->gc = XCreateGC(dpy, root, 0, NULL);
+       drw->cachelen = 0;
        XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
        return drw;
 }
@@ -37,6 +86,12 @@

 void
 drw_free(Drw *drw) {
+       size_t i;
+       for (i = 0; i < drw->cachelen; i++) {
+               if (drw->fontcache[i] != drw->font) {
+                       drw_font_free(drw->dpy, drw->fontcache[i]);
+               }
+       }
        XFreePixmap(drw->dpy, drw->drawable);
        XFreeGC(drw->dpy, drw->gc);
        free(drw);
@@ -52,6 +107,8 @@
        if(!(font->xfont = XftFontOpenName(dpy,screen,fontname))
        && !(font->xfont = XftFontOpenName(dpy,screen,"fixed")))
                die("error, cannot load font: '%s'\n", fontname);
+       if (!(font->pattern = FcNameParse((FcChar8 *)fontname)))
+               die("error, cannot load font pattern for font: '%s'\n", fontname);
        font->ascent = font->xfont->ascent;
        font->descent = font->xfont->descent;
        font->h = font->ascent + font->descent;
@@ -94,8 +151,13 @@

 void
 drw_setfont(Drw *drw, Fnt *font) {
-       if(drw)
+       if(drw) {
                drw->font = font;
+               if (!drw->cachelen) {
+                       drw->fontcache[0] = font;
+                       drw->cachelen = 1;
+               }
+       }
 }

 void
@@ -119,39 +181,136 @@
 }

 void
-drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char
*text, int invert) {
-       char buf[256];
-       int i, tx, ty, th, len, olen;
+drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char
*text, int invert) {
+       char buf[1024];
+       int tx, ty, th;
        Extnts tex;
        Colormap cmap;
        Visual *vis;
        XftDraw *d;
+       Fnt *curfont, *iterfont, *nextfont;
+       size_t i, len;
+       int utf8strlen, utf8charlen;
+       long utf8codepoint = 0;
+       char *fontname;
+       const char *utf8str;
+       FcCharSet *fccharset;
+       FcPattern *fcpattern;
+       FcPattern *match;
+       XftResult result;
+       int fallback;
+       int charexists = 0;

        if(!drw || !drw->scheme)
                return;
        XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->fg->pix :
        drw->scheme->bg->pix);
        XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
-       if(!text || !drw->font)
+       if(!text || !drw->font)
                return;

-       olen = strlen(text);
-       drw_font_getexts(drw->font, text, olen, &tex);
-       th = drw->font->ascent + drw->font->descent;
-       ty = y + (h / 2) - (th / 2) + drw->font->ascent;
-       tx = x + (h / 2);
-       /* shorten text if necessary */
-       for(len = MIN(olen, sizeof buf); len && (tex.w > w - tex.h || w < tex.h); len--)
-               drw_font_getexts(drw->font, text, len, &tex);
-       if(!len)
-               return;
-       memcpy(buf, text, len);
-       if(len < olen)
-               for(i = len; i && i > len - 3; buf[--i] = '.');
+       if (!drw->cachelen) {
+               drw->fontcache[0] = drw->font;
+               drw->cachelen = 1;
+       }

        cmap = DefaultColormap(drw->dpy, drw->screen);
        vis = DefaultVisual(drw->dpy, drw->screen);
        d = XftDrawCreate(drw->dpy, drw->drawable, vis, cmap);
- XftDrawStringUtf8(d, invert ? &drw->scheme->bg->rgb : &drw->scheme->fg->rgb,
drw->font->xfont, tx, ty, (XftChar8 *)buf, len);
+       curfont = drw->font;
+
+       while (1) {
+               utf8strlen = 0;
+               utf8str = text;
+               nextfont = NULL;
+
+               while (*text) {
+                       utf8charlen = _drw_utf8decode(text, &utf8codepoint, UTF_SIZ);
+                       fallback = 1;
+                       for (i = 0; i < drw->cachelen; i++) {
+                               iterfont = drw->fontcache[i];
+ charexists = charexists || XftCharExists(drw->dpy, iterfont->xfont,
utf8codepoint);
+                               if (charexists) {
+                                       if (iterfont == curfont) {
+                                               utf8strlen += utf8charlen;
+                                               text += utf8charlen;
+                                               fallback = 0;
+                                       } else {
+                                               nextfont = iterfont;
+                                       }
+
+                                       break;
+                               }
+                       }
+
+                       if (fallback || (nextfont && nextfont != curfont)) {
+                               break;
+                       }
+
+                       charexists = 0;
+               }
+
+               if (utf8strlen) {
+                       drw_font_getexts(curfont, utf8str, utf8strlen, &tex);
+                       /* shorten text if necessary */
+ for(len = MIN(utf8strlen, sizeof buf); len && (tex.w > w - tex.h || w <
tex.h); len--)
+                               drw_font_getexts(curfont, utf8str, len, &tex);
+                       if(!len)
+                               break;
+                       memcpy(buf, utf8str, len);
+                       buf[len] = '\0';
+                       if(len < utf8strlen)
+                               for(i = len; i && i > len - 3; buf[--i] = '.');
+
+                       th = curfont->ascent + curfont->descent;
+                       ty = y + (h / 2) - (th / 2) + curfont->ascent;
+                       tx = x + (h / 2);
+
+ XftDrawStringUtf8(d, invert ? &drw->scheme->bg->rgb : &drw->scheme->fg->rgb,
curfont->xfont, tx, ty, (XftChar8 *)buf, len);
+                       x += tex.w;
+                       w -= tex.w;
+               }
+
+               if (!*text) {
+                       break;
+               } else if (nextfont) {
+                       curfont = nextfont;
+                       charexists = 0;
+               } else {
+                       // Regardless of whether or not a fallback font is found, the
+                       // character must be drawn.
+                       charexists = 1;
+
+                       if (drw->cachelen >= DRW_FONT_CACHE_SIZE) {
+                               continue;
+                       }
+
+                       fccharset = FcCharSetCreate();
+                       FcCharSetAddChar(fccharset, utf8codepoint);
+
+                       fcpattern = FcPatternDuplicate(drw->font->pattern);
+                       FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
+
+                       FcConfigSubstitute(0, fcpattern, FcMatchPattern);
+                       FcDefaultSubstitute(fcpattern);
+                       match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
+                       FcPatternDestroy(fcpattern);
+
+                       if (match) {
+                               FcPatternDel(match, FC_CHARSET);
+                               fontname = (char *)FcNameUnparse(match);
+                               FcPatternDestroy(match);
+
+ curfont = drw_font_create(drw->dpy, drw->screen, fontname);
+ if (XftCharExists(drw->dpy, curfont->xfont, utf8codepoint)) {
+                                       drw->fontcache[drw->cachelen++] = curfont;
+                               } else {
+                                       drw_font_free(drw->dpy, curfont);
+                                       curfont = drw->font;
+                               }
+                       }
+               }
+       }
+
        XftDrawDestroy(d);
 }

--- drw.h.old   2015-02-21 11:07:03.052095420 -0800
+++ drw.h       2015-02-21 11:09:03.444941484 -0800
@@ -1,4 +1,5 @@
 /* See LICENSE file for copyright and license details. */
+#define DRW_FONT_CACHE_SIZE 32

 typedef struct {
        unsigned long pix;
@@ -15,6 +16,7 @@
        int descent;
        unsigned int h;
        XftFont *xfont;
+       FcPattern *pattern;
 } Fnt;

 typedef struct {
@@ -32,6 +34,8 @@
        GC gc;
        ClrScheme *scheme;
        Fnt *font;
+       size_t cachelen;
+       Fnt *fontcache[DRW_FONT_CACHE_SIZE];
 } Drw;

 typedef struct {
diff --git a/util.h b/util.h
index 033700c..f7ce721 100644
--- a/util.h
+++ b/util.h
@@ -2,5 +2,6 @@

 #define MAX(A, B)               ((A) > (B) ? (A) : (B))
 #define MIN(A, B)               ((A) < (B) ? (A) : (B))
+#define BETWEEN(X, A, B)        ((A) <= (X) && (X) <= (B))

 void die(const char *errstr, ...);

Reply via email to