use font 8point by default if none is specified
[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        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     /* get the style/slant */
120     if (FcPatternGetInteger(pat, "slant", 0, &ival) == FcResultMatch) {
121         if (ival == FC_SLANT_ITALIC)
122             pango_font_description_set_style(out->font_desc,
123                                              PANGO_STYLE_ITALIC);
124         else if (ival == FC_SLANT_OBLIQUE)
125             pango_font_description_set_style(out->font_desc,
126                                              PANGO_STYLE_OBLIQUE);
127     }
128
129     /* get the size */
130     if (FcPatternGetInteger(pat, "size", 0, &ival) == FcResultMatch)
131         pango_font_description_set_size(out->font_desc, ival * PANGO_SCALE);
132     else if (FcPatternGetInteger(pat, "pixelsize", 0, &ival) == FcResultMatch)
133         pango_font_description_set_absolute_size(out->font_desc,
134                                                  ival * PANGO_SCALE);
135     else
136         pango_font_description_set_size(out->font_desc, 8 * PANGO_SCALE);
137
138     if (FcPatternGetBool(pat, OB_SHADOW, 0, &out->shadow) != FcResultMatch)
139         out->shadow = FALSE;
140
141     if (FcPatternGetInteger(pat, OB_SHADOW_OFFSET, 0, &out->offset) !=
142         FcResultMatch)
143         out->offset = 1;
144
145     if (FcPatternGetInteger(pat, OB_SHADOW_ALPHA, 0, &tint) != FcResultMatch)
146         tint = 25;
147     if (tint > 100) tint = 100;
148     else if (tint < -100) tint = -100;
149     out->tint = tint;
150
151     /* setup the layout */
152     pango_layout_set_font_description(out->layout, out->font_desc);
153     pango_layout_set_single_paragraph_mode(out->layout, TRUE);
154     pango_layout_set_ellipsize(out->layout, PANGO_ELLIPSIZE_MIDDLE);
155
156     /* get the ascent and descent */
157     measure_font(inst, out);
158
159     return out;
160 }
161
162 RrFont *RrFontOpen(const RrInstance *inst, gchar *fontstring)
163 {
164     RrFont *out;
165
166     if (!started) {
167         font_startup();
168         started = TRUE;
169     }
170
171     if ((out = openfont(inst, fontstring)))
172         return out;
173     g_warning(_("Unable to load font: %s\n"), fontstring);
174     g_warning(_("Trying fallback font: %s\n"), "sans");
175
176     if ((out = openfont(inst, "sans")))
177         return out;
178     g_warning(_("Unable to load font: %s\n"), "sans");
179
180     return NULL;
181 }
182
183 void RrFontClose(RrFont *f)
184 {
185     if (f) {
186         g_object_unref(f->layout);
187         pango_font_description_free(f->font_desc);
188         g_free(f);
189     }
190 }
191
192 static void font_measure_full(const RrFont *f, const gchar *str,
193                               gint *x, gint *y)
194 {
195     PangoRectangle rect;
196
197     pango_layout_set_text(f->layout, str, -1);
198     pango_layout_set_width(f->layout, -1);
199     pango_layout_get_pixel_extents(f->layout, NULL, &rect);
200     *x = rect.width + (f->shadow ? ABS(f->offset) : 0);
201     *y = rect.height + (f->shadow ? ABS(f->offset) : 0);
202 }
203
204 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str)
205 {
206     RrSize *size;
207     size = g_new(RrSize, 1);
208     font_measure_full(f, str, &size->width, &size->height);
209     return size;
210 }
211
212 gint RrFontHeight(const RrFont *f)
213 {
214     return (f->ascent + f->descent) / PANGO_SCALE +
215         (f->shadow ? f->offset : 0);
216 }
217
218 static inline int font_calculate_baseline(RrFont *f, gint height)
219 {
220 /* For my own reference:
221  *   _________
222  *  ^space/2  ^height     ^baseline
223  *  v_________|_          |
224  *            | ^ascent   |   _           _
225  *            | |         |  | |_ _____ _| |_ _  _
226  *            | |         |  |  _/ -_) \ /  _| || |
227  *            | v_________v   \__\___/_\_\\__|\_, |
228  *            | ^descent                      |__/
229  *  __________|_v
230  *  ^space/2  |
231  *  V_________v
232  */
233     return (((height * PANGO_SCALE) /* height of the space in pango units */
234              - (f->ascent + f->descent)) /* minus space taken up by text */
235             / 2 /* divided by two -> half of the empty space (this is the top
236                    of the text) */
237             + f->ascent) /* now move down to the baseline */
238         / PANGO_SCALE; /* back to pixels */
239 }
240
241 void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
242 {
243     gint x,y,w,h;
244     XftColor c;
245     gint mw;
246     PangoRectangle rect;
247
248     /* center the text vertically
249        We do this centering based on the 'baseline' since different fonts have
250        different top edges. It looks bad when the whole string is moved when 1
251        character from a non-default language is included in the string */
252     y = area->y +
253         font_calculate_baseline(t->font, area->height);
254
255     /* the +2 and -4 leave a small blank edge on the sides */
256     x = area->x + 2;
257     w = area->width - 4;
258     h = area->height;
259
260     pango_layout_set_text(t->font->layout, t->string, -1);
261     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
262
263     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
264     mw = rect.width;
265
266     /* pango_layout_set_alignment doesn't work with 
267        pango_xft_render_layout_line */
268     switch (t->justify) {
269     case RR_JUSTIFY_LEFT:
270         break;
271     case RR_JUSTIFY_RIGHT:
272         x += (w - mw);
273         break;
274     case RR_JUSTIFY_CENTER:
275         x += (w - mw) / 2;
276         break;
277     }
278
279     if (t->font->shadow) {
280         if (t->font->tint >= 0) {
281             c.color.red = 0;
282             c.color.green = 0;
283             c.color.blue = 0;
284             c.color.alpha = 0xffff * t->font->tint / 100;
285             c.pixel = BlackPixel(RrDisplay(t->font->inst),
286                                  RrScreen(t->font->inst));
287         } else {
288             c.color.red = 0xffff;
289             c.color.green = 0xffff;
290             c.color.blue = 0xffff;
291             c.color.alpha = 0xffff * -t->font->tint / 100;
292             c.pixel = WhitePixel(RrDisplay(t->font->inst),
293                                  RrScreen(t->font->inst));
294         }
295         /* see below... */
296         pango_xft_render_layout_line
297             (d, &c, pango_layout_get_line(t->font->layout, 0),
298              (x + t->font->offset) * PANGO_SCALE,
299              (y + t->font->offset) * PANGO_SCALE);
300     }
301
302     c.color.red = t->color->r | t->color->r << 8;
303     c.color.green = t->color->g | t->color->g << 8;
304     c.color.blue = t->color->b | t->color->b << 8;
305     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
306     c.pixel = t->color->pixel;
307
308     /* layout_line() uses y to specify the baseline
309        The line doesn't need to be freed, it's a part of the layout */
310     pango_xft_render_layout_line
311         (d, &c, pango_layout_get_line(t->font->layout, 0),
312          x * PANGO_SCALE, y * PANGO_SCALE);
313 }