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, ...);