Merge branch 'backport' into work
[dana/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         pango_layout_set_ellipsize(f->layout, PANGO_ELLIPSIZE_NONE);
152     }
153     else {
154         /* single line mode */
155         pango_layout_set_single_paragraph_mode(f->layout, TRUE);
156         pango_layout_set_width(f->layout, -1);
157         pango_layout_set_ellipsize(f->layout, PANGO_ELLIPSIZE_MIDDLE);
158     }
159
160     /* pango_layout_get_pixel_extents lies! this is the right way to get the
161        size of the text's area */
162     pango_layout_get_extents(f->layout, NULL, &rect);
163 #if PANGO_VERSION_MAJOR > 1 || \
164     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
165     /* pass the logical rect as the ink rect, this is on purpose so we get the
166        full area for the text */
167     pango_extents_to_pixels(&rect, NULL);
168 #else
169     rect.width = (rect.width + PANGO_SCALE - 1) / PANGO_SCALE;
170     rect.height = (rect.height + PANGO_SCALE - 1) / PANGO_SCALE;
171 #endif
172     *x = rect.width + ABS(shadow_x) + 4 /* we put a 2 px edge on each side */;
173     *y = rect.height + ABS(shadow_y);
174 }
175
176 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str,
177                             gint shadow_x, gint shadow_y,
178                             gboolean flow, gint maxwidth)
179 {
180     RrSize *size;
181
182     g_assert(!flow || maxwidth > 0);
183
184     size = g_new(RrSize, 1);
185     font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y,
186                       flow, maxwidth);
187     return size;
188 }
189
190 gint RrFontHeight(const RrFont *f, gint shadow_y)
191 {
192     return (f->ascent + f->descent) / PANGO_SCALE + ABS(shadow_y);
193 }
194
195 static inline int font_calculate_baseline(RrFont *f, gint height)
196 {
197 /* For my own reference:
198  *   _________
199  *  ^space/2  ^height     ^baseline
200  *  v_________|_          |
201  *            | ^ascent   |   _           _
202  *            | |         |  | |_ _____ _| |_ _  _
203  *            | |         |  |  _/ -_) \ /  _| || |
204  *            | v_________v   \__\___/_\_\\__|\_, |
205  *            | ^descent                      |__/
206  *  __________|_v
207  *  ^space/2  |
208  *  V_________v
209  */
210     return (((height * PANGO_SCALE) /* height of the space in pango units */
211              - (f->ascent + f->descent)) /* minus space taken up by text */
212             / 2 /* divided by two -> half of the empty space (this is the top
213                    of the text) */
214             + f->ascent) /* now move down to the baseline */
215         / PANGO_SCALE; /* back to pixels */
216 }
217
218 void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
219 {
220     gint x,y,w;
221     XftColor c;
222     gint mw;
223     PangoRectangle rect;
224     PangoAttrList *attrlist;
225     PangoEllipsizeMode ell;
226
227     g_assert(!t->flow || t->maxwidth > 0);
228
229     y = area->y;
230     if (!t->flow)
231         /* center the text vertically
232            We do this centering based on the 'baseline' since different fonts
233            have different top edges. It looks bad when the whole string is
234            moved when 1 character from a non-default language is included in
235            the string */
236         y += font_calculate_baseline(t->font, area->height);
237
238     /* the +2 and -4 leave a small blank edge on the sides */
239     x = area->x + 2;
240     w = area->width;
241     if (t->flow) w = MAX(w, t->maxwidth);
242     w -= 4;
243     /* h = area->height; */
244
245     if (t->flow)
246         ell = PANGO_ELLIPSIZE_NONE;
247     else {
248         switch (t->ellipsize) {
249         case RR_ELLIPSIZE_NONE:
250             ell = PANGO_ELLIPSIZE_NONE;
251             break;
252         case RR_ELLIPSIZE_START:
253             ell = PANGO_ELLIPSIZE_START;
254             break;
255         case RR_ELLIPSIZE_MIDDLE:
256             ell = PANGO_ELLIPSIZE_MIDDLE;
257             break;
258         case RR_ELLIPSIZE_END:
259             ell = PANGO_ELLIPSIZE_END;
260             break;
261         default:
262             g_assert_not_reached();
263         }
264     }
265
266     pango_layout_set_text(t->font->layout, t->string, -1);
267     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
268     pango_layout_set_ellipsize(t->font->layout, ell);
269     pango_layout_set_single_paragraph_mode(t->font->layout, !t->flow);
270
271     /* * * end of setting up the layout * * */
272
273     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
274     mw = rect.width;
275
276     /* pango_layout_set_alignment doesn't work with
277        pango_xft_render_layout_line */
278     switch (t->justify) {
279     case RR_JUSTIFY_LEFT:
280         break;
281     case RR_JUSTIFY_RIGHT:
282         x += (w - mw);
283         break;
284     case RR_JUSTIFY_CENTER:
285         x += (w - mw) / 2;
286         break;
287     }
288
289     if (t->shadow_offset_x || t->shadow_offset_y) {
290         /* From nvidia's readme (chapter 23):
291
292            When rendering to a 32-bit window, keep in mind that the X RENDER
293            extension, used by most composite managers, expects "premultiplied
294            alpha" colors. This means that if your color has components (r,g,b)
295            and alpha value a, then you must render (a*r, a*g, a*b, a) into the
296            target window.
297         */
298         c.color.red = (t->shadow_color->r | t->shadow_color->r << 8) *
299             t->shadow_alpha / 255;
300         c.color.green = (t->shadow_color->g | t->shadow_color->g << 8) *
301             t->shadow_alpha / 255;
302         c.color.blue = (t->shadow_color->b | t->shadow_color->b << 8) *
303             t->shadow_alpha / 255;
304         c.color.alpha = 0xffff * t->shadow_alpha / 255;
305         c.pixel = t->shadow_color->pixel;
306
307         /* see below... */
308         if (!t->flow) {
309             pango_xft_render_layout_line
310                 (d, &c,
311 #if PANGO_VERSION_MAJOR > 1 || \
312     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
313                  pango_layout_get_line_readonly(t->font->layout, 0),
314 #else
315                  pango_layout_get_line(t->font->layout, 0),
316 #endif
317                  (x + t->shadow_offset_x) * PANGO_SCALE,
318                  (y + t->shadow_offset_y) * PANGO_SCALE);
319         }
320         else {
321             pango_xft_render_layout(d, &c, t->font->layout,
322                                     (x + t->shadow_offset_x) * PANGO_SCALE,
323                                     (y + t->shadow_offset_y) * PANGO_SCALE);
324         }
325     }
326
327     c.color.red = t->color->r | t->color->r << 8;
328     c.color.green = t->color->g | t->color->g << 8;
329     c.color.blue = t->color->b | t->color->b << 8;
330     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
331     c.pixel = t->color->pixel;
332
333     if (t->shortcut) {
334         const gchar *s = t->string + t->shortcut_pos;
335
336         t->font->shortcut_underline->start_index = t->shortcut_pos;
337         t->font->shortcut_underline->end_index = t->shortcut_pos +
338             (g_utf8_next_char(s) - s);
339
340         /* the attributes are owned by the layout.
341            re-add the attributes to the layout after changing the
342            start and end index */
343         attrlist = pango_layout_get_attributes(t->font->layout);
344         pango_attr_list_ref(attrlist);
345         pango_layout_set_attributes(t->font->layout, attrlist);
346         pango_attr_list_unref(attrlist);
347     }
348
349     /* layout_line() uses y to specify the baseline
350        The line doesn't need to be freed, it's a part of the layout */
351     if (!t->flow) {
352         pango_xft_render_layout_line
353             (d, &c,
354 #if PANGO_VERSION_MAJOR > 1 || \
355     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
356              pango_layout_get_line_readonly(t->font->layout, 0),
357 #else
358              pango_layout_get_line(t->font->layout, 0),
359 #endif
360              x * PANGO_SCALE,
361              y * PANGO_SCALE);
362     }
363     else {
364         pango_xft_render_layout(d, &c, t->font->layout,
365                                 x * PANGO_SCALE,
366                                 y * PANGO_SCALE);
367     }
368
369     if (t->shortcut) {
370         t->font->shortcut_underline->start_index = 0;
371         t->font->shortcut_underline->end_index = 0;
372         /* the attributes are owned by the layout.
373            re-add the attributes to the layout after changing the
374            start and end index */
375         attrlist = pango_layout_get_attributes(t->font->layout);
376         pango_attr_list_ref(attrlist);
377         pango_layout_set_attributes(t->font->layout, attrlist);
378         pango_attr_list_unref(attrlist);
379     }
380 }