drw.c 11.3 K raw
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
}