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