548e831388ee4780ee46a6465e8a08dffcce7ba8
[vte.git] / src / vtefc.c
1 /*
2  * Copyright (C) 2003 Red Hat, Inc.
3  *
4  * This is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU Library General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #ident "$Id$"
20
21 #include "../config.h"
22
23 #include <stdio.h>
24 #include <string.h>
25 #include <gtk/gtk.h>
26 #include <pango/pango.h>
27 #include <fontconfig/fontconfig.h>
28 #include <glib.h>
29 #include "vtefc.h"
30 #include "vterdb.h"
31
32 static int
33 _vte_fc_weight_from_pango_weight(int weight)
34 {
35         /* Cut-and-pasted from Pango. */
36         if (weight < (PANGO_WEIGHT_NORMAL + PANGO_WEIGHT_LIGHT) / 2)
37                 return FC_WEIGHT_LIGHT;
38         else if (weight < (PANGO_WEIGHT_NORMAL + 600) / 2)
39                 return FC_WEIGHT_MEDIUM;
40         else if (weight < (600 + PANGO_WEIGHT_BOLD) / 2)
41                 return FC_WEIGHT_DEMIBOLD;
42         else if (weight < (PANGO_WEIGHT_BOLD + PANGO_WEIGHT_ULTRABOLD) / 2)
43                 return FC_WEIGHT_BOLD;
44         else
45                 return FC_WEIGHT_BLACK;
46 }
47
48 static int
49 _vte_fc_slant_from_pango_style(int style)
50 {
51         switch (style) {
52         case PANGO_STYLE_NORMAL:
53                 return FC_SLANT_ROMAN;
54                 break;
55         case PANGO_STYLE_ITALIC:
56                 return FC_SLANT_ITALIC;
57                 break;
58         case PANGO_STYLE_OBLIQUE:
59                 return FC_SLANT_OBLIQUE;
60                 break;
61         }
62         return FC_SLANT_ROMAN;
63 }
64
65 static void
66 _vte_fc_transcribe_from_pango_font_description(GtkWidget *widget,
67                                                FcPattern *pattern,
68                                        const PangoFontDescription *font_desc)
69 {
70 #if GTK_CHECK_VERSION(2,2,0)
71         GdkScreen *screen;
72 #endif
73         const char *family = "monospace";
74         PangoLanguage *language;
75         double size = 10.0;
76         int pango_mask;
77         PangoContext *context;
78         int weight, style;
79
80         if (font_desc == NULL) {
81                 return;
82         }
83
84         pango_mask = pango_font_description_get_set_fields(font_desc);
85
86         /* Set the family for the pattern, or use a sensible default. */
87         if (pango_mask & PANGO_FONT_MASK_FAMILY) {
88                 family = pango_font_description_get_family(font_desc);
89         }
90         FcPatternAddString(pattern, FC_FAMILY, family);
91
92         /* Set the font size for the pattern, or use a sensible default. */
93         if (pango_mask & PANGO_FONT_MASK_SIZE) {
94                 size = pango_font_description_get_size(font_desc);
95                 size /= PANGO_SCALE;
96         }
97         FcPatternAddDouble(pattern, FC_SIZE, size);
98
99         /* Set the language for the pattern. */
100 #if GTK_CHECK_VERSION(2,2,0)
101         if (gtk_widget_has_screen(widget)) {
102                 screen = gtk_widget_get_screen(widget);
103         } else {
104                 screen = gdk_display_get_default_screen(gtk_widget_get_display(widget));
105         }
106         context = gdk_pango_context_get_for_screen(screen);
107 #else
108         context = gdk_pango_context_get();
109 #endif
110         language = pango_context_get_language(context);
111         if (pango_language_to_string(language) != NULL) {
112                 FcPatternAddString(pattern, FC_LANG,
113                                    pango_language_to_string(language));
114         }
115
116         /* There aren't any fallbacks for these, so just omit them from the
117          * pattern if they're not set in the pango font. */
118         if (pango_mask & PANGO_FONT_MASK_WEIGHT) {
119                 weight = pango_font_description_get_weight(font_desc);
120                 FcPatternAddInteger(pattern, FC_WEIGHT,
121                                     _vte_fc_weight_from_pango_weight(weight));
122         }
123
124         if (pango_mask & PANGO_FONT_MASK_STYLE) {
125                 style = pango_font_description_get_style(font_desc);
126                 FcPatternAddInteger(pattern, FC_SLANT,
127                                     _vte_fc_slant_from_pango_style(style));
128         }
129
130         g_object_unref(G_OBJECT(context));
131 }
132
133 static void
134 _vte_fc_defaults_from_gtk(GtkWidget *widget, FcPattern *pattern)
135 {
136         GtkSettings *settings;
137 #if GTK_CHECK_VERSION(2,2,0)
138         GdkScreen *screen;
139 #endif
140         GObjectClass *klass;
141         int i, antialias = -1, hinting = -1, dpi = -1;
142         char *rgba = NULL, *hintstyle = NULL;
143
144         /* Add any defaults configured for GTK+. */
145 #if GTK_CHECK_VERSION(2,2,0)
146         if (gtk_widget_has_screen(widget)) {
147                 screen = gtk_widget_get_screen(widget);
148         } else {
149                 screen = gdk_display_get_default_screen(gtk_widget_get_display(widget));
150         }
151         settings = gtk_settings_get_for_screen(screen);
152 #else
153         settings = gtk_settings_get_default();
154 #endif
155         if (settings == NULL) {
156                 return;
157         }
158
159         /* Check that the properties we're looking at are defined. */
160         klass = G_OBJECT_CLASS(GTK_SETTINGS_GET_CLASS(settings));
161         if (g_object_class_find_property(klass, "gtk-xft-antialias") == NULL) {
162                 return;
163         }
164
165         /* Read the settings. */
166         g_object_get(G_OBJECT(settings),
167                      "gtk-xft-antialias", &antialias,
168                      "gtk-xft-dpi", &dpi,
169                      "gtk-xft-rgba", &rgba,
170                      "gtk-xft-hinting", &hinting,
171                      "gtk-xft-hintstyle", &hintstyle,
172                      NULL);
173
174         /* Pick up the antialiasing setting. */
175         if (antialias >= 0) {
176                 FcPatternDel(pattern, FC_ANTIALIAS);
177                 FcPatternAddBool(pattern, FC_ANTIALIAS, antialias > 0);
178         }
179
180         /* Pick up the configured DPI setting. */
181         if (dpi >= 0) {
182                 FcPatternDel(pattern, FC_DPI);
183                 FcPatternAddDouble(pattern, FC_DPI, dpi / 1024.0);
184         }
185
186         /* Pick up the configured subpixel rendering setting. */
187         if (rgba != NULL) {
188                 gboolean found;
189
190                 i = FC_RGBA_NONE;
191
192                 if (g_ascii_strcasecmp(rgba, "none") == 0) {
193                         i = FC_RGBA_NONE;
194                         found = TRUE;
195                 } else
196                 if (g_ascii_strcasecmp(rgba, "rgb") == 0) {
197                         i = FC_RGBA_RGB;
198                         found = TRUE;
199                 } else
200                 if (g_ascii_strcasecmp(rgba, "bgr") == 0) {
201                         i = FC_RGBA_BGR;
202                         found = TRUE;
203                 } else
204                 if (g_ascii_strcasecmp(rgba, "vrgb") == 0) {
205                         i = FC_RGBA_VRGB;
206                         found = TRUE;
207                 } else
208                 if (g_ascii_strcasecmp(rgba, "vbgr") == 0) {
209                         i = FC_RGBA_VBGR;
210                         found = TRUE;
211                 } else {
212                         found = FALSE;
213                 }
214                 if (found) {
215                         FcPatternDel(pattern, FC_RGBA);
216                         FcPatternAddInteger(pattern, FC_RGBA, i);
217                 }
218                 g_free(rgba);
219         }
220
221         /* Pick up the configured hinting setting. */
222         if (hinting >= 0) {
223                 FcPatternDel(pattern, FC_HINTING);
224                 FcPatternAddBool(pattern, FC_HINTING, hinting > 0);
225         }
226
227 #ifdef FC_HINT_STYLE
228         /* Pick up the default hinting style. */
229         if (hintstyle != NULL) {
230                 gboolean found;
231
232                 i = FC_HINT_NONE;
233
234                 if (g_ascii_strcasecmp(hintstyle, "hintnone") == 0) {
235                         i = FC_HINT_NONE;
236                         found = TRUE;
237                 } else
238                 if (g_ascii_strcasecmp(hintstyle, "hintslight") == 0) {
239                         i = FC_HINT_SLIGHT;
240                         found = TRUE;
241                 } else
242                 if (g_ascii_strcasecmp(hintstyle, "hintmedium") == 0) {
243                         i = FC_HINT_MEDIUM;
244                         found = TRUE;
245                 } else
246                 if (g_ascii_strcasecmp(hintstyle, "hintfull") == 0) {
247                         i = FC_HINT_FULL;
248                         found = TRUE;
249                 } else {
250                         found = FALSE;
251                 }
252                 if (found) {
253                         FcPatternDel(pattern, FC_HINT_STYLE);
254                         FcPatternAddInteger(pattern, FC_HINT_STYLE, i);
255                 }
256                 g_free(hintstyle);
257         }
258 #endif
259 }
260
261 static void
262 _vte_fc_defaults_from_rdb(GtkWidget *widget, FcPattern *pattern)
263 {
264         FcBool fcb;
265         double fcd;
266         int antialias = -1, hinting = -1, fci;
267         double dpi;
268         const char *rgba = NULL, *hintstyle = NULL;
269
270         /* Read the settings. */
271         hintstyle = _vte_rdb_get_hintstyle(widget);
272         rgba = _vte_rdb_get_rgba(widget);
273
274         /* Pick up the antialiasing setting. */
275         if (FcPatternGetBool(pattern, FC_ANTIALIAS, 0,
276                              &fcb) == FcResultNoMatch) {
277                 antialias = _vte_rdb_get_antialias(widget);
278                 FcPatternAddBool(pattern, FC_ANTIALIAS, antialias);
279         }
280
281         /* Pick up the hinting setting. */
282         if (FcPatternGetBool(pattern, FC_HINTING, 0,
283                              &fcb) == FcResultNoMatch) {
284                 hinting = _vte_rdb_get_hinting(widget);
285                 FcPatternAddBool(pattern, FC_HINTING, hinting);
286         }
287
288         /* Pick up the configured DPI setting. */
289         if (FcPatternGetDouble(pattern, FC_DPI, 0,
290                                &fcd) == FcResultNoMatch) {
291                 dpi = _vte_rdb_get_dpi(widget);
292                 if (dpi >= 0) {
293                         FcPatternAddDouble(pattern, FC_DPI, dpi);
294                 }
295         }
296
297         /* Pick up the configured subpixel rendering setting. */
298         if (FcPatternGetInteger(pattern, FC_RGBA, 0,
299                                 &fci) == FcResultNoMatch) {
300                 rgba = _vte_rdb_get_rgba(widget);
301                 if (g_ascii_strcasecmp(rgba, "none") == 0) {
302                         FcPatternAddInteger(pattern, FC_RGBA, FC_RGBA_NONE);
303                 } else
304                 if (g_ascii_strcasecmp(rgba, "rgb") == 0) {
305                         FcPatternAddInteger(pattern, FC_RGBA, FC_RGBA_RGB);
306                 } else
307                 if (g_ascii_strcasecmp(rgba, "bgr") == 0) {
308                         FcPatternAddInteger(pattern, FC_RGBA, FC_RGBA_BGR);
309                 } else
310                 if (g_ascii_strcasecmp(rgba, "vrgb") == 0) {
311                         FcPatternAddInteger(pattern, FC_RGBA, FC_RGBA_VRGB);
312                 } else
313                 if (g_ascii_strcasecmp(rgba, "vbgr") == 0) {
314                         FcPatternAddInteger(pattern, FC_RGBA, FC_RGBA_VBGR);
315                 }
316         }
317
318 #ifdef FC_HINT_STYLE
319         /* Pick up the default hinting style. */
320         if (FcPatternGetInteger(pattern, FC_HINT_STYLE, 0,
321                                 &fci) == FcResultNoMatch) {
322                 hintstyle = _vte_rdb_get_hintstyle(widget);
323                 if (g_ascii_strcasecmp(hintstyle, "hintnone") == 0) {
324                         FcPatternAddInteger(pattern, FC_HINT_STYLE,
325                                             FC_HINT_NONE);
326                 } else
327                 if (g_ascii_strcasecmp(hintstyle, "hintslight") == 0) {
328                         FcPatternAddInteger(pattern, FC_HINT_STYLE,
329                                             FC_HINT_SLIGHT);
330                 } else
331                 if (g_ascii_strcasecmp(hintstyle, "hintmedium") == 0) {
332                         FcPatternAddInteger(pattern, FC_HINT_STYLE,
333                                             FC_HINT_MEDIUM);
334                 } else
335                 if (g_ascii_strcasecmp(hintstyle, "hintfull") == 0) {
336                         FcPatternAddInteger(pattern, FC_HINT_STYLE,
337                                             FC_HINT_FULL);
338                 }
339         }
340 #endif
341 }
342
343 /* Create a sorted set of fontconfig patterns from a Pango font description
344  * and append them to the array. */
345 gboolean
346 _vte_fc_patterns_from_pango_font_desc(GtkWidget *widget,
347                                       const PangoFontDescription *font_desc,
348                                       GArray *pattern_array,
349                                       _vte_fc_defaults_cb defaults_cb,
350                                       gpointer defaults_data)
351
352 {
353         FcPattern *pattern, *match, *tmp, *save;
354         FcFontSet *fontset;
355         FcResult result;
356         gboolean ret = FALSE;
357         int i;
358
359         g_return_val_if_fail(pattern_array != NULL, FALSE);
360
361         /* Create a scratch pattern. */
362         pattern = FcPatternCreate();
363
364         /* Transcribe what we can get from the Pango font description. */
365         _vte_fc_transcribe_from_pango_font_description(widget, pattern,
366                                                        font_desc);
367
368         /* Add any defaults specified in the configuration. */
369         FcConfigSubstitute(NULL, pattern, FcMatchPattern);
370
371         /* Add any defaults configured for GTK+. */
372         _vte_fc_defaults_from_gtk(widget, pattern);
373
374         /* Add defaults configured via the resource database. */
375         _vte_fc_defaults_from_rdb(widget, pattern);
376
377         /* Add any defaults which are hard-coded in fontconfig. */
378         FcDefaultSubstitute(pattern);
379
380         /* Add any defaults via a callback. */
381         if (defaults_cb != NULL) {
382                 defaults_cb(pattern, defaults_data);
383         }
384
385         /* Get a sorted list of patterns, duplicate them, and append them
386          * to the passed-in array. */
387         fontset = FcFontSort(NULL, pattern, FcTrue, NULL, &result);
388         if (fontset != NULL) {
389                 for (i = 0; i < fontset->nfont; i++) {
390                         tmp = FcFontRenderPrepare(NULL,
391                                                   pattern,
392                                                   fontset->fonts[i]);
393                         _vte_fc_defaults_from_gtk(widget, tmp);
394                         save = FcPatternDuplicate(tmp);
395                         FcPatternDestroy(tmp);
396                         g_array_append_val(pattern_array, save);
397                 }
398                 FcFontSetDestroy(fontset);
399                 ret = TRUE;
400         }
401
402         /* Last ditch effort. */
403         if (pattern_array->len == 0) {
404                 match = FcFontMatch(NULL, pattern, &result);
405                 if (result == FcResultMatch) {
406                         tmp = FcPatternDuplicate(match);
407                         _vte_fc_defaults_from_gtk(widget, tmp);
408                         save = FcPatternDuplicate(tmp);
409                         FcPatternDestroy(tmp);
410                         g_array_append_val(pattern_array, save);
411                         ret = TRUE;
412                 } else {
413                         ret = FALSE;
414                 }
415         }
416
417         FcPatternDestroy(pattern);
418
419         return ret;
420 }
421
422 void
423 _vte_fc_connect_settings_changes(GtkWidget *widget, GCallback *changed_cb)
424 {
425         GtkSettings *settings;
426         GObjectClass *klass;
427
428         /* Get the settings object used by the widget. */
429         settings = gtk_widget_get_settings(widget);
430         if (settings == NULL) {
431                 return;
432         }
433
434         /* Check that the properties we're looking at are defined. */
435         klass = G_OBJECT_CLASS(GTK_SETTINGS_GET_CLASS(settings));
436         if (g_object_class_find_property(klass, "gtk-xft-antialias") == NULL) {
437                 return;
438         }
439
440         /* Start listening for changes to the fontconfig settings. */
441         g_signal_connect(G_OBJECT(settings),
442                          "notify::gtk-xft-antialias",
443                          G_CALLBACK(changed_cb), widget);
444         g_signal_connect(G_OBJECT(settings),
445                          "notify::gtk-xft-hinting",
446                          G_CALLBACK(changed_cb), widget);
447         g_signal_connect(G_OBJECT(settings),
448                          "notify::gtk-xft-hintstyle",
449                          G_CALLBACK(changed_cb), widget);
450         g_signal_connect(G_OBJECT(settings),
451                          "notify::gtk-xft-rgba",
452                          G_CALLBACK(changed_cb), widget);
453         g_signal_connect(G_OBJECT(settings),
454                          "notify::gtk-xft-dpi",
455                          G_CALLBACK(changed_cb), widget);
456 }
457
458 void
459 _vte_fc_disconnect_settings_changes(GtkWidget *widget, GCallback *changed_cb)
460 {
461         GtkSettings *settings;
462
463         /* Get the settings object used by the widget. */
464         settings = gtk_widget_get_settings(widget);
465         if (settings == NULL) {
466                 return;
467         }
468
469         /* Stop listening for changes to the fontconfig settings. */
470         g_signal_handlers_disconnect_matched(G_OBJECT(settings),
471                                              G_SIGNAL_MATCH_FUNC |
472                                              G_SIGNAL_MATCH_DATA,
473                                              0, 0, NULL,
474                                              changed_cb,
475                                              widget);
476 }