72933e0b7ecc4ed1319190f884400c787c256529
[mikachu/openbox.git] / obrender / 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 void RrFontDescriptionFromString(RrFont *font, gchar *description)
64 {
65     PangoFontDescription *desc;
66     desc = pango_font_description_from_string(description);
67     pango_font_description_merge(font->font_desc, desc, TRUE);
68     pango_font_description_free(desc);
69     pango_layout_set_font_description(font->layout, font->font_desc);
70 }
71
72 RrFont *RrFontOpen(const RrInstance *inst, const gchar *name, gint size,
73                    RrFontWeight weight, RrFontSlant slant)
74 {
75     RrFont *out;
76     PangoWeight pweight;
77     PangoStyle pstyle;
78     PangoAttrList *attrlist;
79
80     out = g_slice_new(RrFont);
81     out->inst = inst;
82     out->ref = 1;
83     out->font_desc = pango_font_description_new();
84     out->layout = pango_layout_new(inst->pango);
85     out->shortcut_underline = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
86     out->shortcut_underline->start_index = 0;
87     out->shortcut_underline->end_index = 0;
88
89     attrlist = pango_attr_list_new();
90     /* shortcut_underline is owned by the attrlist */
91     pango_attr_list_insert(attrlist, out->shortcut_underline);
92     /* the attributes are owned by the layout */
93     pango_layout_set_attributes(out->layout, attrlist);
94     pango_attr_list_unref(attrlist);
95
96     switch (weight) {
97     case RR_FONTWEIGHT_LIGHT:     pweight = PANGO_WEIGHT_LIGHT;     break;
98     case RR_FONTWEIGHT_NORMAL:    pweight = PANGO_WEIGHT_NORMAL;    break;
99     case RR_FONTWEIGHT_SEMIBOLD:  pweight = PANGO_WEIGHT_SEMIBOLD;  break;
100     case RR_FONTWEIGHT_BOLD:      pweight = PANGO_WEIGHT_BOLD;      break;
101     case RR_FONTWEIGHT_ULTRABOLD: pweight = PANGO_WEIGHT_ULTRABOLD; break;
102     default: g_assert_not_reached();
103     }
104
105     switch (slant) {
106     case RR_FONTSLANT_NORMAL:  pstyle = PANGO_STYLE_NORMAL;    break;
107     case RR_FONTSLANT_ITALIC:  pstyle = PANGO_STYLE_ITALIC;    break;
108     case RR_FONTSLANT_OBLIQUE: pstyle = PANGO_STYLE_OBLIQUE;   break;
109     default: g_assert_not_reached();
110     }
111
112     /* setup the font */
113     pango_font_description_set_family(out->font_desc, name);
114     pango_font_description_set_weight(out->font_desc, pweight);
115     pango_font_description_set_style(out->font_desc, pstyle);
116     pango_font_description_set_size(out->font_desc, size * PANGO_SCALE);
117
118     /* setup the layout */
119     pango_layout_set_font_description(out->layout, out->font_desc);
120     pango_layout_set_wrap(out->layout, PANGO_WRAP_WORD_CHAR);
121
122     /* get the ascent and descent */
123     measure_font(inst, out);
124
125     return out;
126 }
127
128 RrFont *RrFontOpenDefault(const RrInstance *inst)
129 {
130     return RrFontOpen(inst, RrDefaultFontFamily, RrDefaultFontSize,
131                       RrDefaultFontWeight, RrDefaultFontSlant);
132 }
133
134 void RrFontRef(RrFont *f)
135 {
136     ++f->ref;
137 }
138
139 void RrFontClose(RrFont *f)
140 {
141     if (f) {
142         if (--f->ref < 1) {
143             g_object_unref(f->layout);
144             pango_font_description_free(f->font_desc);
145             g_slice_free(RrFont, f);
146         }
147     }
148 }
149
150 static void font_measure_full(const RrFont *f, const gchar *str,
151                               gint *x, gint *y, gint shadow_x, gint shadow_y,
152                               gboolean flow, gint maxwidth)
153 {
154     PangoRectangle rect;
155
156     pango_layout_set_text(f->layout, str, -1);
157     if (flow) {
158         pango_layout_set_single_paragraph_mode(f->layout, FALSE);
159         pango_layout_set_width(f->layout, maxwidth * PANGO_SCALE);
160         pango_layout_set_ellipsize(f->layout, PANGO_ELLIPSIZE_NONE);
161     }
162     else {
163         /* single line mode */
164         pango_layout_set_single_paragraph_mode(f->layout, TRUE);
165         pango_layout_set_width(f->layout, -1);
166         pango_layout_set_ellipsize(f->layout, PANGO_ELLIPSIZE_MIDDLE);
167     }
168
169     /* pango_layout_get_pixel_extents lies! this is the right way to get the
170        size of the text's area */
171     pango_layout_get_extents(f->layout, NULL, &rect);
172 #if PANGO_VERSION_MAJOR > 1 || \
173     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
174     /* pass the logical rect as the ink rect, this is on purpose so we get the
175        full area for the text */
176     pango_extents_to_pixels(&rect, NULL);
177 #else
178     rect.width = (rect.width + PANGO_SCALE - 1) / PANGO_SCALE;
179     rect.height = (rect.height + PANGO_SCALE - 1) / PANGO_SCALE;
180 #endif
181     *x = rect.width + /* ABS(shadow_x) +*/ 4 /* we put a 2 px edge on each side */;
182     *y = rect.height /*+ ABS(shadow_y) */;
183 }
184
185 RrSize *RrFontMeasureString(const RrFont *f, const gchar *str,
186                             gint shadow_x, gint shadow_y,
187                             gboolean flow, gint maxwidth)
188 {
189     RrSize *size;
190
191     g_assert(!flow || maxwidth > 0);
192
193     size = g_slice_new(RrSize);
194     font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y,
195                       flow, maxwidth);
196     return size;
197 }
198
199 gint RrFontHeight(const RrFont *f, gint shadow_y)
200 {
201     return (f->ascent + f->descent) / PANGO_SCALE + 0-1;//ABS(shadow_y);
202 }
203
204 static inline int font_calculate_baseline(RrFont *f, gint height)
205 {
206 /* For my own reference:
207  *   _________
208  *  ^space/2  ^height     ^baseline
209  *  v_________|_          |
210  *            | ^ascent   |   _           _
211  *            | |         |  | |_ _____ _| |_ _  _
212  *            | |         |  |  _/ -_) \ /  _| || |
213  *            | v_________v   \__\___/_\_\\__|\_, |
214  *            | ^descent                      |__/
215  *  __________|_v
216  *  ^space/2  |
217  *  V_________v
218  */
219     return (((height * PANGO_SCALE) /* height of the space in pango units */
220              - (f->ascent + f->descent)) /* minus space taken up by text */
221             / 2 /* divided by two -> half of the empty space (this is the top
222                    of the text) */
223             + f->ascent) /* now move down to the baseline */
224         / PANGO_SCALE; /* back to pixels */
225 }
226
227 void RrFontDraw(XftDraw *d, RrTextureText *t, RrRect *area)
228 {
229     gint x,y,w;
230     XftColor c;
231     gint mw;
232     PangoRectangle rect;
233     PangoAttrList *attrlist;
234     PangoEllipsizeMode ell;
235
236     g_assert(!t->flow || t->maxwidth > 0);
237
238     y = area->y;
239     if (!t->flow)
240         /* center the text vertically
241            We do this centering based on the 'baseline' since different fonts
242            have different top edges. It looks bad when the whole string is
243            moved when 1 character from a non-default language is included in
244            the string */
245         y += font_calculate_baseline(t->font, area->height);
246
247     /* the +2 and -4 leave a small blank edge on the sides */
248     x = area->x + 2;
249     w = area->width;
250     if (t->flow) w = MAX(w, t->maxwidth);
251     w -= 4;
252     /* h = area->height; */
253
254     if (t->flow)
255         ell = PANGO_ELLIPSIZE_NONE;
256     else {
257         switch (t->ellipsize) {
258         case RR_ELLIPSIZE_NONE:
259             ell = PANGO_ELLIPSIZE_NONE;
260             break;
261         case RR_ELLIPSIZE_START:
262             ell = PANGO_ELLIPSIZE_START;
263             break;
264         case RR_ELLIPSIZE_MIDDLE:
265             ell = PANGO_ELLIPSIZE_MIDDLE;
266             break;
267         case RR_ELLIPSIZE_END:
268             ell = PANGO_ELLIPSIZE_END;
269             break;
270         default:
271             g_assert_not_reached();
272         }
273     }
274
275     pango_layout_set_text(t->font->layout, t->string, -1);
276     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
277     pango_layout_set_ellipsize(t->font->layout, ell);
278     pango_layout_set_single_paragraph_mode(t->font->layout, !t->flow);
279
280     /* * * end of setting up the layout * * */
281
282     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
283     mw = rect.width;
284
285     /* pango_layout_set_alignment doesn't work with
286        pango_xft_render_layout_line */
287     switch (t->justify) {
288     case RR_JUSTIFY_LEFT:
289         break;
290     case RR_JUSTIFY_RIGHT:
291         x += (w - mw);
292         break;
293     case RR_JUSTIFY_CENTER:
294         x += (w - mw) / 2;
295         break;
296     case RR_JUSTIFY_NUM_TYPES:
297         g_assert_not_reached();
298     }
299
300     if (t->shadow_offset_x || t->shadow_offset_y) {
301         /* From nvidia's readme (chapter 23):
302
303            When rendering to a 32-bit window, keep in mind that the X RENDER
304            extension, used by most composite managers, expects "premultiplied
305            alpha" colors. This means that if your color has components (r,g,b)
306            and alpha value a, then you must render (a*r, a*g, a*b, a) into the
307            target window.
308         */
309         c.color.red = (t->shadow_color->r | t->shadow_color->r << 8) *
310             t->shadow_alpha / 255;
311         c.color.green = (t->shadow_color->g | t->shadow_color->g << 8) *
312             t->shadow_alpha / 255;
313         c.color.blue = (t->shadow_color->b | t->shadow_color->b << 8) *
314             t->shadow_alpha / 255;
315         c.color.alpha = 0xffff * t->shadow_alpha / 255;
316         c.pixel = t->shadow_color->pixel;
317
318         /* see below... */
319         if (!t->flow) {
320             pango_xft_render_layout_line
321                 (d, &c,
322 #if PANGO_VERSION_MAJOR > 1 || \
323     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
324                  pango_layout_get_line_readonly(t->font->layout, 0),
325 #else
326                  pango_layout_get_line(t->font->layout, 0),
327 #endif
328                  (x + t->shadow_offset_x) * PANGO_SCALE,
329                  (y + t->shadow_offset_y) * PANGO_SCALE);
330         }
331         else {
332             pango_xft_render_layout(d, &c, t->font->layout,
333                                     (x + t->shadow_offset_x) * PANGO_SCALE,
334                                     (y + t->shadow_offset_y) * PANGO_SCALE);
335         }
336     }
337
338     c.color.red = t->color->r | t->color->r << 8;
339     c.color.green = t->color->g | t->color->g << 8;
340     c.color.blue = t->color->b | t->color->b << 8;
341     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
342     c.pixel = t->color->pixel;
343
344     if (t->shortcut) {
345         const gchar *s = t->string + t->shortcut_pos;
346
347         t->font->shortcut_underline->start_index = t->shortcut_pos;
348         t->font->shortcut_underline->end_index = t->shortcut_pos +
349             (g_utf8_next_char(s) - s);
350
351         /* the attributes are owned by the layout.
352            re-add the attributes to the layout after changing the
353            start and end index */
354         attrlist = pango_layout_get_attributes(t->font->layout);
355         pango_attr_list_ref(attrlist);
356         pango_layout_set_attributes(t->font->layout, attrlist);
357         pango_attr_list_unref(attrlist);
358     }
359
360     /* layout_line() uses y to specify the baseline
361        The line doesn't need to be freed, it's a part of the layout */
362     if (!t->flow) {
363         pango_xft_render_layout_line
364             (d, &c,
365 #if PANGO_VERSION_MAJOR > 1 || \
366     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
367              pango_layout_get_line_readonly(t->font->layout, 0),
368 #else
369              pango_layout_get_line(t->font->layout, 0),
370 #endif
371              x * PANGO_SCALE,
372              y * PANGO_SCALE);
373     }
374     else {
375         pango_xft_render_layout(d, &c, t->font->layout,
376                                 x * PANGO_SCALE,
377                                 y * PANGO_SCALE);
378     }
379
380     if (t->shortcut) {
381         t->font->shortcut_underline->start_index = 0;
382         t->font->shortcut_underline->end_index = 0;
383         /* the attributes are owned by the layout.
384            re-add the attributes to the layout after changing the
385            start and end index */
386         attrlist = pango_layout_get_attributes(t->font->layout);
387         pango_attr_list_ref(attrlist);
388         pango_layout_set_attributes(t->font->layout, attrlist);
389         pango_attr_list_unref(attrlist);
390     }
391 }