]> icculus.org git repositories - mikachu/openbox.git/blob - render/font.c
We shouldn't enforce incr when the app resizes itself. But it's so confusing.
[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 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_new(RrFont, 1);
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_LOW);
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_free(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_new(RrSize, 1);
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,h;
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         }
271     }
272
273     pango_layout_set_text(t->font->layout, t->string, -1);
274     pango_layout_set_width(t->font->layout, w * PANGO_SCALE);
275     pango_layout_set_ellipsize(t->font->layout, ell);
276     pango_layout_set_single_paragraph_mode(t->font->layout, !t->flow);
277
278     /* * * end of setting up the layout * * */
279
280     pango_layout_get_pixel_extents(t->font->layout, NULL, &rect);
281     mw = rect.width;
282
283     /* pango_layout_set_alignment doesn't work with
284        pango_xft_render_layout_line */
285     switch (t->justify) {
286     case RR_JUSTIFY_LEFT:
287         break;
288     case RR_JUSTIFY_RIGHT:
289         x += (w - mw);
290         break;
291     case RR_JUSTIFY_CENTER:
292         x += (w - mw) / 2;
293         break;
294     }
295
296     if (t->shadow_offset_x || t->shadow_offset_y) {
297         /* From nvidia's readme (chapter 23):
298
299            When rendering to a 32-bit window, keep in mind that the X RENDER
300            extension, used by most composite managers, expects "premultiplied
301            alpha" colors. This means that if your color has components (r,g,b)
302            and alpha value a, then you must render (a*r, a*g, a*b, a) into the
303            target window.
304         */
305         c.color.red = (t->shadow_color->r | t->shadow_color->r << 8) *
306             t->shadow_alpha / 255;
307         c.color.green = (t->shadow_color->g | t->shadow_color->g << 8) *
308             t->shadow_alpha / 255;
309         c.color.blue = (t->shadow_color->b | t->shadow_color->b << 8) *
310             t->shadow_alpha / 255;
311         c.color.alpha = 0xffff * t->shadow_alpha / 255;
312         c.pixel = t->shadow_color->pixel;
313
314         /* see below... */
315         if (!t->flow) {
316             pango_xft_render_layout_line
317                 (d, &c,
318 #if PANGO_VERSION_MAJOR > 1 || \
319     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
320                  pango_layout_get_line_readonly(t->font->layout, 0),
321 #else
322                  pango_layout_get_line(t->font->layout, 0),
323 #endif
324                  (x + t->shadow_offset_x) * PANGO_SCALE,
325                  (y + t->shadow_offset_y) * PANGO_SCALE);
326         }
327         else {
328             pango_xft_render_layout(d, &c, t->font->layout,
329                                     (x + t->shadow_offset_x) * PANGO_SCALE,
330                                     (y + t->shadow_offset_y) * PANGO_SCALE);
331         }
332     }
333
334     c.color.red = t->color->r | t->color->r << 8;
335     c.color.green = t->color->g | t->color->g << 8;
336     c.color.blue = t->color->b | t->color->b << 8;
337     c.color.alpha = 0xff | 0xff << 8; /* fully opaque text */
338     c.pixel = t->color->pixel;
339
340     if (t->shortcut) {
341         const gchar *s = t->string + t->shortcut_pos;
342
343         t->font->shortcut_underline->start_index = t->shortcut_pos;
344         t->font->shortcut_underline->end_index = t->shortcut_pos +
345             (g_utf8_next_char(s) - s);
346
347         /* the attributes are owned by the layout.
348            re-add the attributes to the layout after changing the
349            start and end index */
350         attrlist = pango_layout_get_attributes(t->font->layout);
351         pango_attr_list_ref(attrlist);
352         pango_layout_set_attributes(t->font->layout, attrlist);
353         pango_attr_list_unref(attrlist);
354     }
355
356     /* layout_line() uses y to specify the baseline
357        The line doesn't need to be freed, it's a part of the layout */
358     if (!t->flow) {
359         pango_xft_render_layout_line
360             (d, &c,
361 #if PANGO_VERSION_MAJOR > 1 || \
362     (PANGO_VERSION_MAJOR == 1 && PANGO_VERSION_MINOR >= 16)
363              pango_layout_get_line_readonly(t->font->layout, 0),
364 #else
365              pango_layout_get_line(t->font->layout, 0),
366 #endif
367              x * PANGO_SCALE,
368              y * PANGO_SCALE);
369     }
370     else {
371         pango_xft_render_layout(d, &c, t->font->layout,
372                                 x * PANGO_SCALE,
373                                 y * PANGO_SCALE);
374     }
375
376     if (t->shortcut) {
377         t->font->shortcut_underline->start_index = 0;
378         t->font->shortcut_underline->end_index = 0;
379         /* the attributes are owned by the layout.
380            re-add the attributes to the layout after changing the
381            start and end index */
382         attrlist = pango_layout_get_attributes(t->font->layout);
383         pango_attr_list_ref(attrlist);
384         pango_layout_set_attributes(t->font->layout, attrlist);
385         pango_attr_list_unref(attrlist);
386     }
387 }