]> icculus.org git repositories - mikachu/openbox.git/blob - render/font.c
pango lies. do the pixel conversion ourselves
[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     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_ellipsize(out->layout, PANGO_ELLIPSIZE_MIDDLE);
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 {
145     PangoRectangle rect;
146
147     pango_layout_set_text(f->layout, str, -1);
148     pango_layout_set_width(f->layout, -1);
149     pango_layout_get_extents(f->layout, NULL, &rect);
150     /* DONT use pango_layout_get_pixel_extents. they say it's guaranteed to
151        fit the text but they lie. figure it out ourselves */
152     *x = (rect.width + PANGO_SCALE - 1) / PANGO_SCALE
153         + ABS(shadow_x) + 4 /* we put a 2 px edge on each side */;
154     *y = (rect.height + PANGO_SCALE - 1) / PANGO_SCALE + ABS(shadow_y);
155 }
156
157 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str,
158                             gint shadow_x, gint shadow_y)
159 {
160     RrSize *size;
161     size = g_new(RrSize, 1);
162     font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y);
163     return size;
164 }
165
166 gint RrFontHeight(const RrFont *f, gint shadow_y)
167 {
168     return (f->ascent + f->descent) / PANGO_SCALE + ABS(shadow_y);
169 }
170
171 static inline int font_calculate_baseline(RrFont *f, gint height)
172 {
173 /* For my own reference:
174  *   _________
175  *  ^space/2  ^height     ^baseline
176  *  v_________|_          |
177  *            | ^ascent   |   _           _
178  *            | |         |  | |_ _____ _| |_ _  _
179  *            | |         |  |  _/ -_) \ /  _| || |
180  *            | v_________v   \__\___/_\_\\__|\_, |
181  *            | ^descent                      |__/
182  *  __________|_v
183  *  ^space/2  |
184  *  V_________v
185  */
186     return (((height * PANGO_SCALE) /* height of the space in pango units */
187              - (f->ascent + f->descent)) /* minus space taken up by text */
188             / 2 /* divided by two -> half of the empty space (this is the top
189                    of the text) */
190             + f->ascent) /* now move down to the baseline */
191         / PANGO_SCALE; /* back to pixels */
192 }
193
194 void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
195 {
196     gint x,y,w,h;
197     XftColor c;
198     gint mw;
199     PangoRectangle rect;
200     PangoAttrList *attrlist;
201
202     /* center the text vertically
203        We do this centering based on the 'baseline' since different fonts have
204        different top edges. It looks bad when the whole string is moved when 1
205        character from a non-default language is included in the string */
206     y = area->y +
207         font_calculate_baseline(t->font, area->height);
208
209     /* the +2 and -4 leave a small blank edge on the sides */
210     x = area->x + 2;
211     w = area->width - 4;
212     h = area->height;
213
214     pango_layout_set_text(t->font->layout, t->string, -1);
215     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
216
217     /* * * end of setting up the layout * * */
218
219     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
220     mw = rect.width;
221
222     /* pango_layout_set_alignment doesn't work with 
223        pango_xft_render_layout_line */
224     switch (t->justify) {
225     case RR_JUSTIFY_LEFT:
226         break;
227     case RR_JUSTIFY_RIGHT:
228         x += (w - mw);
229         break;
230     case RR_JUSTIFY_CENTER:
231         x += (w - mw) / 2;
232         break;
233     }
234
235     if (t->shadow_offset_x || t->shadow_offset_y) {
236         c.color.red = t->shadow_color->r | t->shadow_color->r << 8;
237         c.color.green = t->shadow_color->g | t->shadow_color->g << 8;
238         c.color.blue = t->shadow_color->b | t->shadow_color->b << 8;
239         c.color.alpha = 0xffff * t->shadow_alpha / 255;
240         c.pixel = t->shadow_color->pixel;
241
242         /* see below... */
243         pango_xft_render_layout_line
244             (d, &c, pango_layout_get_line(t->font->layout, 0),
245              (x + t->shadow_offset_x) * PANGO_SCALE,
246              (y + t->shadow_offset_y) * PANGO_SCALE);
247     }
248
249     c.color.red = t->color->r | t->color->r << 8;
250     c.color.green = t->color->g | t->color->g << 8;
251     c.color.blue = t->color->b | t->color->b << 8;
252     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
253     c.pixel = t->color->pixel;
254
255     if (t->shortcut) {
256         const gchar *c = t->string + t->shortcut_pos;
257
258         t->font->shortcut_underline->start_index = t->shortcut_pos;
259         t->font->shortcut_underline->end_index = t->shortcut_pos +
260             (g_utf8_next_char(c) - c);
261
262         /* the attributes are owned by the layout.
263            re-add the attributes to the layout after changing the
264            start and end index */
265         attrlist = pango_layout_get_attributes(t->font->layout);
266         pango_attr_list_ref(attrlist);
267         pango_layout_set_attributes(t->font->layout, attrlist);
268         pango_attr_list_unref(attrlist);
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
277     if (t->shortcut) {
278         t->font->shortcut_underline->start_index = 0;
279         t->font->shortcut_underline->end_index = 0;
280         /* the attributes are owned by the layout.
281            re-add the attributes to the layout after changing the
282            start and end index */
283         attrlist = pango_layout_get_attributes(t->font->layout);
284         pango_attr_list_ref(attrlist);
285         pango_layout_set_attributes(t->font->layout, attrlist);
286         pango_attr_list_unref(attrlist);
287     }
288 }