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