allow font rendering to use multiple lines
[mikachu/openbox.git] / render / font.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    font.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6    Copyright (c) 2003        Derek Foreman
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    See the COPYING file for a copy of the GNU General Public License.
19 */
20
21 #include "font.h"
22 #include "color.h"
23 #include "mask.h"
24 #include "theme.h"
25 #include "geom.h"
26 #include "instance.h"
27 #include "gettext.h"
28
29 #include <glib.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <locale.h>
33
34 static void measure_font(const RrInstance *inst, RrFont *f)
35 {
36     PangoFontMetrics *metrics;
37     static PangoLanguage *lang = NULL;
38
39     if (lang == NULL) {
40 #if PANGO_VERSION_MAJOR > 1 || \
41     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
42         lang = pango_language_get_default();
43 #else
44         gchar *locale, *p;
45         /* get the default language from the locale
46            (based on gtk_get_default_language in gtkmain.c) */
47         locale = g_strdup(setlocale(LC_CTYPE, NULL));
48         if ((p = strchr(locale, '.'))) *p = '\0'; /* strip off the . */
49         if ((p = strchr(locale, '@'))) *p = '\0'; /* strip off the @ */
50         lang = pango_language_from_string(locale);
51         g_free(locale);
52 #endif
53     }
54
55     /* measure the ascent and descent */
56     metrics = pango_context_get_metrics(inst->pango, f->font_desc, lang);
57     f->ascent = pango_font_metrics_get_ascent(metrics);
58     f->descent = pango_font_metrics_get_descent(metrics);
59     pango_font_metrics_unref(metrics);
60
61 }
62
63 RrFont *RrFontOpen(const RrInstance *inst, const gchar *name, gint size,
64                    RrFontWeight weight, RrFontSlant slant)
65 {
66     RrFont *out;
67     PangoWeight pweight;
68     PangoStyle pstyle;
69     PangoAttrList *attrlist;
70
71     out = g_new(RrFont, 1);
72     out->inst = inst;
73     out->ref = 1;
74     out->font_desc = pango_font_description_new();
75     out->layout = pango_layout_new(inst->pango);
76     out->shortcut_underline = pango_attr_underline_new(PANGO_UNDERLINE_LOW);
77     out->shortcut_underline->start_index = 0;
78     out->shortcut_underline->end_index = 0;
79
80     attrlist = pango_attr_list_new();
81     /* shortcut_underline is owned by the attrlist */
82     pango_attr_list_insert(attrlist, out->shortcut_underline);
83     /* the attributes are owned by the layout */
84     pango_layout_set_attributes(out->layout, attrlist);
85     pango_attr_list_unref(attrlist);
86
87     switch (weight) {
88     case RR_FONTWEIGHT_LIGHT:     pweight = PANGO_WEIGHT_LIGHT;     break;
89     case RR_FONTWEIGHT_NORMAL:    pweight = PANGO_WEIGHT_NORMAL;    break;
90     case RR_FONTWEIGHT_SEMIBOLD:  pweight = PANGO_WEIGHT_SEMIBOLD;  break;
91     case RR_FONTWEIGHT_BOLD:      pweight = PANGO_WEIGHT_BOLD;      break;
92     case RR_FONTWEIGHT_ULTRABOLD: pweight = PANGO_WEIGHT_ULTRABOLD; break;
93     default: g_assert_not_reached();
94     }
95
96     switch (slant) {
97     case RR_FONTSLANT_NORMAL:  pstyle = PANGO_STYLE_NORMAL;    break;
98     case RR_FONTSLANT_ITALIC:  pstyle = PANGO_STYLE_ITALIC;    break;
99     case RR_FONTSLANT_OBLIQUE: pstyle = PANGO_STYLE_OBLIQUE;   break;
100     default: g_assert_not_reached();
101     }
102
103     /* setup the font */
104     pango_font_description_set_family(out->font_desc, name);
105     pango_font_description_set_weight(out->font_desc, pweight);
106     pango_font_description_set_style(out->font_desc, pstyle);
107     pango_font_description_set_size(out->font_desc, size * PANGO_SCALE);
108
109     /* setup the layout */
110     pango_layout_set_font_description(out->layout, out->font_desc);
111     pango_layout_set_wrap(out->layout, PANGO_WRAP_WORD_CHAR);
112
113     /* get the ascent and descent */
114     measure_font(inst, out);
115
116     return out;
117 }
118
119 RrFont *RrFontOpenDefault(const RrInstance *inst)
120 {
121     return RrFontOpen(inst, RrDefaultFontFamily, RrDefaultFontSize,
122                       RrDefaultFontWeight, RrDefaultFontSlant);
123 }
124
125 void RrFontRef(RrFont *f)
126 {
127     ++f->ref;
128 }
129
130 void RrFontClose(RrFont *f)
131 {
132     if (f) {
133         if (--f->ref < 1) {
134             g_object_unref(f->layout);
135             pango_font_description_free(f->font_desc);
136             g_free(f);
137         }
138     }
139 }
140
141 static void font_measure_full(const RrFont *f, const gchar *str,
142                               gint *x, gint *y, gint shadow_x, gint shadow_y,
143                               gboolean flow, gint maxwidth)
144 {
145     PangoRectangle rect;
146
147     pango_layout_set_text(f->layout, str, -1);
148     if (flow) {
149         pango_layout_set_single_paragraph_mode(f->layout, FALSE);
150         pango_layout_set_width(f->layout, maxwidth * PANGO_SCALE);
151     }
152     else {
153         /* single line mode */
154         pango_layout_set_single_paragraph_mode(f->layout, TRUE);
155         pango_layout_set_width(f->layout, -1);
156     }
157
158     /* pango_layout_get_pixel_extents lies! this is the right way to get the
159        size of the text's area */
160     pango_layout_get_extents(f->layout, NULL, &rect);
161 #if PANGO_VERSION_MAJOR > 1 || \
162     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
163     /* pass the logical rect as the ink rect, this is on purpose so we get the
164        full area for the text */
165     pango_extents_to_pixels(&rect, NULL);
166 #else
167     rect.width = (rect.width + PANGO_SCALE - 1) / PANGO_SCALE;
168     rect.height = (rect.height + PANGO_SCALE - 1) / PANGO_SCALE;
169 #endif
170     *x = rect.width + ABS(shadow_x) + 4 /* we put a 2 px edge on each side */;
171     *y = rect.height + ABS(shadow_y);
172 }
173
174 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str,
175                             gint shadow_x, gint shadow_y,
176                             gboolean flow, gint maxwidth)
177 {
178     RrSize *size;
179
180     g_assert(!flow || maxwidth > 0);
181
182     size = g_new(RrSize, 1);
183     font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y,
184                       flow, maxwidth);
185     return size;
186 }
187
188 gint RrFontHeight(const RrFont *f, gint shadow_y)
189 {
190     return (f->ascent + f->descent) / PANGO_SCALE + ABS(shadow_y);
191 }
192
193 static inline int font_calculate_baseline(RrFont *f, gint height)
194 {
195 /* For my own reference:
196  *   _________
197  *  ^space/2  ^height     ^baseline
198  *  v_________|_          |
199  *            | ^ascent   |   _           _
200  *            | |         |  | |_ _____ _| |_ _  _
201  *            | |         |  |  _/ -_) \ /  _| || |
202  *            | v_________v   \__\___/_\_\\__|\_, |
203  *            | ^descent                      |__/
204  *  __________|_v
205  *  ^space/2  |
206  *  V_________v
207  */
208     return (((height * PANGO_SCALE) /* height of the space in pango units */
209              - (f->ascent + f->descent)) /* minus space taken up by text */
210             / 2 /* divided by two -> half of the empty space (this is the top
211                    of the text) */
212             + f->ascent) /* now move down to the baseline */
213         / PANGO_SCALE; /* back to pixels */
214 }
215
216 void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
217 {
218     gint x,y,w,h;
219     XftColor c;
220     gint mw;
221     PangoRectangle rect;
222     PangoAttrList *attrlist;
223     PangoEllipsizeMode ell;
224
225     g_assert(!t->flow || t->maxwidth > 0);
226
227     y = area->y;
228     if (!t->flow)
229         /* center the text vertically
230            We do this centering based on the 'baseline' since different fonts
231            have different top edges. It looks bad when the whole string is
232            moved when 1 character from a non-default language is included in
233            the string */
234         y += font_calculate_baseline(t->font, area->height);
235
236     /* the +2 and -4 leave a small blank edge on the sides */
237     x = area->x + 2;
238     w = area->width;
239     if (t->flow) w = MAX(w, t->maxwidth);
240     w -= 4;
241     h = area->height;
242
243     switch (t->ellipsize) {
244     case RR_ELLIPSIZE_NONE:
245         ell = PANGO_ELLIPSIZE_NONE;
246         break;
247     case RR_ELLIPSIZE_START:
248         ell = PANGO_ELLIPSIZE_START;
249         break;
250     case RR_ELLIPSIZE_MIDDLE:
251         ell = PANGO_ELLIPSIZE_MIDDLE;
252         break;
253     case RR_ELLIPSIZE_END:
254         ell = PANGO_ELLIPSIZE_END;
255         break;
256     }
257
258     pango_layout_set_text(t->font->layout, t->string, -1);
259     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
260     pango_layout_set_ellipsize(t->font->layout, ell);
261     pango_layout_set_single_paragraph_mode(t->font->layout, !t->flow);
262
263     /* * * end of setting up the layout * * */
264
265     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
266     mw = rect.width;
267
268     /* pango_layout_set_alignment doesn't work with
269        pango_xft_render_layout_line */
270     switch (t->justify) {
271     case RR_JUSTIFY_LEFT:
272         break;
273     case RR_JUSTIFY_RIGHT:
274         x += (w - mw);
275         break;
276     case RR_JUSTIFY_CENTER:
277         x += (w - mw) / 2;
278         break;
279     }
280
281     if (t->shadow_offset_x || t->shadow_offset_y) {
282         c.color.red = t->shadow_color->r | t->shadow_color->r << 8;
283         c.color.green = t->shadow_color->g | t->shadow_color->g << 8;
284         c.color.blue = t->shadow_color->b | t->shadow_color->b << 8;
285         c.color.alpha = 0xffff * t->shadow_alpha / 255;
286         c.pixel = t->shadow_color->pixel;
287
288         /* see below... */
289         if (!t->flow) {
290             pango_xft_render_layout_line
291                 (d, &c,
292 #if PANGO_VERSION_MAJOR > 1 || \
293     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
294                  pango_layout_get_line_readonly(t->font->layout, 0),
295 #else
296                  pango_layout_get_line(t->font->layout, 0),
297 #endif
298                  (x + t->shadow_offset_x) * PANGO_SCALE,
299                  (y + t->shadow_offset_y) * PANGO_SCALE);
300         }
301         else {
302             pango_xft_render_layout(d, &c, t->font->layout,
303                                     (x + t->shadow_offset_x) * PANGO_SCALE,
304                                     (y + t->shadow_offset_y) * PANGO_SCALE);
305         }
306     }
307
308     c.color.red = t->color->r | t->color->r << 8;
309     c.color.green = t->color->g | t->color->g << 8;
310     c.color.blue = t->color->b | t->color->b << 8;
311     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
312     c.pixel = t->color->pixel;
313
314     if (t->shortcut) {
315         const gchar *s = t->string + t->shortcut_pos;
316
317         t->font->shortcut_underline->start_index = t->shortcut_pos;
318         t->font->shortcut_underline->end_index = t->shortcut_pos +
319             (g_utf8_next_char(s) - s);
320
321         /* the attributes are owned by the layout.
322            re-add the attributes to the layout after changing the
323            start and end index */
324         attrlist = pango_layout_get_attributes(t->font->layout);
325         pango_attr_list_ref(attrlist);
326         pango_layout_set_attributes(t->font->layout, attrlist);
327         pango_attr_list_unref(attrlist);
328     }
329
330     /* layout_line() uses y to specify the baseline
331        The line doesn't need to be freed, it's a part of the layout */
332     if (!t->flow) {
333         pango_xft_render_layout_line
334             (d, &c,
335 #if PANGO_VERSION_MAJOR > 1 || \
336     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
337              pango_layout_get_line_readonly(t->font->layout, 0),
338 #else
339              pango_layout_get_line(t->font->layout, 0),
340 #endif
341              x * PANGO_SCALE,
342              y * PANGO_SCALE);
343     }
344     else {
345         pango_xft_render_layout(d, &c, t->font->layout,
346                                 x * PANGO_SCALE,
347                                 y * PANGO_SCALE);
348     }
349
350     if (t->shortcut) {
351         t->font->shortcut_underline->start_index = 0;
352         t->font->shortcut_underline->end_index = 0;
353         /* the attributes are owned by the layout.
354            re-add the attributes to the layout after changing the
355            start and end index */
356         attrlist = pango_layout_get_attributes(t->font->layout);
357         pango_attr_list_ref(attrlist);
358         pango_layout_set_attributes(t->font->layout, attrlist);
359         pango_attr_list_unref(attrlist);
360     }
361 }