drw.c (10980B)
1 /* 2 * Copyright (C) Raymond Cole <rc@wolog.xyz> 3 * 4 * Licensed under the GNU General Public License; either version 3 of 5 * the License, or (at your option) any later version. See the LICENSE 6 * file for details. 7 * 8 * Portions of the code originally belong to dwm as developed by Anselm 9 * R. Garbe et al. See LICENSE-dwm for the copyright and license details 10 * of those portions. 11 */ 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include <X11/Xlib.h> 16 #include <X11/Xft/Xft.h> 17 18 #include "drw.h" 19 #include "util.h" 20 21 #define UTF_INVALID 0xFFFD 22 #define UTF_SIZ 4 23 24 static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 25 static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 26 static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 27 static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 28 29 static long 30 utf8decodebyte(const char c, size_t *i) 31 { 32 for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) 33 if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) 34 return (unsigned char)c & ~utfmask[*i]; 35 return 0; 36 } 37 38 static size_t 39 utf8validate(long *u, size_t i) 40 { 41 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 42 *u = UTF_INVALID; 43 for (i = 1; *u > utfmax[i]; ++i) 44 ; 45 return i; 46 } 47 48 static size_t 49 utf8decode(const char *c, long *u, size_t clen) 50 { 51 size_t i, j, len, type; 52 long udecoded; 53 54 *u = UTF_INVALID; 55 if (!clen) 56 return 0; 57 udecoded = utf8decodebyte(c[0], &len); 58 if (!BETWEEN(len, 1, UTF_SIZ)) 59 return 1; 60 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 61 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 62 if (type) 63 return j; 64 } 65 if (j < len) 66 return 0; 67 *u = udecoded; 68 utf8validate(u, len); 69 70 return len; 71 } 72 73 Drw * 74 drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) 75 { 76 Drw *drw; 77 78 if (!(drw = malloc(sizeof(*drw)))) 79 return NULL; 80 drw->dpy = dpy; 81 drw->screen = screen; 82 drw->root = root; 83 drw->w = w; 84 drw->h = h; 85 drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); 86 drw->gc = XCreateGC(dpy, root, 0, NULL); 87 XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); 88 89 return drw; 90 } 91 92 void 93 drw_resize(Drw *drw, unsigned int w, unsigned int h) 94 { 95 if (!drw) 96 return; 97 98 drw->w = w; 99 drw->h = h; 100 if (drw->drawable) 101 XFreePixmap(drw->dpy, drw->drawable); 102 drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); 103 } 104 105 void 106 drw_free(Drw *drw) 107 { 108 XFreePixmap(drw->dpy, drw->drawable); 109 XFreeGC(drw->dpy, drw->gc); 110 drw_fontset_free(drw->fonts); 111 free(drw); 112 } 113 114 /* This function is an implementation detail. Library users should use 115 * drw_fontset_create instead. 116 */ 117 static Fnt * 118 xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) 119 { 120 Fnt *font; 121 XftFont *xfont = NULL; 122 FcPattern *pattern = NULL; 123 124 if (fontname) { 125 if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { 126 fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); 127 return NULL; 128 } 129 if (!(pattern = FcNameParse((FcChar8 *) fontname))) { 130 fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); 131 XftFontClose(drw->dpy, xfont); 132 return NULL; 133 } 134 } else if (fontpattern) { 135 if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { 136 fprintf(stderr, "error, cannot load font from pattern.\n"); 137 return NULL; 138 } 139 } else { 140 warn("no font specified"); 141 return NULL; 142 } 143 144 if (!(font = malloc(sizeof(*font)))) { 145 warn("malloc:"); 146 return NULL; 147 } 148 font->dpy = drw->dpy; 149 font->h = xfont->ascent + xfont->descent; 150 font->xfont = xfont; 151 font->pattern = pattern; 152 font->next = NULL; 153 154 return font; 155 } 156 157 static void 158 xfont_free(Fnt *font) 159 { 160 if (!font) 161 return; 162 if (font->pattern) 163 FcPatternDestroy(font->pattern); 164 XftFontClose(font->dpy, font->xfont); 165 free(font); 166 } 167 168 Fnt * 169 drw_fontset_create(Drw* drw, const char *const fonts[], size_t fontcount) 170 { 171 Fnt *cur, *ret = NULL; 172 size_t i; 173 174 if (!drw || !fonts) 175 return NULL; 176 177 for (i = 1; i <= fontcount; i++) { 178 if (!(cur = xfont_create(drw, fonts[fontcount - i], NULL))) { 179 drw_fontset_free(ret); 180 return NULL; 181 } 182 cur->next = ret; 183 ret = cur; 184 } 185 return (drw->fonts = ret); 186 } 187 188 void 189 drw_fontset_free(Fnt *font) 190 { 191 if (font) { 192 drw_fontset_free(font->next); 193 xfont_free(font); 194 } 195 } 196 197 Clr * 198 drw_clr_create(Drw *drw, Clr *dest, const char *clrname) 199 { 200 if (!drw || !dest || !clrname) 201 return NULL; 202 203 if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), 204 DefaultColormap(drw->dpy, drw->screen), 205 clrname, dest)) { 206 warn("cannot allocate color `%s'", clrname); 207 return NULL; 208 } 209 return dest; 210 } 211 212 /* Wrapper to create color schemes. The caller has to call free(3) on the 213 * returned color scheme when done using it. */ 214 Scm * 215 drw_scm_create(Drw *drw, const char *fg, const char *bg) 216 { 217 Scm *ret; 218 219 if (!drw || !fg || !bg) 220 return NULL; 221 222 if (!(ret = malloc(sizeof(*ret)))) { 223 warn("malloc:"); 224 return NULL; 225 } 226 227 if (!drw_clr_create(drw, &ret->fg, fg) 228 || !drw_clr_create(drw, &ret->bg, bg)) { 229 free(ret); 230 return NULL; 231 } 232 return ret; 233 } 234 235 void 236 drw_setfontset(Drw *drw, Fnt *set) 237 { 238 if (drw) 239 drw->fonts = set; 240 } 241 242 void 243 drw_setscheme(Drw *drw, Scm *scm) 244 { 245 if (drw) 246 drw->scheme = scm; 247 } 248 249 void 250 drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) 251 { 252 if (!drw || !drw->scheme) 253 return; 254 XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->bg.pixel : drw->scheme->fg.pixel); 255 if (filled) 256 XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 257 else 258 XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); 259 } 260 261 int 262 drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) 263 { 264 int i, ty, ellipsis_x = 0; 265 unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len; 266 XftDraw *d = NULL; 267 Fnt *usedfont, *curfont, *nextfont; 268 int utf8strlen, utf8charlen, render = x || y || w || h; 269 long utf8codepoint = 0; 270 const char *utf8str; 271 FcCharSet *fccharset; 272 FcPattern *fcpattern; 273 FcPattern *match; 274 XftResult result; 275 int charexists = 0, overflow = 0; 276 /* keep track of a couple codepoints for which we have no match. */ 277 enum { nomatches_len = 64 }; 278 static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; 279 static unsigned int ellipsis_width = 0; 280 281 if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) 282 return 0; 283 284 if (!render) { 285 w = invert ? invert : ~invert; 286 } else { 287 XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->fg.pixel : drw->scheme->bg.pixel); 288 XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 289 if (w < lpad) /* avoid underflowing w */ 290 return x + w; 291 d = XftDrawCreate(drw->dpy, drw->drawable, 292 DefaultVisual(drw->dpy, drw->screen), 293 DefaultColormap(drw->dpy, drw->screen)); 294 x += lpad; 295 w -= lpad; 296 } 297 298 usedfont = drw->fonts; 299 if (!ellipsis_width && render) 300 ellipsis_width = drw_fontset_getwidth(drw, "..."); 301 while (1) { 302 ew = ellipsis_len = utf8strlen = 0; 303 utf8str = text; 304 nextfont = NULL; 305 while (*text) { 306 utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); 307 for (curfont = drw->fonts; curfont; curfont = curfont->next) { 308 charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); 309 if (charexists) { 310 drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); 311 if (ew + ellipsis_width <= w) { 312 /* keep track where the ellipsis still fits */ 313 ellipsis_x = x + ew; 314 ellipsis_w = w - ew; 315 ellipsis_len = utf8strlen; 316 } 317 318 if (ew + tmpw > w) { 319 overflow = 1; 320 /* called from drw_fontset_getwidth_clamp(): 321 * it wants the width AFTER the overflow 322 */ 323 if (!render) 324 x += tmpw; 325 else 326 utf8strlen = ellipsis_len; 327 } else if (curfont == usedfont) { 328 utf8strlen += utf8charlen; 329 text += utf8charlen; 330 ew += tmpw; 331 } else { 332 nextfont = curfont; 333 } 334 break; 335 } 336 } 337 338 if (overflow || !charexists || nextfont) 339 break; 340 else 341 charexists = 0; 342 } 343 344 if (utf8strlen) { 345 if (render) { 346 ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; 347 XftDrawStringUtf8(d, invert ? &drw->scheme->bg : &drw->scheme->fg, 348 usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); 349 } 350 x += ew; 351 w -= ew; 352 } 353 if (render && overflow) 354 drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); 355 356 if (!*text || overflow) { 357 break; 358 } else if (nextfont) { 359 charexists = 0; 360 usedfont = nextfont; 361 } else { 362 /* Regardless of whether or not a fallback font is found, the 363 * character must be drawn. 364 */ 365 charexists = 1; 366 367 for (i = 0; i < nomatches_len; ++i) { 368 /* avoid calling XftFontMatch if we know we won't find a match */ 369 if (utf8codepoint == nomatches.codepoint[i]) 370 goto no_match; 371 } 372 373 fccharset = FcCharSetCreate(); 374 FcCharSetAddChar(fccharset, utf8codepoint); 375 376 if (!drw->fonts->pattern) { 377 /* Refer to the comment in xfont_create for more information. */ 378 die("the first font in the cache must be loaded from a font string."); 379 } 380 381 fcpattern = FcPatternDuplicate(drw->fonts->pattern); 382 FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); 383 FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); 384 385 FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); 386 FcDefaultSubstitute(fcpattern); 387 match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); 388 389 FcCharSetDestroy(fccharset); 390 FcPatternDestroy(fcpattern); 391 392 if (match) { 393 usedfont = xfont_create(drw, NULL, match); 394 if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { 395 for (curfont = drw->fonts; curfont->next; curfont = curfont->next) 396 ; /* NOP */ 397 curfont->next = usedfont; 398 } else { 399 xfont_free(usedfont); 400 nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint; 401 no_match: 402 usedfont = drw->fonts; 403 } 404 } 405 } 406 } 407 if (d) 408 XftDrawDestroy(d); 409 410 return x + (render ? w : 0); 411 } 412 413 void 414 drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) 415 { 416 if (!drw) 417 return; 418 419 XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); 420 XSync(drw->dpy, False); 421 } 422 423 unsigned int 424 drw_fontset_getwidth(Drw *drw, const char *text) 425 { 426 if (!drw || !drw->fonts || !text) 427 return 0; 428 return drw_text(drw, 0, 0, 0, 0, 0, text, 0); 429 } 430 431 unsigned int 432 drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) 433 { 434 unsigned int tmp = 0; 435 if (drw && drw->fonts && text && n) 436 tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); 437 return MIN(n, tmp); 438 } 439 440 void 441 drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) 442 { 443 XGlyphInfo ext; 444 445 if (!font || !text) 446 return; 447 448 XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); 449 if (w) 450 *w = ext.xOff; 451 if (h) 452 *h = font->h; 453 }