fonts are no longer loaded from the theme file. instead, they are created by the...
[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 *openfontstring(const RrInstance *inst, gchar *fontstring)
79 {
80     RrFont *out;
81     FcPattern *pat;
82     gint tint;
83     gchar *sval;
84     gint ival;
85
86     if (!(pat = XftNameParse(fontstring)))
87         return NULL;
88
89     out = g_new(RrFont, 1);
90     out->inst = inst;
91     out->ref = 1;
92     out->font_desc = pango_font_description_new();
93     out->layout = pango_layout_new(inst->pango);
94
95     /* get the data from the parsed xft string */
96
97     /* get the family */
98     if (FcPatternGetString(pat, "family", 0,
99                            (FcChar8**)&sval) == FcResultMatch)
100         pango_font_description_set_family(out->font_desc, sval);
101     
102     /* get the weight */
103     if (FcPatternGetInteger(pat, "weight", 0, &ival) == FcResultMatch) {
104         if (ival == FC_WEIGHT_LIGHT)
105             pango_font_description_set_weight(out->font_desc,
106                                               PANGO_WEIGHT_LIGHT);
107         else if (ival == FC_WEIGHT_DEMIBOLD)
108             pango_font_description_set_weight(out->font_desc,
109                                               PANGO_WEIGHT_SEMIBOLD);
110         else if (ival == FC_WEIGHT_BOLD)
111             pango_font_description_set_weight(out->font_desc,
112                                               PANGO_WEIGHT_BOLD);
113         else if (ival == FC_WEIGHT_BLACK)
114             pango_font_description_set_weight(out->font_desc,
115                                               PANGO_WEIGHT_ULTRABOLD);
116     }
117
118     /* get the style/slant */
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     else
135         pango_font_description_set_size(out->font_desc, 8 * PANGO_SCALE);
136
137     if (FcPatternGetBool(pat, OB_SHADOW, 0, &out->shadow) != FcResultMatch)
138         out->shadow = FALSE;
139
140     if (FcPatternGetInteger(pat, OB_SHADOW_OFFSET, 0, &out->offset) !=
141         FcResultMatch)
142         out->offset = 1;
143
144     if (FcPatternGetInteger(pat, OB_SHADOW_ALPHA, 0, &tint) != FcResultMatch)
145         tint = 25;
146     if (tint > 100) tint = 100;
147     else if (tint < -100) tint = -100;
148     out->tint = tint;
149
150     /* setup the layout */
151     pango_layout_set_font_description(out->layout, out->font_desc);
152     pango_layout_set_single_paragraph_mode(out->layout, TRUE);
153     pango_layout_set_ellipsize(out->layout, PANGO_ELLIPSIZE_MIDDLE);
154
155     /* get the ascent and descent */
156     measure_font(inst, out);
157
158     FcPatternDestroy(pat);
159
160     return out;
161 }
162
163 RrFont *RrFontOpenByString(const RrInstance *inst, gchar *fontstring)
164 {
165     RrFont *out;
166
167     if (!started) {
168         font_startup();
169         started = TRUE;
170     }
171
172     if ((out = openfontstring(inst, fontstring)))
173         return out;
174     g_warning(_("Unable to load font: %s\n"), fontstring);
175     g_warning(_("Trying fallback font: %s\n"), "sans");
176
177     if ((out = openfontstring(inst, "sans")))
178         return out;
179     g_warning(_("Unable to load font: %s\n"), "sans");
180
181     return NULL;
182 }
183
184 RrFont *RrFontOpen(const RrInstance *inst, gchar *name, gint size,
185                    RrFontWeight weight, RrFontSlant slant, gboolean shadow,
186                    gint shadowoffset, gchar shadowtint)
187 {
188     RrFont *out;
189     PangoWeight pweight;
190     PangoStyle pstyle;
191
192     if (!started) {
193         font_startup();
194         started = TRUE;
195     }
196
197     g_assert(shadowtint <= 100 && shadowtint >= -100);
198
199     out = g_new(RrFont, 1);
200     out->inst = inst;
201     out->ref = 1;
202     out->font_desc = pango_font_description_new();
203     out->layout = pango_layout_new(inst->pango);
204
205     switch (weight) {
206     case RR_FONTWEIGHT_LIGHT:     pweight = PANGO_WEIGHT_LIGHT;     break;
207     case RR_FONTWEIGHT_NORMAL:    pweight = PANGO_WEIGHT_NORMAL;    break;
208     case RR_FONTWEIGHT_SEMIBOLD:  pweight = PANGO_WEIGHT_SEMIBOLD;  break;
209     case RR_FONTWEIGHT_BOLD:      pweight = PANGO_WEIGHT_BOLD;      break;
210     case RR_FONTWEIGHT_ULTRABOLD: pweight = PANGO_WEIGHT_ULTRABOLD; break;
211     default: g_assert_not_reached();
212     }
213
214     switch (slant) {
215     case RR_FONTSLANT_NORMAL:  pstyle = PANGO_STYLE_NORMAL;    break;
216     case RR_FONTSLANT_ITALIC:  pstyle = PANGO_STYLE_ITALIC;    break;
217     case RR_FONTSLANT_OBLIQUE: pstyle = PANGO_STYLE_OBLIQUE;   break;
218     default: g_assert_not_reached();
219     }
220
221     /* setup the font */
222     pango_font_description_set_family(out->font_desc, name);
223     pango_font_description_set_weight(out->font_desc, pweight);
224     pango_font_description_set_style(out->font_desc, pstyle);
225     pango_font_description_set_size(out->font_desc, size * PANGO_SCALE);
226
227     /* setup the shadow */
228     out->shadow = shadow;
229     out->offset = shadowoffset;
230     out->tint = shadowtint;
231
232     /* setup the layout */
233     pango_layout_set_font_description(out->layout, out->font_desc);
234     pango_layout_set_single_paragraph_mode(out->layout, TRUE);
235     pango_layout_set_ellipsize(out->layout, PANGO_ELLIPSIZE_MIDDLE);
236
237     /* get the ascent and descent */
238     measure_font(inst, out);
239
240     return out;
241 }
242
243 RrFont *RrFontOpenDefault(const RrInstance *inst)
244 {
245     return RrFontOpen(inst, RrDefaultFontFamily, RrDefaultFontSize,
246                       RrDefaultFontWeight, RrDefaultFontSlant,
247                       RrDefaultFontShadow, RrDefaultFontShadowOffset,
248                       RrDefaultFontShadowTint);
249 }
250
251 void RrFontRef(RrFont *f)
252 {
253     ++f->ref;
254 }
255
256 void RrFontClose(RrFont *f)
257 {
258     if (f) {
259         if (--f->ref < 1) {
260             g_object_unref(f->layout);
261             pango_font_description_free(f->font_desc);
262             g_free(f);
263         }
264     }
265 }
266
267 static void font_measure_full(const RrFont *f, const gchar *str,
268                               gint *x, gint *y)
269 {
270     PangoRectangle rect;
271
272     pango_layout_set_text(f->layout, str, -1);
273     pango_layout_set_width(f->layout, -1);
274     pango_layout_get_pixel_extents(f->layout, NULL, &rect);
275     *x = rect.width + (f->shadow ? ABS(f->offset) : 0);
276     *y = rect.height + (f->shadow ? ABS(f->offset) : 0);
277 }
278
279 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str)
280 {
281     RrSize *size;
282     size = g_new(RrSize, 1);
283     font_measure_full(f, str, &size->width, &size->height);
284     return size;
285 }
286
287 gint RrFontHeight(const RrFont *f)
288 {
289     return (f->ascent + f->descent) / PANGO_SCALE +
290         (f->shadow ? f->offset : 0);
291 }
292
293 static inline int font_calculate_baseline(RrFont *f, gint height)
294 {
295 /* For my own reference:
296  *   _________
297  *  ^space/2  ^height     ^baseline
298  *  v_________|_          |
299  *            | ^ascent   |   _           _
300  *            | |         |  | |_ _____ _| |_ _  _
301  *            | |         |  |  _/ -_) \ /  _| || |
302  *            | v_________v   \__\___/_\_\\__|\_, |
303  *            | ^descent                      |__/
304  *  __________|_v
305  *  ^space/2  |
306  *  V_________v
307  */
308     return (((height * PANGO_SCALE) /* height of the space in pango units */
309              - (f->ascent + f->descent)) /* minus space taken up by text */
310             / 2 /* divided by two -> half of the empty space (this is the top
311                    of the text) */
312             + f->ascent) /* now move down to the baseline */
313         / PANGO_SCALE; /* back to pixels */
314 }
315
316 void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
317 {
318     gint x,y,w,h;
319     XftColor c;
320     gint mw;
321     PangoRectangle rect;
322
323     /* center the text vertically
324        We do this centering based on the 'baseline' since different fonts have
325        different top edges. It looks bad when the whole string is moved when 1
326        character from a non-default language is included in the string */
327     y = area->y +
328         font_calculate_baseline(t->font, area->height);
329
330     /* the +2 and -4 leave a small blank edge on the sides */
331     x = area->x + 2;
332     w = area->width - 4;
333     h = area->height;
334
335     pango_layout_set_text(t->font->layout, t->string, -1);
336     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
337
338     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
339     mw = rect.width;
340
341     /* pango_layout_set_alignment doesn't work with 
342        pango_xft_render_layout_line */
343     switch (t->justify) {
344     case RR_JUSTIFY_LEFT:
345         break;
346     case RR_JUSTIFY_RIGHT:
347         x += (w - mw);
348         break;
349     case RR_JUSTIFY_CENTER:
350         x += (w - mw) / 2;
351         break;
352     }
353
354     if (t->font->shadow) {
355         if (t->font->tint >= 0) {
356             c.color.red = 0;
357             c.color.green = 0;
358             c.color.blue = 0;
359             c.color.alpha = 0xffff * t->font->tint / 100;
360             c.pixel = BlackPixel(RrDisplay(t->font->inst),
361                                  RrScreen(t->font->inst));
362         } else {
363             c.color.red = 0xffff;
364             c.color.green = 0xffff;
365             c.color.blue = 0xffff;
366             c.color.alpha = 0xffff * -t->font->tint / 100;
367             c.pixel = WhitePixel(RrDisplay(t->font->inst),
368                                  RrScreen(t->font->inst));
369         }
370         /* see below... */
371         pango_xft_render_layout_line
372             (d, &c, pango_layout_get_line(t->font->layout, 0),
373              (x + t->font->offset) * PANGO_SCALE,
374              (y + t->font->offset) * PANGO_SCALE);
375     }
376
377     c.color.red = t->color->r | t->color->r << 8;
378     c.color.green = t->color->g | t->color->g << 8;
379     c.color.blue = t->color->b | t->color->b << 8;
380     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
381     c.pixel = t->color->pixel;
382
383     /* layout_line() uses y to specify the baseline
384        The line doesn't need to be freed, it's a part of the layout */
385     pango_xft_render_layout_line
386         (d, &c, pango_layout_get_line(t->font->layout, 0),
387          x * PANGO_SCALE, y * PANGO_SCALE);
388 }