pango is now mandatory..
[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        Ben 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 #define OB_SHADOW "shadow"
35 #define OB_SHADOW_OFFSET "shadowoffset"
36 #define OB_SHADOW_ALPHA "shadowtint"
37
38 FcObjectType objs[] = {
39     { OB_SHADOW,        FcTypeBool    },
40     { OB_SHADOW_OFFSET, FcTypeInteger },
41     { OB_SHADOW_ALPHA,  FcTypeInteger  }
42 };
43
44 static gboolean started = FALSE;
45
46 static void font_startup(void)
47 {
48     if (!XftInit(0)) {
49         g_warning(_("Couldn't initialize Xft."));
50         exit(EXIT_FAILURE);
51     }
52
53     /* Here we are teaching xft about the shadow, shadowoffset & shadowtint */
54     FcNameRegisterObjectTypes(objs, (sizeof(objs) / sizeof(objs[0])));
55 }
56
57 static void measure_font(const RrInstance *inst, RrFont *f)
58 {
59     PangoFontMetrics *metrics;
60     gchar *locale, *p;
61
62     /* get the default language from the locale
63        (based on gtk_get_default_language in gtkmain.c) */
64     locale = g_strdup(setlocale(LC_CTYPE, NULL));
65     if ((p = strchr(locale, '.'))) *p = '\0'; /* strip off the . */
66     if ((p = strchr(locale, '@'))) *p = '\0'; /* strip off the @ */
67
68     /* measure the ascent and descent */
69     metrics = pango_context_get_metrics(inst->pango, f->font_desc,
70                                         pango_language_from_string(locale));
71     f->ascent = pango_font_metrics_get_ascent(metrics);
72     f->descent = pango_font_metrics_get_descent(metrics);
73     pango_font_metrics_unref(metrics);
74
75     g_free(locale);
76 }
77
78 static RrFont *openfont(const RrInstance *inst, gchar *fontstring)
79 {
80     /* This function is called for each font in the theme file. */
81     /* It returns a pointer to a RrFont struct after filling it. */
82     RrFont *out;
83     FcPattern *pat;
84     gint tint;
85     gchar *sval;
86     gint ival;
87
88     if (!(pat = XftNameParse(fontstring)))
89         return NULL;
90
91     out = g_new(RrFont, 1);
92     out->inst = inst;
93     out->font_desc = pango_font_description_new();
94     out->layout = pango_layout_new(inst->pango);
95
96     /* get the data from the parsed xft string */
97
98     /* get the family */
99     if (FcPatternGetString(pat, "family", 0,
100                            (FcChar8**)&sval) == FcResultMatch)
101         pango_font_description_set_family(out->font_desc, sval);
102     
103     /* get the weight */
104     if (FcPatternGetInteger(pat, "weight", 0, &ival) == FcResultMatch) {
105         if (ival == FC_WEIGHT_LIGHT)
106             pango_font_description_set_weight(out->font_desc,
107                                               PANGO_WEIGHT_LIGHT);
108         else if (ival == FC_WEIGHT_DEMIBOLD)
109             pango_font_description_set_weight(out->font_desc,
110                                               PANGO_WEIGHT_SEMIBOLD);
111         else if (ival == FC_WEIGHT_BOLD)
112             pango_font_description_set_weight(out->font_desc,
113                                               PANGO_WEIGHT_BOLD);
114         else if (ival == FC_WEIGHT_BLACK)
115             pango_font_description_set_weight(out->font_desc,
116                                               PANGO_WEIGHT_ULTRABOLD);
117     }
118
119     if (FcPatternGetInteger(pat, "slant", 0, &ival) == FcResultMatch) {
120         if (ival == FC_SLANT_ITALIC)
121             pango_font_description_set_style(out->font_desc,
122                                              PANGO_STYLE_ITALIC);
123         else if (ival == FC_SLANT_OBLIQUE)
124             pango_font_description_set_style(out->font_desc,
125                                              PANGO_STYLE_OBLIQUE);
126     }
127
128     /* get the size */
129     if (FcPatternGetInteger(pat, "size", 0, &ival) == FcResultMatch)
130         pango_font_description_set_size(out->font_desc, ival * PANGO_SCALE);
131     else if (FcPatternGetInteger(pat, "pixelsize", 0, &ival) == FcResultMatch)
132         pango_font_description_set_absolute_size(out->font_desc,
133                                                  ival * PANGO_SCALE);
134
135     if (FcPatternGetBool(pat, OB_SHADOW, 0, &out->shadow) != FcResultMatch)
136         out->shadow = FALSE;
137
138     if (FcPatternGetInteger(pat, OB_SHADOW_OFFSET, 0, &out->offset) !=
139         FcResultMatch)
140         out->offset = 1;
141
142     if (FcPatternGetInteger(pat, OB_SHADOW_ALPHA, 0, &tint) != FcResultMatch)
143         tint = 25;
144     if (tint > 100) tint = 100;
145     else if (tint < -100) tint = -100;
146     out->tint = tint;
147
148     /* setup the layout */
149     pango_layout_set_font_description(out->layout, out->font_desc);
150     pango_layout_set_single_paragraph_mode(out->layout, TRUE);
151     pango_layout_set_ellipsize(out->layout, PANGO_ELLIPSIZE_MIDDLE);
152
153     /* get the ascent and descent */
154     measure_font(inst, out);
155
156     return out;
157 }
158
159 RrFont *RrFontOpen(const RrInstance *inst, gchar *fontstring)
160 {
161     RrFont *out;
162
163     if (!started) {
164         font_startup();
165         started = TRUE;
166     }
167
168     if ((out = openfont(inst, fontstring)))
169         return out;
170     g_warning(_("Unable to load font: %s\n"), fontstring);
171     g_warning(_("Trying fallback font: %s\n"), "sans");
172
173     if ((out = openfont(inst, "sans")))
174         return out;
175     g_warning(_("Unable to load font: %s\n"), "sans");
176
177     return NULL;
178 }
179
180 void RrFontClose(RrFont *f)
181 {
182     if (f) {
183         g_object_unref(f->layout);
184         pango_font_description_free(f->font_desc);
185         g_free(f);
186     }
187 }
188
189 static void font_measure_full(const RrFont *f, const gchar *str,
190                               gint *x, gint *y)
191 {
192     PangoRectangle rect;
193
194     pango_layout_set_text(f->layout, str, -1);
195     pango_layout_set_width(f->layout, -1);
196     pango_layout_get_pixel_extents(f->layout, NULL, &rect);
197     *x = rect.width + (f->shadow ? ABS(f->offset) : 0);
198     *y = rect.height + (f->shadow ? ABS(f->offset) : 0);
199 }
200
201 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str)
202 {
203     RrSize *size;
204     size = g_new(RrSize, 1);
205     font_measure_full(f, str, &size->width, &size->height);
206     return size;
207 }
208
209 gint RrFontHeight(const RrFont *f)
210 {
211     return (f->ascent + f->descent) / PANGO_SCALE +
212         (f->shadow ? f->offset : 0);
213 }
214
215 static inline int font_calculate_baseline(RrFont *f, gint height)
216 {
217 /* For my own reference:
218  *   _________
219  *  ^space/2  ^height     ^baseline
220  *  v_________|_          |
221  *            | ^ascent   |   _           _
222  *            | |         |  | |_ _____ _| |_ _  _
223  *            | |         |  |  _/ -_) \ /  _| || |
224  *            | v_________v   \__\___/_\_\\__|\_, |
225  *            | ^descent                      |__/
226  *  __________|_v
227  *  ^space/2  |
228  *  V_________v
229  */
230     return (((height * PANGO_SCALE) /* height of the space in pango units */
231              - (f->ascent + f->descent)) /* minus space taken up by text */
232             / 2 /* divided by two -> half of the empty space (this is the top
233                    of the text) */
234             + f->ascent) /* now move down to the baseline */
235         / PANGO_SCALE; /* back to pixels */
236 }
237
238 void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
239 {
240     gint x,y,w,h;
241     XftColor c;
242     gint mw;
243     PangoRectangle rect;
244
245     /* center the text vertically
246        We do this centering based on the 'baseline' since different fonts have
247        different top edges. It looks bad when the whole string is moved when 1
248        character from a non-default language is included in the string */
249     y = area->y +
250         font_calculate_baseline(t->font, area->height);
251
252     /* the +2 and -4 leave a small blank edge on the sides */
253     x = area->x + 2;
254     w = area->width - 4;
255     h = area->height;
256
257     pango_layout_set_text(t->font->layout, t->string, -1);
258     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
259
260     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
261     mw = rect.width;
262
263     /* pango_layout_set_alignment doesn't work with 
264        pango_xft_render_layout_line */
265     switch (t->justify) {
266     case RR_JUSTIFY_LEFT:
267         break;
268     case RR_JUSTIFY_RIGHT:
269         x += (w - mw);
270         break;
271     case RR_JUSTIFY_CENTER:
272         x += (w - mw) / 2;
273         break;
274     }
275
276     if (t->font->shadow) {
277         if (t->font->tint >= 0) {
278             c.color.red = 0;
279             c.color.green = 0;
280             c.color.blue = 0;
281             c.color.alpha = 0xffff * t->font->tint / 100;
282             c.pixel = BlackPixel(RrDisplay(t->font->inst),
283                                  RrScreen(t->font->inst));
284         } else {
285             c.color.red = 0xffff;
286             c.color.green = 0xffff;
287             c.color.blue = 0xffff;
288             c.color.alpha = 0xffff * -t->font->tint / 100;
289             c.pixel = WhitePixel(RrDisplay(t->font->inst),
290                                  RrScreen(t->font->inst));
291         }
292         /* see below... */
293         pango_xft_render_layout_line
294             (d, &c, pango_layout_get_line(t->font->layout, 0),
295              (x + t->font->offset) * PANGO_SCALE,
296              (y + t->font->offset) * PANGO_SCALE);
297     }
298
299     c.color.red = t->color->r | t->color->r << 8;
300     c.color.green = t->color->g | t->color->g << 8;
301     c.color.blue = t->color->b | t->color->b << 8;
302     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
303     c.pixel = t->color->pixel;
304
305     /* layout_line() uses y to specify the baseline
306        The line doesn't need to be freed, it's a part of the layout */
307     pango_xft_render_layout_line
308         (d, &c, pango_layout_get_line(t->font->layout, 0),
309          x * PANGO_SCALE, y * PANGO_SCALE);
310 }