make rendering the shortcuts much faster
[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     gchar *locale, *p;
38
39     /* get the default language from the locale
40        (based on gtk_get_default_language in gtkmain.c) */
41     locale = g_strdup(setlocale(LC_CTYPE, NULL));
42     if ((p = strchr(locale, '.'))) *p = '\0'; /* strip off the . */
43     if ((p = strchr(locale, '@'))) *p = '\0'; /* strip off the @ */
44
45     /* measure the ascent and descent */
46     metrics = pango_context_get_metrics(inst->pango, f->font_desc,
47                                         pango_language_from_string(locale));
48     f->ascent = pango_font_metrics_get_ascent(metrics);
49     f->descent = pango_font_metrics_get_descent(metrics);
50     pango_font_metrics_unref(metrics);
51
52     g_free(locale);
53 }
54
55 RrFont *RrFontOpen(const RrInstance *inst, const gchar *name, gint size,
56                    RrFontWeight weight, RrFontSlant slant)
57 {
58     RrFont *out;
59     PangoWeight pweight;
60     PangoStyle pstyle;
61     PangoAttrList *attrlist;
62
63     out = g_new(RrFont, 1);
64     out->inst = inst;
65     out->ref = 1;
66     out->font_desc = pango_font_description_new();
67     out->layout = pango_layout_new(inst->pango);
68     out->shortcut_underline = pango_attr_underline_new(PANGO_UNDERLINE_LOW);
69
70     attrlist = pango_attr_list_new();
71     /* shortcut_underline is owned by the attrlist */
72     pango_attr_list_insert(attrlist, out->shortcut_underline);
73     /* the attributes are owned by the layout */
74     pango_layout_set_attributes(out->layout, attrlist);
75     pango_attr_list_unref(attrlist);
76
77     switch (weight) {
78     case RR_FONTWEIGHT_LIGHT:     pweight = PANGO_WEIGHT_LIGHT;     break;
79     case RR_FONTWEIGHT_NORMAL:    pweight = PANGO_WEIGHT_NORMAL;    break;
80     case RR_FONTWEIGHT_SEMIBOLD:  pweight = PANGO_WEIGHT_SEMIBOLD;  break;
81     case RR_FONTWEIGHT_BOLD:      pweight = PANGO_WEIGHT_BOLD;      break;
82     case RR_FONTWEIGHT_ULTRABOLD: pweight = PANGO_WEIGHT_ULTRABOLD; break;
83     default: g_assert_not_reached();
84     }
85
86     switch (slant) {
87     case RR_FONTSLANT_NORMAL:  pstyle = PANGO_STYLE_NORMAL;    break;
88     case RR_FONTSLANT_ITALIC:  pstyle = PANGO_STYLE_ITALIC;    break;
89     case RR_FONTSLANT_OBLIQUE: pstyle = PANGO_STYLE_OBLIQUE;   break;
90     default: g_assert_not_reached();
91     }
92
93     /* setup the font */
94     pango_font_description_set_family(out->font_desc, name);
95     pango_font_description_set_weight(out->font_desc, pweight);
96     pango_font_description_set_style(out->font_desc, pstyle);
97     pango_font_description_set_size(out->font_desc, size * PANGO_SCALE);
98
99     /* setup the layout */
100     pango_layout_set_font_description(out->layout, out->font_desc);
101     pango_layout_set_single_paragraph_mode(out->layout, TRUE);
102     pango_layout_set_ellipsize(out->layout, PANGO_ELLIPSIZE_MIDDLE);
103
104     /* get the ascent and descent */
105     measure_font(inst, out);
106
107     return out;
108 }
109
110 RrFont *RrFontOpenDefault(const RrInstance *inst)
111 {
112     return RrFontOpen(inst, RrDefaultFontFamily, RrDefaultFontSize,
113                       RrDefaultFontWeight, RrDefaultFontSlant);
114 }
115
116 void RrFontRef(RrFont *f)
117 {
118     ++f->ref;
119 }
120
121 void RrFontClose(RrFont *f)
122 {
123     if (f) {
124         if (--f->ref < 1) {
125             g_object_unref(f->layout);
126             pango_font_description_free(f->font_desc);
127             g_free(f);
128         }
129     }
130 }
131
132 static void font_measure_full(const RrFont *f, const gchar *str,
133                               gint *x, gint *y, gint shadow_x, gint shadow_y)
134 {
135     PangoRectangle rect;
136
137     pango_layout_set_text(f->layout, str, -1);
138     pango_layout_set_width(f->layout, -1);
139     pango_layout_get_pixel_extents(f->layout, NULL, &rect);
140     *x = rect.width + ABS(shadow_x);
141     *y = rect.height + ABS(shadow_y);
142 }
143
144 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str,
145                             gint shadow_x, gint shadow_y)
146 {
147     RrSize *size;
148     size = g_new(RrSize, 1);
149     font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y);
150     return size;
151 }
152
153 gint RrFontHeight(const RrFont *f, gint shadow_y)
154 {
155     return (f->ascent + f->descent) / PANGO_SCALE + ABS(shadow_y);
156 }
157
158 static inline int font_calculate_baseline(RrFont *f, gint height)
159 {
160 /* For my own reference:
161  *   _________
162  *  ^space/2  ^height     ^baseline
163  *  v_________|_          |
164  *            | ^ascent   |   _           _
165  *            | |         |  | |_ _____ _| |_ _  _
166  *            | |         |  |  _/ -_) \ /  _| || |
167  *            | v_________v   \__\___/_\_\\__|\_, |
168  *            | ^descent                      |__/
169  *  __________|_v
170  *  ^space/2  |
171  *  V_________v
172  */
173     return (((height * PANGO_SCALE) /* height of the space in pango units */
174              - (f->ascent + f->descent)) /* minus space taken up by text */
175             / 2 /* divided by two -> half of the empty space (this is the top
176                    of the text) */
177             + f->ascent) /* now move down to the baseline */
178         / PANGO_SCALE; /* back to pixels */
179 }
180
181 void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
182 {
183     gint x,y,w,h;
184     XftColor c;
185     gint mw;
186     PangoRectangle rect;
187     PangoAttrList *attrlist;
188
189     /* center the text vertically
190        We do this centering based on the 'baseline' since different fonts have
191        different top edges. It looks bad when the whole string is moved when 1
192        character from a non-default language is included in the string */
193     y = area->y +
194         font_calculate_baseline(t->font, area->height);
195
196     /* the +2 and -4 leave a small blank edge on the sides */
197     x = area->x + 2;
198     w = area->width - 4;
199     h = area->height;
200
201     pango_layout_set_text(t->font->layout, t->string, -1);
202     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
203
204     /* * * end of setting up the layout * * */
205
206     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
207     mw = rect.width;
208
209     /* pango_layout_set_alignment doesn't work with 
210        pango_xft_render_layout_line */
211     switch (t->justify) {
212     case RR_JUSTIFY_LEFT:
213         break;
214     case RR_JUSTIFY_RIGHT:
215         x += (w - mw);
216         break;
217     case RR_JUSTIFY_CENTER:
218         x += (w - mw) / 2;
219         break;
220     }
221
222     t->font->shortcut_underline->start_index = 0;
223     t->font->shortcut_underline->end_index = 0;
224
225     if (t->shadow_offset_x || t->shadow_offset_y) {
226         c.color.red = t->shadow_color->r | t->shadow_color->r << 8;
227         c.color.green = t->shadow_color->g | t->shadow_color->g << 8;
228         c.color.blue = t->shadow_color->b | t->shadow_color->b << 8;
229         c.color.alpha = 0xffff * t->shadow_alpha / 255;
230         c.pixel = t->shadow_color->pixel;
231
232         /* the attributes are owned by the layout.
233            re-add the attributes to the layout after changing the
234            start and end index */
235         attrlist = pango_layout_get_attributes(t->font->layout);
236         pango_attr_list_ref(attrlist);
237         pango_layout_set_attributes(t->font->layout, attrlist);
238         pango_attr_list_unref(attrlist);
239
240         /* see below... */
241         pango_xft_render_layout_line
242             (d, &c, pango_layout_get_line(t->font->layout, 0),
243              (x + t->shadow_offset_x) * PANGO_SCALE,
244              (y + t->shadow_offset_y) * PANGO_SCALE);
245     }
246
247     c.color.red = t->color->r | t->color->r << 8;
248     c.color.green = t->color->g | t->color->g << 8;
249     c.color.blue = t->color->b | t->color->b << 8;
250     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
251     c.pixel = t->color->pixel;
252
253     if (t->shortcut) {
254         const gchar *c = t->string + t->shortcut_pos;
255
256         if (g_utf8_validate(c, -1, NULL)) {
257             t->font->shortcut_underline->start_index = t->shortcut_pos;
258             t->font->shortcut_underline->end_index = t->shortcut_pos +
259                 (g_utf8_next_char(c) - c);
260
261             /* the attributes are owned by the layout.
262                re-add the attributes to the layout after changing the
263                start and end index */
264             attrlist = pango_layout_get_attributes(t->font->layout);
265             pango_attr_list_ref(attrlist);
266             pango_layout_set_attributes(t->font->layout, attrlist);
267             pango_attr_list_unref(attrlist);
268         }
269     }
270
271     /* layout_line() uses y to specify the baseline
272        The line doesn't need to be freed, it's a part of the layout */
273     pango_xft_render_layout_line
274         (d, &c, pango_layout_get_line(t->font->layout, 0),
275          x * PANGO_SCALE, y * PANGO_SCALE);
276 }