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