]> git.sur5r.net Git - i3/i3/blob - libi3/font.c
libi3: Rework font to support multiple backends
[i3/i3] / libi3 / font.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  */
8 #include <assert.h>
9 #include <stdint.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdbool.h>
13 #include <err.h>
14
15 #include "libi3.h"
16
17 extern xcb_connection_t *conn;
18 static const i3Font *savedFont = NULL;
19
20 /*
21  * Loads a font for usage, also getting its metrics. If fallback is true,
22  * the fonts 'fixed' or '-misc-*' will be loaded instead of exiting.
23  *
24  */
25 i3Font load_font(const char *pattern, const bool fallback) {
26     i3Font font;
27     font.type = FONT_TYPE_NONE;
28
29
30     /* Send all our requests first */
31     font.specific.xcb.id = xcb_generate_id(conn);
32     xcb_void_cookie_t font_cookie = xcb_open_font_checked(conn, font.specific.xcb.id,
33             strlen(pattern), pattern);
34     xcb_query_font_cookie_t info_cookie = xcb_query_font(conn, font.specific.xcb.id);
35
36     /* Check for errors. If errors, fall back to default font. */
37     xcb_generic_error_t *error;
38     error = xcb_request_check(conn, font_cookie);
39
40     /* If we fail to open font, fall back to 'fixed' */
41     if (fallback && error != NULL) {
42         ELOG("Could not open font %s (X error %d). Trying fallback to 'fixed'.\n",
43              pattern, error->error_code);
44         pattern = "fixed";
45         font_cookie = xcb_open_font_checked(conn, font.specific.xcb.id,
46                 strlen(pattern), pattern);
47         info_cookie = xcb_query_font(conn, font.specific.xcb.id);
48
49         /* Check if we managed to open 'fixed' */
50         error = xcb_request_check(conn, font_cookie);
51
52         /* Fall back to '-misc-*' if opening 'fixed' fails. */
53         if (error != NULL) {
54             ELOG("Could not open fallback font 'fixed', trying with '-misc-*'.\n");
55             pattern = "-misc-*";
56             font_cookie = xcb_open_font_checked(conn, font.specific.xcb.id,
57                     strlen(pattern), pattern);
58             info_cookie = xcb_query_font(conn, font.specific.xcb.id);
59
60             if ((error = xcb_request_check(conn, font_cookie)) != NULL)
61                 errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks "
62                      "(fixed or -misc-*): X11 error %d", error->error_code);
63         }
64     }
65
66     /* Get information (height/name) for this font */
67     if (!(font.specific.xcb.info = xcb_query_font_reply(conn, info_cookie, NULL)))
68         errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern);
69
70     /* Get the font table, if possible */
71     if (xcb_query_font_char_infos_length(font.specific.xcb.info) == 0)
72         font.specific.xcb.table = NULL;
73     else
74         font.specific.xcb.table = xcb_query_font_char_infos(font.specific.xcb.info);
75
76     /* Calculate the font height */
77     font.height = font.specific.xcb.info->font_ascent + font.specific.xcb.info->font_descent;
78
79     /* Set the font type and return successfully */
80     font.type = FONT_TYPE_XCB;
81     return font;
82 }
83
84 /*
85  * Defines the font to be used for the forthcoming calls.
86  *
87  */
88 void set_font(i3Font *font) {
89     savedFont = font;
90 }
91
92 /*
93  * Frees the resources taken by the current font.
94  *
95  */
96 void free_font(void) {
97     switch (savedFont->type) {
98         case FONT_TYPE_NONE:
99             /* Nothing to do */
100             break;
101         case FONT_TYPE_XCB: {
102             /* Close the font and free the info */
103             xcb_close_font(conn, savedFont->specific.xcb.id);
104             if (savedFont->specific.xcb.info)
105                 free(savedFont->specific.xcb.info);
106             break;
107         }
108         default:
109             assert(false);
110             break;
111     }
112 }
113
114 /*
115  * Defines the colors to be used for the forthcoming draw_text calls.
116  *
117  */
118 void set_font_colors(xcb_gcontext_t gc, uint32_t foreground, uint32_t background) {
119     assert(savedFont != NULL);
120
121     switch (savedFont->type) {
122         case FONT_TYPE_NONE:
123             /* Nothing to do */
124             break;
125         case FONT_TYPE_XCB: {
126             /* Change the font and colors in the GC */
127             uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
128             uint32_t values[] = { foreground, background, savedFont->specific.xcb.id };
129             xcb_change_gc(conn, gc, mask, values);
130             break;
131         }
132         default:
133             assert(false);
134             break;
135     }
136 }
137
138 static int predict_text_width_xcb(const xcb_char2b_t *text, size_t text_len);
139
140 static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawable_t drawable,
141                xcb_gcontext_t gc, int x, int y, int max_width) {
142     /* X11 coordinates for fonts start at the baseline */
143     int pos_y = y + savedFont->specific.xcb.info->font_ascent;
144
145     /* The X11 protocol limits text drawing to 255 chars, so we may need
146      * multiple calls */
147     int offset = 0;
148     for (;;) {
149         /* Calculate the size of this chunk */
150         int chunk_size = (text_len > 255 ? 255 : text_len);
151         const xcb_char2b_t *chunk = text + offset;
152
153         /* Draw it */
154         xcb_image_text_16(conn, chunk_size, drawable, gc, x, pos_y, chunk);
155
156         /* Advance the offset and length of the text to draw */
157         offset += chunk_size;
158         text_len -= chunk_size;
159
160         /* Check if we're done */
161         if (text_len == 0)
162             break;
163
164         /* Advance pos_x based on the predicted text width */
165         x += predict_text_width_xcb(chunk, chunk_size);
166     }
167 }
168
169 /*
170  * Draws text onto the specified X drawable (normally a pixmap) at the
171  * specified coordinates (from the top left corner of the leftmost, uppermost
172  * glyph) and using the provided gc.
173  *
174  * Text must be specified as an i3String.
175  *
176  */
177 void draw_text(i3String *text, xcb_drawable_t drawable,
178                xcb_gcontext_t gc, int x, int y, int max_width) {
179     assert(savedFont != NULL);
180
181     switch (savedFont->type) {
182         case FONT_TYPE_NONE:
183             /* Nothing to do */
184             return;
185         case FONT_TYPE_XCB:
186             draw_text_xcb(i3string_as_ucs2(text), i3string_get_num_glyphs(text),
187                       drawable, gc, x, y, max_width);
188             break;
189         default:
190             assert(false);
191     }
192 }
193
194 /*
195  * ASCII version of draw_text to print static strings.
196  *
197  */
198 void draw_text_ascii(const char *text, xcb_drawable_t drawable,
199                xcb_gcontext_t gc, int x, int y, int max_width) {
200     assert(savedFont != NULL);
201
202     switch (savedFont->type) {
203         case FONT_TYPE_NONE:
204             /* Nothing to do */
205             return;
206         case FONT_TYPE_XCB:
207         {
208             size_t text_len = strlen(text);
209             if (text_len > 255) {
210                 /* The text is too long to draw it directly to X */
211                 i3String *str = i3string_from_utf8(text);
212                 draw_text(str, drawable, gc, x, y, max_width);
213                 i3string_free(str);
214             } else {
215                 /* X11 coordinates for fonts start at the baseline */
216                 int pos_y = y + savedFont->specific.xcb.info->font_ascent;
217
218                 xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text);
219             }
220             break;
221         }
222         default:
223             assert(false);
224     }
225 }
226
227 static int xcb_query_text_width(const xcb_char2b_t *text, size_t text_len) {
228     /* Make the user know we’re using the slow path, but only once. */
229     static bool first_invocation = true;
230     if (first_invocation) {
231         fprintf(stderr, "Using slow code path for text extents\n");
232         first_invocation = false;
233     }
234
235     /* Query the text width */
236     xcb_generic_error_t *error;
237     xcb_query_text_extents_cookie_t cookie = xcb_query_text_extents(conn,
238             savedFont->specific.xcb.id, text_len, (xcb_char2b_t*)text);
239     xcb_query_text_extents_reply_t *reply = xcb_query_text_extents_reply(conn,
240             cookie, &error);
241     if (reply == NULL) {
242         /* We return a safe estimate because a rendering error is better than
243          * a crash. Plus, the user will see the error in his log. */
244         fprintf(stderr, "Could not get text extents (X error code %d)\n",
245                 error->error_code);
246         return savedFont->specific.xcb.info->max_bounds.character_width * text_len;
247     }
248
249     int width = reply->overall_width;
250     free(reply);
251     return width;
252 }
253
254 static int predict_text_width_xcb(const xcb_char2b_t *input, size_t text_len) {
255     if (text_len == 0)
256         return 0;
257
258     int width;
259     if (savedFont->specific.xcb.table == NULL) {
260         /* If we don't have a font table, fall back to querying the server */
261         width = xcb_query_text_width(input, text_len);
262     } else {
263         /* Save some pointers for convenience */
264         xcb_query_font_reply_t *font_info = savedFont->specific.xcb.info;
265         xcb_charinfo_t *font_table = savedFont->specific.xcb.table;
266
267         /* Calculate the width using the font table */
268         width = 0;
269         for (size_t i = 0; i < text_len; i++) {
270             xcb_charinfo_t *info;
271             int row = input[i].byte1;
272             int col = input[i].byte2;
273
274             if (row < font_info->min_byte1 ||
275                 row > font_info->max_byte1 ||
276                 col < font_info->min_char_or_byte2 ||
277                 col > font_info->max_char_or_byte2)
278                 continue;
279
280             /* Don't you ask me, how this one works… (Merovius) */
281             info = &font_table[((row - font_info->min_byte1) *
282                     (font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) +
283                 (col - font_info->min_char_or_byte2)];
284
285             if (info->character_width != 0 ||
286                     (info->right_side_bearing |
287                      info->left_side_bearing |
288                      info->ascent |
289                      info->descent) != 0) {
290                 width += info->character_width;
291             }
292         }
293     }
294
295     return width;
296 }
297
298 /*
299  * Predict the text width in pixels for the given text. Text must be
300  * specified as an i3String.
301  *
302  */
303 int predict_text_width(i3String *text) {
304     assert(savedFont != NULL);
305
306     switch (savedFont->type) {
307         case FONT_TYPE_NONE:
308             /* Nothing to do */
309             return 0;
310         case FONT_TYPE_XCB:
311             return predict_text_width_xcb(i3string_as_ucs2(text), i3string_get_num_glyphs(text));
312         default:
313             assert(false);
314             return 0;
315     }
316 }