remove trailing whitespace
[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
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 {
144     PangoRectangle rect;
145
146     pango_layout_set_text(f->layout, str, -1);
147     pango_layout_set_width(f->layout, -1);
148
149     /* pango_layout_get_pixel_extents lies! this is the right way to get the
150        size of the text's area */
151     pango_layout_get_extents(f->layout, NULL, &rect);
152 #if PANGO_VERSION_MAJOR > 1 || \
153     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
154     /* pass the logical rect as the ink rect, this is on purpose so we get the
155        full area for the text */
156     pango_extents_to_pixels(&rect, NULL);
157 #else
158     rect.width = (rect.width + PANGO_SCALE - 1) / PANGO_SCALE;
159     rect.height = (rect.height + PANGO_SCALE - 1) / PANGO_SCALE;
160 #endif
161     *x = rect.width + ABS(shadow_x) + 4 /* we put a 2 px edge on each side */;
162     *y = rect.height + ABS(shadow_y);
163 }
164
165 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str,
166                             gint shadow_x, gint shadow_y)
167 {
168     RrSize *size;
169     size = g_new(RrSize, 1);
170     font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y);
171     return size;
172 }
173
174 gint RrFontHeight(const RrFont *f, gint shadow_y)
175 {
176     return (f->ascent + f->descent) / PANGO_SCALE + ABS(shadow_y);
177 }
178
179 static inline int font_calculate_baseline(RrFont *f, gint height)
180 {
181 /* For my own reference:
182  *   _________
183  *  ^space/2  ^height     ^baseline
184  *  v_________|_          |
185  *            | ^ascent   |   _           _
186  *            | |         |  | |_ _____ _| |_ _  _
187  *            | |         |  |  _/ -_) \ /  _| || |
188  *            | v_________v   \__\___/_\_\\__|\_, |
189  *            | ^descent                      |__/
190  *  __________|_v
191  *  ^space/2  |
192  *  V_________v
193  */
194     return (((height * PANGO_SCALE) /* height of the space in pango units */
195              - (f->ascent + f->descent)) /* minus space taken up by text */
196             / 2 /* divided by two -> half of the empty space (this is the top
197                    of the text) */
198             + f->ascent) /* now move down to the baseline */
199         / PANGO_SCALE; /* back to pixels */
200 }
201
202 void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
203 {
204     gint x,y,w,h;
205     XftColor c;
206     gint mw;
207     PangoRectangle rect;
208     PangoAttrList *attrlist;
209     PangoEllipsizeMode ell;
210
211     /* center the text vertically
212        We do this centering based on the 'baseline' since different fonts have
213        different top edges. It looks bad when the whole string is moved when 1
214        character from a non-default language is included in the string */
215     y = area->y +
216         font_calculate_baseline(t->font, area->height);
217
218     /* the +2 and -4 leave a small blank edge on the sides */
219     x = area->x + 2;
220     w = area->width - 4;
221     h = area->height;
222
223     switch (t->ellipsize) {
224     case RR_ELLIPSIZE_NONE:
225         ell = PANGO_ELLIPSIZE_NONE;
226         break;
227     case RR_ELLIPSIZE_START:
228         ell = PANGO_ELLIPSIZE_START;
229         break;
230     case RR_ELLIPSIZE_MIDDLE:
231         ell = PANGO_ELLIPSIZE_MIDDLE;
232         break;
233     case RR_ELLIPSIZE_END:
234         ell = PANGO_ELLIPSIZE_END;
235         break;
236     }
237
238     pango_layout_set_text(t->font->layout, t->string, -1);
239     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
240     pango_layout_set_ellipsize(t->font->layout, ell);
241
242     /* * * end of setting up the layout * * */
243
244     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
245     mw = rect.width;
246
247     /* pango_layout_set_alignment doesn't work with
248        pango_xft_render_layout_line */
249     switch (t->justify) {
250     case RR_JUSTIFY_LEFT:
251         break;
252     case RR_JUSTIFY_RIGHT:
253         x += (w - mw);
254         break;
255     case RR_JUSTIFY_CENTER:
256         x += (w - mw) / 2;
257         break;
258     }
259
260     if (t->shadow_offset_x || t->shadow_offset_y) {
261         c.color.red = t->shadow_color->r | t->shadow_color->r << 8;
262         c.color.green = t->shadow_color->g | t->shadow_color->g << 8;
263         c.color.blue = t->shadow_color->b | t->shadow_color->b << 8;
264         c.color.alpha = 0xffff * t->shadow_alpha / 255;
265         c.pixel = t->shadow_color->pixel;
266
267         /* see below... */
268         pango_xft_render_layout_line
269             (d, &c, pango_layout_get_line(t->font->layout, 0),
270              (x + t->shadow_offset_x) * PANGO_SCALE,
271              (y + t->shadow_offset_y) * PANGO_SCALE);
272     }
273
274     c.color.red = t->color->r | t->color->r << 8;
275     c.color.green = t->color->g | t->color->g << 8;
276     c.color.blue = t->color->b | t->color->b << 8;
277     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
278     c.pixel = t->color->pixel;
279
280     if (t->shortcut) {
281         const gchar *c = t->string + t->shortcut_pos;
282
283         t->font->shortcut_underline->start_index = t->shortcut_pos;
284         t->font->shortcut_underline->end_index = t->shortcut_pos +
285             (g_utf8_next_char(c) - c);
286
287         /* the attributes are owned by the layout.
288            re-add the attributes to the layout after changing the
289            start and end index */
290         attrlist = pango_layout_get_attributes(t->font->layout);
291         pango_attr_list_ref(attrlist);
292         pango_layout_set_attributes(t->font->layout, attrlist);
293         pango_attr_list_unref(attrlist);
294     }
295
296     /* layout_line() uses y to specify the baseline
297        The line doesn't need to be freed, it's a part of the layout */
298     pango_xft_render_layout_line
299         (d, &c, pango_layout_get_line(t->font->layout, 0),
300          x * PANGO_SCALE, y * PANGO_SCALE);
301
302     if (t->shortcut) {
303         t->font->shortcut_underline->start_index = 0;
304         t->font->shortcut_underline->end_index = 0;
305         /* the attributes are owned by the layout.
306            re-add the attributes to the layout after changing the
307            start and end index */
308         attrlist = pango_layout_get_attributes(t->font->layout);
309         pango_attr_list_ref(attrlist);
310         pango_layout_set_attributes(t->font->layout, attrlist);
311         pango_attr_list_unref(attrlist);
312     }
313 }