29d482150bd6b9b82f5647dadb1b7a14a4053518
[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_single_paragraph_mode(out->layout, TRUE);
112     pango_layout_set_wrap(out->layout, PANGO_WRAP_WORD_CHAR);
113
114     /* get the ascent and descent */
115     measure_font(inst, out);
116
117     return out;
118 }
119
120 RrFont *RrFontOpenDefault(const RrInstance *inst)
121 {
122     return RrFontOpen(inst, RrDefaultFontFamily, RrDefaultFontSize,
123                       RrDefaultFontWeight, RrDefaultFontSlant);
124 }
125
126 void RrFontRef(RrFont *f)
127 {
128     ++f->ref;
129 }
130
131 void RrFontClose(RrFont *f)
132 {
133     if (f) {
134         if (--f->ref < 1) {
135             g_object_unref(f->layout);
136             pango_font_description_free(f->font_desc);
137             g_free(f);
138         }
139     }
140 }
141
142 static void font_measure_full(const RrFont *f, const gchar *str,
143                               gint *x, gint *y, gint shadow_x, gint shadow_y,
144                               gint maxwidth)
145 {
146     PangoRectangle rect;
147
148     pango_layout_set_text(f->layout, str, -1);
149     pango_layout_set_width(f->layout,
150                            (maxwidth <= 0 ? -1 : maxwidth * PANGO_SCALE));
151
152     /* pango_layout_get_pixel_extents lies! this is the right way to get the
153        size of the text's area */
154     pango_layout_get_extents(f->layout, NULL, &rect);
155 #if PANGO_VERSION_MAJOR > 1 || \
156     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
157     /* pass the logical rect as the ink rect, this is on purpose so we get the
158        full area for the text */
159     pango_extents_to_pixels(&rect, NULL);
160 #else
161     rect.width = (rect.width + PANGO_SCALE - 1) / PANGO_SCALE;
162     rect.height = (rect.height + PANGO_SCALE - 1) / PANGO_SCALE;
163 #endif
164     *x = rect.width + ABS(shadow_x) + 4 /* we put a 2 px edge on each side */;
165     *y = rect.height + ABS(shadow_y);
166 }
167
168 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str,
169                             gint shadow_x, gint shadow_y, gint maxwidth)
170 {
171     RrSize *size;
172     size = g_new(RrSize, 1);
173     font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y,
174                       maxwidth);
175     return size;
176 }
177
178 gint RrFontHeight(const RrFont *f, gint shadow_y)
179 {
180     return (f->ascent + f->descent) / PANGO_SCALE + ABS(shadow_y);
181 }
182
183 static inline int font_calculate_baseline(RrFont *f, gint height)
184 {
185 /* For my own reference:
186  *   _________
187  *  ^space/2  ^height     ^baseline
188  *  v_________|_          |
189  *            | ^ascent   |   _           _
190  *            | |         |  | |_ _____ _| |_ _  _
191  *            | |         |  |  _/ -_) \ /  _| || |
192  *            | v_________v   \__\___/_\_\\__|\_, |
193  *            | ^descent                      |__/
194  *  __________|_v
195  *  ^space/2  |
196  *  V_________v
197  */
198     return (((height * PANGO_SCALE) /* height of the space in pango units */
199              - (f->ascent + f->descent)) /* minus space taken up by text */
200             / 2 /* divided by two -> half of the empty space (this is the top
201                    of the text) */
202             + f->ascent) /* now move down to the baseline */
203         / PANGO_SCALE; /* back to pixels */
204 }
205
206 void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
207 {
208     gint x,y,w,h;
209     XftColor c;
210     gint mw;
211     PangoRectangle rect;
212     PangoAttrList *attrlist;
213     PangoEllipsizeMode ell;
214
215     /* center the text vertically
216        We do this centering based on the 'baseline' since different fonts have
217        different top edges. It looks bad when the whole string is moved when 1
218        character from a non-default language is included in the string */
219     y = area->y +
220         font_calculate_baseline(t->font, area->height);
221
222     /* the +2 and -4 leave a small blank edge on the sides */
223     x = area->x + 2;
224     w = area->width - 4;
225     h = area->height;
226
227     switch (t->ellipsize) {
228     case RR_ELLIPSIZE_NONE:
229         ell = PANGO_ELLIPSIZE_NONE;
230         break;
231     case RR_ELLIPSIZE_START:
232         ell = PANGO_ELLIPSIZE_START;
233         break;
234     case RR_ELLIPSIZE_MIDDLE:
235         ell = PANGO_ELLIPSIZE_MIDDLE;
236         break;
237     case RR_ELLIPSIZE_END:
238         ell = PANGO_ELLIPSIZE_END;
239         break;
240     }
241
242     pango_layout_set_text(t->font->layout, t->string, -1);
243     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
244     pango_layout_set_ellipsize(t->font->layout, ell);
245
246     /* * * end of setting up the layout * * */
247
248     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
249     mw = rect.width;
250
251     /* pango_layout_set_alignment doesn't work with
252        pango_xft_render_layout_line */
253     switch (t->justify) {
254     case RR_JUSTIFY_LEFT:
255         break;
256     case RR_JUSTIFY_RIGHT:
257         x += (w - mw);
258         break;
259     case RR_JUSTIFY_CENTER:
260         x += (w - mw) / 2;
261         break;
262     }
263
264     if (t->shadow_offset_x || t->shadow_offset_y) {
265         /* From nvidia's readme (chapter 23):
266
267            When rendering to a 32-bit window, keep in mind that the X RENDER
268            extension, used by most composite managers, expects "premultiplied
269            alpha" colors. This means that if your color has components (r,g,b)
270            and alpha value a, then you must render (a*r, a*g, a*b, a) into the
271            target window.
272         */
273         c.color.red = (t->shadow_color->r | t->shadow_color->r << 8) *
274             t->shadow_alpha / 255;
275         c.color.green = (t->shadow_color->g | t->shadow_color->g << 8) *
276             t->shadow_alpha / 255;
277         c.color.blue = (t->shadow_color->b | t->shadow_color->b << 8) *
278             t->shadow_alpha / 255;
279         c.color.alpha = 0xffff * t->shadow_alpha / 255;
280         c.pixel = t->shadow_color->pixel;
281
282         /* see below... */
283         pango_xft_render_layout_line
284             (d, &c, pango_layout_get_line(t->font->layout, 0),
285              (x + t->shadow_offset_x) * PANGO_SCALE,
286              (y + t->shadow_offset_y) * PANGO_SCALE);
287     }
288
289     c.color.red = t->color->r | t->color->r << 8;
290     c.color.green = t->color->g | t->color->g << 8;
291     c.color.blue = t->color->b | t->color->b << 8;
292     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
293     c.pixel = t->color->pixel;
294
295     if (t->shortcut) {
296         const gchar *s = t->string + t->shortcut_pos;
297
298         t->font->shortcut_underline->start_index = t->shortcut_pos;
299         t->font->shortcut_underline->end_index = t->shortcut_pos +
300             (g_utf8_next_char(s) - s);
301
302         /* the attributes are owned by the layout.
303            re-add the attributes to the layout after changing the
304            start and end index */
305         attrlist = pango_layout_get_attributes(t->font->layout);
306         pango_attr_list_ref(attrlist);
307         pango_layout_set_attributes(t->font->layout, attrlist);
308         pango_attr_list_unref(attrlist);
309     }
310
311     /* layout_line() uses y to specify the baseline
312        The line doesn't need to be freed, it's a part of the layout */
313     pango_xft_render_layout_line
314         (d, &c, pango_layout_get_line(t->font->layout, 0),
315          x * PANGO_SCALE, y * PANGO_SCALE);
316
317     if (t->shortcut) {
318         t->font->shortcut_underline->start_index = 0;
319         t->font->shortcut_underline->end_index = 0;
320         /* the attributes are owned by the layout.
321            re-add the attributes to the layout after changing the
322            start and end index */
323         attrlist = pango_layout_get_attributes(t->font->layout);
324         pango_attr_list_ref(attrlist);
325         pango_layout_set_attributes(t->font->layout, attrlist);
326         pango_attr_list_unref(attrlist);
327     }
328 }