1 | /* Pango |
2 | * pango-fontmap.c: Font handling |
3 | * |
4 | * Copyright (C) 2000 Red Hat Software |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Library General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Library General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Library General Public |
17 | * License along with this library; if not, write to the |
18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
19 | * Boston, MA 02111-1307, USA. |
20 | */ |
21 | |
22 | #include "config.h" |
23 | |
24 | #include <gio/gio.h> |
25 | |
26 | #include "pango-fontmap-private.h" |
27 | #include "pango-fontset-simple.h" |
28 | #include "pango-impl-utils.h" |
29 | #include <stdlib.h> |
30 | |
31 | static PangoFontset *pango_font_map_real_load_fontset (PangoFontMap *fontmap, |
32 | PangoContext *context, |
33 | const PangoFontDescription *desc, |
34 | PangoLanguage *language); |
35 | |
36 | |
37 | static PangoFontFamily *pango_font_map_real_get_family (PangoFontMap *fontmap, |
38 | const char *name); |
39 | |
40 | static void pango_font_map_real_changed (PangoFontMap *fontmap); |
41 | |
42 | static guint pango_font_map_get_n_items (GListModel *list); |
43 | |
44 | static void pango_font_map_list_model_init (GListModelInterface *iface); |
45 | |
46 | typedef struct { |
47 | guint n_families; |
48 | } PangoFontMapPrivate; |
49 | |
50 | enum |
51 | { |
52 | PROP_0, |
53 | PROP_ITEM_TYPE, |
54 | PROP_N_ITEMS, |
55 | N_PROPERTIES |
56 | }; |
57 | |
58 | static GParamSpec *properties[N_PROPERTIES] = { NULL, }; |
59 | |
60 | G_DEFINE_ABSTRACT_TYPE_WITH_CODE (PangoFontMap, pango_font_map, G_TYPE_OBJECT, |
61 | G_ADD_PRIVATE (PangoFontMap) |
62 | G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, pango_font_map_list_model_init)) |
63 | |
64 | static void |
65 | pango_font_map_get_property (GObject *object, |
66 | guint property_id, |
67 | GValue *value, |
68 | GParamSpec *pspec) |
69 | { |
70 | switch (property_id) |
71 | { |
72 | case PROP_ITEM_TYPE: |
73 | g_value_set_gtype (value, PANGO_TYPE_FONT_FAMILY); |
74 | break; |
75 | |
76 | case PROP_N_ITEMS: |
77 | g_value_set_uint (value, v_uint: pango_font_map_get_n_items (list: G_LIST_MODEL (ptr: object))); |
78 | break; |
79 | |
80 | default: |
81 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
82 | } |
83 | } |
84 | |
85 | static void |
86 | pango_font_map_class_init (PangoFontMapClass *class) |
87 | { |
88 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
89 | |
90 | object_class->get_property = pango_font_map_get_property; |
91 | |
92 | class->load_fontset = pango_font_map_real_load_fontset; |
93 | class->get_family = pango_font_map_real_get_family; |
94 | class->changed = pango_font_map_real_changed; |
95 | |
96 | /** |
97 | * PangoFontMap:item-type: |
98 | * |
99 | * The type of items contained in this list. |
100 | */ |
101 | properties[PROP_ITEM_TYPE] = |
102 | g_param_spec_gtype (name: "item-type" , nick: "" , blurb: "" , G_TYPE_OBJECT, |
103 | flags: G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); |
104 | |
105 | /** |
106 | * PangoFontMap:n-items: |
107 | * |
108 | * The number of items contained in this list. |
109 | */ |
110 | properties[PROP_N_ITEMS] = |
111 | g_param_spec_uint (name: "n-items" , nick: "" , blurb: "" , minimum: 0, G_MAXUINT, default_value: 0, |
112 | flags: G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); |
113 | |
114 | g_object_class_install_properties (oclass: object_class, n_pspecs: N_PROPERTIES, pspecs: properties); |
115 | } |
116 | |
117 | static void |
118 | pango_font_map_init (PangoFontMap *fontmap G_GNUC_UNUSED) |
119 | { |
120 | } |
121 | |
122 | /** |
123 | * pango_font_map_create_context: |
124 | * @fontmap: a `PangoFontMap` |
125 | * |
126 | * Creates a `PangoContext` connected to @fontmap. |
127 | * |
128 | * This is equivalent to [ctor@Pango.Context.new] followed by |
129 | * [method@Pango.Context.set_font_map]. |
130 | * |
131 | * If you are using Pango as part of a higher-level system, |
132 | * that system may have it's own way of create a `PangoContext`. |
133 | * For instance, the GTK toolkit has, among others, |
134 | * gtk_widget_get_pango_context(). Use those instead. |
135 | * |
136 | * Return value: (transfer full): the newly allocated `PangoContext`, |
137 | * which should be freed with g_object_unref(). |
138 | * |
139 | * Since: 1.22 |
140 | */ |
141 | PangoContext * |
142 | pango_font_map_create_context (PangoFontMap *fontmap) |
143 | { |
144 | PangoContext *context; |
145 | |
146 | g_return_val_if_fail (fontmap != NULL, NULL); |
147 | |
148 | context = pango_context_new (); |
149 | pango_context_set_font_map (context, font_map: fontmap); |
150 | |
151 | return context; |
152 | } |
153 | |
154 | /** |
155 | * pango_font_map_load_font: |
156 | * @fontmap: a `PangoFontMap` |
157 | * @context: the `PangoContext` the font will be used with |
158 | * @desc: a `PangoFontDescription` describing the font to load |
159 | * |
160 | * Load the font in the fontmap that is the closest match for @desc. |
161 | * |
162 | * Returns: (transfer full) (nullable): the newly allocated `PangoFont` |
163 | * loaded, or %NULL if no font matched. |
164 | */ |
165 | PangoFont * |
166 | pango_font_map_load_font (PangoFontMap *fontmap, |
167 | PangoContext *context, |
168 | const PangoFontDescription *desc) |
169 | { |
170 | g_return_val_if_fail (fontmap != NULL, NULL); |
171 | |
172 | return PANGO_FONT_MAP_GET_CLASS (fontmap)->load_font (fontmap, context, desc); |
173 | } |
174 | |
175 | /** |
176 | * pango_font_map_list_families: |
177 | * @fontmap: a `PangoFontMap` |
178 | * @families: (out) (array length=n_families) (transfer container): location to |
179 | * store a pointer to an array of `PangoFontFamily` *. |
180 | * This array should be freed with g_free(). |
181 | * @n_families: (out): location to store the number of elements in @families |
182 | * |
183 | * List all families for a fontmap. |
184 | * |
185 | * Note that the returned families are not in any particular order. |
186 | * |
187 | * `PangoFontMap` also implemented the [iface@Gio.ListModel] interface |
188 | * for enumerating families. |
189 | */ |
190 | void |
191 | pango_font_map_list_families (PangoFontMap *fontmap, |
192 | PangoFontFamily ***families, |
193 | int *n_families) |
194 | { |
195 | PangoFontMapPrivate *priv = pango_font_map_get_instance_private (self: fontmap); |
196 | g_return_if_fail (fontmap != NULL); |
197 | |
198 | PANGO_FONT_MAP_GET_CLASS (fontmap)->list_families (fontmap, families, n_families); |
199 | |
200 | /* keep this value for GListModel::changed */ |
201 | priv->n_families = *n_families; |
202 | } |
203 | |
204 | /** |
205 | * pango_font_map_load_fontset: |
206 | * @fontmap: a `PangoFontMap` |
207 | * @context: the `PangoContext` the font will be used with |
208 | * @desc: a `PangoFontDescription` describing the font to load |
209 | * @language: a `PangoLanguage` the fonts will be used for |
210 | * |
211 | * Load a set of fonts in the fontmap that can be used to render |
212 | * a font matching @desc. |
213 | * |
214 | * Returns: (transfer full) (nullable): the newly allocated |
215 | * `PangoFontset` loaded, or %NULL if no font matched. |
216 | */ |
217 | PangoFontset * |
218 | pango_font_map_load_fontset (PangoFontMap *fontmap, |
219 | PangoContext *context, |
220 | const PangoFontDescription *desc, |
221 | PangoLanguage *language) |
222 | { |
223 | g_return_val_if_fail (fontmap != NULL, NULL); |
224 | |
225 | return PANGO_FONT_MAP_GET_CLASS (fontmap)->load_fontset (fontmap, context, desc, language); |
226 | } |
227 | |
228 | static void |
229 | pango_font_map_fontset_add_fonts (PangoFontMap *fontmap, |
230 | PangoContext *context, |
231 | PangoFontsetSimple *fonts, |
232 | PangoFontDescription *desc, |
233 | const char *family) |
234 | { |
235 | PangoFont *font; |
236 | |
237 | pango_font_description_set_family_static (desc, family); |
238 | font = pango_font_map_load_font (fontmap, context, desc); |
239 | if (font) |
240 | pango_fontset_simple_append (fontset: fonts, font); |
241 | } |
242 | |
243 | static PangoFontset * |
244 | pango_font_map_real_load_fontset (PangoFontMap *fontmap, |
245 | PangoContext *context, |
246 | const PangoFontDescription *desc, |
247 | PangoLanguage *language) |
248 | { |
249 | PangoFontDescription *tmp_desc = pango_font_description_copy_static (desc); |
250 | const char *family; |
251 | char **families; |
252 | int i; |
253 | PangoFontsetSimple *fonts; |
254 | static GHashTable *warned_fonts = NULL; /* MT-safe */ |
255 | G_LOCK_DEFINE_STATIC (warned_fonts); |
256 | |
257 | family = pango_font_description_get_family (desc); |
258 | families = g_strsplit (string: family ? family : "" , delimiter: "," , max_tokens: -1); |
259 | |
260 | fonts = pango_fontset_simple_new (language); |
261 | |
262 | for (i = 0; families[i]; i++) |
263 | pango_font_map_fontset_add_fonts (fontmap, |
264 | context, |
265 | fonts, |
266 | desc: tmp_desc, |
267 | family: families[i]); |
268 | |
269 | g_strfreev (str_array: families); |
270 | |
271 | /* The font description was completely unloadable, try with |
272 | * family == "Sans" |
273 | */ |
274 | if (pango_fontset_simple_size (fontset: fonts) == 0) |
275 | { |
276 | char *ctmp1, *ctmp2; |
277 | |
278 | pango_font_description_set_family_static (desc: tmp_desc, |
279 | family: pango_font_description_get_family (desc)); |
280 | |
281 | ctmp1 = pango_font_description_to_string (desc); |
282 | pango_font_description_set_family_static (desc: tmp_desc, family: "Sans" ); |
283 | |
284 | G_LOCK (warned_fonts); |
285 | if (!warned_fonts || !g_hash_table_lookup (hash_table: warned_fonts, key: ctmp1)) |
286 | { |
287 | if (!warned_fonts) |
288 | warned_fonts = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal); |
289 | |
290 | g_hash_table_insert (hash_table: warned_fonts, key: g_strdup (str: ctmp1), GINT_TO_POINTER (1)); |
291 | |
292 | ctmp2 = pango_font_description_to_string (desc: tmp_desc); |
293 | g_warning ("couldn't load font \"%s\", falling back to \"%s\", " |
294 | "expect ugly output." , ctmp1, ctmp2); |
295 | g_free (mem: ctmp2); |
296 | } |
297 | G_UNLOCK (warned_fonts); |
298 | g_free (mem: ctmp1); |
299 | |
300 | pango_font_map_fontset_add_fonts (fontmap, |
301 | context, |
302 | fonts, |
303 | desc: tmp_desc, |
304 | family: "Sans" ); |
305 | } |
306 | |
307 | /* We couldn't try with Sans and the specified style. Try Sans Normal |
308 | */ |
309 | if (pango_fontset_simple_size (fontset: fonts) == 0) |
310 | { |
311 | char *ctmp1, *ctmp2; |
312 | |
313 | pango_font_description_set_family_static (desc: tmp_desc, family: "Sans" ); |
314 | ctmp1 = pango_font_description_to_string (desc: tmp_desc); |
315 | pango_font_description_set_style (desc: tmp_desc, style: PANGO_STYLE_NORMAL); |
316 | pango_font_description_set_weight (desc: tmp_desc, weight: PANGO_WEIGHT_NORMAL); |
317 | pango_font_description_set_variant (desc: tmp_desc, variant: PANGO_VARIANT_NORMAL); |
318 | pango_font_description_set_stretch (desc: tmp_desc, stretch: PANGO_STRETCH_NORMAL); |
319 | |
320 | G_LOCK (warned_fonts); |
321 | if (!warned_fonts || !g_hash_table_lookup (hash_table: warned_fonts, key: ctmp1)) |
322 | { |
323 | g_hash_table_insert (hash_table: warned_fonts, key: g_strdup (str: ctmp1), GINT_TO_POINTER (1)); |
324 | |
325 | ctmp2 = pango_font_description_to_string (desc: tmp_desc); |
326 | |
327 | g_warning ("couldn't load font \"%s\", falling back to \"%s\", " |
328 | "expect ugly output." , ctmp1, ctmp2); |
329 | g_free (mem: ctmp2); |
330 | } |
331 | G_UNLOCK (warned_fonts); |
332 | g_free (mem: ctmp1); |
333 | |
334 | pango_font_map_fontset_add_fonts (fontmap, |
335 | context, |
336 | fonts, |
337 | desc: tmp_desc, |
338 | family: "Sans" ); |
339 | } |
340 | |
341 | pango_font_description_free (desc: tmp_desc); |
342 | |
343 | /* Everything failed, we are screwed, there is no way to continue, |
344 | * but lets just not crash here. |
345 | */ |
346 | if (pango_fontset_simple_size (fontset: fonts) == 0) |
347 | g_warning ("All font fallbacks failed!!!!" ); |
348 | |
349 | return PANGO_FONTSET (fonts); |
350 | } |
351 | |
352 | /** |
353 | * pango_font_map_get_shape_engine_type: |
354 | * @fontmap: a `PangoFontMap` |
355 | * |
356 | * Returns the render ID for shape engines for this fontmap. |
357 | * See the `render_type` field of `PangoEngineInfo`. |
358 | * |
359 | * Return value (transfer none): the ID string for shape engines |
360 | * for this fontmap |
361 | * |
362 | * Since: 1.4 |
363 | * Deprecated: 1.38 |
364 | */ |
365 | const char * |
366 | pango_font_map_get_shape_engine_type (PangoFontMap *fontmap) |
367 | { |
368 | g_return_val_if_fail (PANGO_IS_FONT_MAP (fontmap), NULL); |
369 | |
370 | return PANGO_FONT_MAP_GET_CLASS (fontmap)->shape_engine_type; |
371 | } |
372 | |
373 | /** |
374 | * pango_font_map_get_serial: |
375 | * @fontmap: a `PangoFontMap` |
376 | * |
377 | * Returns the current serial number of @fontmap. |
378 | * |
379 | * The serial number is initialized to an small number larger than zero |
380 | * when a new fontmap is created and is increased whenever the fontmap |
381 | * is changed. It may wrap, but will never have the value 0. Since it can |
382 | * wrap, never compare it with "less than", always use "not equals". |
383 | * |
384 | * The fontmap can only be changed using backend-specific API, like changing |
385 | * fontmap resolution. |
386 | * |
387 | * This can be used to automatically detect changes to a `PangoFontMap`, |
388 | * like in `PangoContext`. |
389 | * |
390 | * Return value: The current serial number of @fontmap. |
391 | * |
392 | * Since: 1.32.4 |
393 | */ |
394 | guint |
395 | pango_font_map_get_serial (PangoFontMap *fontmap) |
396 | { |
397 | g_return_val_if_fail (PANGO_IS_FONT_MAP (fontmap), 0); |
398 | |
399 | if (PANGO_FONT_MAP_GET_CLASS (fontmap)->get_serial) |
400 | return PANGO_FONT_MAP_GET_CLASS (fontmap)->get_serial (fontmap); |
401 | else |
402 | return 1; |
403 | } |
404 | |
405 | static void |
406 | pango_font_map_real_changed (PangoFontMap *fontmap) |
407 | { |
408 | PangoFontMapPrivate *priv = pango_font_map_get_instance_private (self: fontmap); |
409 | guint removed, added; |
410 | |
411 | removed = priv->n_families; |
412 | added = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: fontmap)); |
413 | |
414 | g_list_model_items_changed (list: G_LIST_MODEL (ptr: fontmap), position: 0, removed, added); |
415 | if (removed != added) |
416 | g_object_notify_by_pspec (G_OBJECT (fontmap), pspec: properties[PROP_N_ITEMS]); |
417 | } |
418 | |
419 | /** |
420 | * pango_font_map_changed: |
421 | * @fontmap: a `PangoFontMap` |
422 | * |
423 | * Forces a change in the context, which will cause any `PangoContext` |
424 | * using this fontmap to change. |
425 | * |
426 | * This function is only useful when implementing a new backend |
427 | * for Pango, something applications won't do. Backends should |
428 | * call this function if they have attached extra data to the |
429 | * context and such data is changed. |
430 | * |
431 | * Since: 1.34 |
432 | */ |
433 | void |
434 | pango_font_map_changed (PangoFontMap *fontmap) |
435 | { |
436 | g_return_if_fail (PANGO_IS_FONT_MAP (fontmap)); |
437 | |
438 | if (PANGO_FONT_MAP_GET_CLASS (fontmap)->changed) |
439 | PANGO_FONT_MAP_GET_CLASS (fontmap)->changed (fontmap); |
440 | } |
441 | |
442 | static PangoFontFamily * |
443 | pango_font_map_real_get_family (PangoFontMap *fontmap, |
444 | const char *name) |
445 | { |
446 | PangoFontFamily **families; |
447 | int n_families; |
448 | PangoFontFamily *family; |
449 | int i; |
450 | |
451 | pango_font_map_list_families (fontmap, families: &families, n_families: &n_families); |
452 | |
453 | family = NULL; |
454 | |
455 | for (i = 0; i < n_families; i++) |
456 | { |
457 | if (strcmp (s1: name, s2: pango_font_family_get_name (family: families[i])) == 0) |
458 | { |
459 | family = families[i]; |
460 | break; |
461 | } |
462 | } |
463 | |
464 | g_free (mem: families); |
465 | |
466 | return family; |
467 | } |
468 | |
469 | /** |
470 | * pango_font_map_get_family: |
471 | * @fontmap: a `PangoFontMap` |
472 | * @name: a family name |
473 | * |
474 | * Gets a font family by name. |
475 | * |
476 | * Returns: (transfer none): the `PangoFontFamily` |
477 | * |
478 | * Since: 1.46 |
479 | */ |
480 | PangoFontFamily * |
481 | pango_font_map_get_family (PangoFontMap *fontmap, |
482 | const char *name) |
483 | { |
484 | g_return_val_if_fail (PANGO_IS_FONT_MAP (fontmap), NULL); |
485 | |
486 | return PANGO_FONT_MAP_GET_CLASS (fontmap)->get_family (fontmap, name); |
487 | } |
488 | |
489 | static GType |
490 | pango_font_map_get_item_type (GListModel *list) |
491 | { |
492 | return PANGO_TYPE_FONT_FAMILY; |
493 | } |
494 | |
495 | static guint |
496 | pango_font_map_get_n_items (GListModel *list) |
497 | { |
498 | PangoFontMap *fontmap = PANGO_FONT_MAP (list); |
499 | int n_families; |
500 | |
501 | pango_font_map_list_families (fontmap, NULL, n_families: &n_families); |
502 | |
503 | return (guint)n_families; |
504 | } |
505 | |
506 | static gpointer |
507 | pango_font_map_get_item (GListModel *list, |
508 | guint position) |
509 | { |
510 | PangoFontMap *fontmap = PANGO_FONT_MAP (list); |
511 | PangoFontFamily **families; |
512 | int n_families; |
513 | PangoFontFamily *family; |
514 | |
515 | pango_font_map_list_families (fontmap, families: &families, n_families: &n_families); |
516 | |
517 | if (position < n_families) |
518 | family = g_object_ref (families[position]); |
519 | else |
520 | family = NULL; |
521 | |
522 | g_free (mem: families); |
523 | |
524 | return family; |
525 | } |
526 | |
527 | static void |
528 | pango_font_map_list_model_init (GListModelInterface *iface) |
529 | { |
530 | iface->get_item_type = pango_font_map_get_item_type; |
531 | iface->get_n_items = pango_font_map_get_n_items; |
532 | iface->get_item = pango_font_map_get_item; |
533 | } |
534 | |