1 | /* Pango |
2 | * pangofc-fontmap.c: Base fontmap type for fontconfig-based backends |
3 | * |
4 | * Copyright (C) 2000-2003 Red Hat, Inc. |
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 | /** |
23 | * PangoFcFontMap: |
24 | * |
25 | * `PangoFcFontMap` is a base class for font map implementations using the |
26 | * Fontconfig and FreeType libraries. |
27 | * |
28 | * It is used in the Xft and FreeType backends shipped with Pango, |
29 | * but can also be used when creating new backends. Any backend |
30 | * deriving from this base class will take advantage of the wide |
31 | * range of shapers implemented using FreeType that come with Pango. |
32 | */ |
33 | #define FONTSET_CACHE_SIZE 256 |
34 | |
35 | #include "config.h" |
36 | #include <math.h> |
37 | |
38 | #include <gio/gio.h> |
39 | |
40 | #include "pango-context.h" |
41 | #include "pango-font-private.h" |
42 | #include "pangofc-fontmap-private.h" |
43 | #include "pangofc-private.h" |
44 | #include "pango-impl-utils.h" |
45 | #include "pango-enum-types.h" |
46 | #include "pango-coverage-private.h" |
47 | #include "pango-trace-private.h" |
48 | #include <hb-ft.h> |
49 | |
50 | |
51 | /* Overview: |
52 | * |
53 | * All programming is a practice in caching data. PangoFcFontMap is the |
54 | * major caching container of a Pango system on a Linux desktop. Here is |
55 | * a short overview of how it all works. |
56 | * |
57 | * In short, Fontconfig search patterns are constructed and a fontset loaded |
58 | * using them. Here is how we achieve that: |
59 | * |
60 | * - All FcPattern's referenced by any object in the fontmap are uniquified |
61 | * and cached in the fontmap. This both speeds lookups based on patterns |
62 | * faster, and saves memory. This is handled by fontmap->priv->pattern_hash. |
63 | * The patterns are cached indefinitely. |
64 | * |
65 | * - The results of a FcFontSort() are used to populate fontsets. However, |
66 | * FcFontSort() relies on the search pattern only, which includes the font |
67 | * size but not the full font matrix. The fontset however depends on the |
68 | * matrix. As a result, multiple fontsets may need results of the |
69 | * FcFontSort() on the same input pattern (think rotating text). As such, |
70 | * we cache FcFontSort() results in fontmap->priv->patterns_hash which |
71 | * is a refcounted structure. This level of abstraction also allows for |
72 | * optimizations like calling FcFontMatch() instead of FcFontSort(), and |
73 | * only calling FcFontSort() if any patterns other than the first match |
74 | * are needed. Another possible optimization would be to call FcFontSort() |
75 | * without trimming, and do the trimming lazily as we go. Only pattern sets |
76 | * already referenced by a fontset are cached. |
77 | * |
78 | * - A number of most-recently-used fontsets are cached and reused when |
79 | * needed. This is achieved using fontmap->priv->fontset_hash and |
80 | * fontmap->priv->fontset_cache. |
81 | * |
82 | * - All fonts created by any of our fontsets are also cached and reused. |
83 | * This is what fontmap->priv->font_hash does. |
84 | * |
85 | * - Data that only depends on the font file and face index is cached and |
86 | * reused by multiple fonts. This includes coverage and cmap cache info. |
87 | * This is done using fontmap->priv->font_face_data_hash. |
88 | * |
89 | * Upon a cache_clear() request, all caches are emptied. All objects (fonts, |
90 | * fontsets, faces, families) having a reference from outside will still live |
91 | * and may reference the fontmap still, but will not be reused by the fontmap. |
92 | * |
93 | * |
94 | * Todo: |
95 | * |
96 | * - Make PangoCoverage a GObject and subclass it as PangoFcCoverage which |
97 | * will directly use FcCharset. (#569622) |
98 | * |
99 | * - Lazy trimming of FcFontSort() results. Requires fontconfig with |
100 | * FcCharSetMerge(). |
101 | */ |
102 | |
103 | typedef enum { |
104 | /* Initial state; Fontconfig is not initialized yet */ |
105 | DEFAULT_CONFIG_NOT_INITIALIZED, |
106 | |
107 | /* We have a thread doing Fontconfig initialization in the background */ |
108 | DEFAULT_CONFIG_INITIALIZING, |
109 | |
110 | /* FcInit() finished and its default configuration is loaded */ |
111 | DEFAULT_CONFIG_INITIALIZED |
112 | } DefaultConfig; |
113 | |
114 | /* We call FcInit in a thread and set fc_initialized |
115 | * when done, and are protected by a mutex. The thread |
116 | * signals the cond when FcInit is done. |
117 | */ |
118 | static GMutex fc_init_mutex; |
119 | static GCond fc_init_cond; |
120 | static DefaultConfig fc_initialized = DEFAULT_CONFIG_NOT_INITIALIZED; |
121 | |
122 | |
123 | typedef struct _PangoFcFontFaceData PangoFcFontFaceData; |
124 | typedef struct _PangoFcFace PangoFcFace; |
125 | typedef struct _PangoFcFamily PangoFcFamily; |
126 | typedef struct _PangoFcFindFuncInfo PangoFcFindFuncInfo; |
127 | typedef struct _PangoFcPatterns PangoFcPatterns; |
128 | typedef struct _PangoFcFontset PangoFcFontset; |
129 | |
130 | #define PANGO_FC_TYPE_FAMILY (pango_fc_family_get_type ()) |
131 | #define PANGO_FC_FAMILY(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), PANGO_FC_TYPE_FAMILY, PangoFcFamily)) |
132 | #define PANGO_FC_IS_FAMILY(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), PANGO_FC_TYPE_FAMILY)) |
133 | |
134 | #define PANGO_FC_TYPE_FACE (pango_fc_face_get_type ()) |
135 | #define PANGO_FC_FACE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), PANGO_FC_TYPE_FACE, PangoFcFace)) |
136 | #define PANGO_FC_IS_FACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), PANGO_FC_TYPE_FACE)) |
137 | |
138 | #define PANGO_FC_TYPE_FONTSET (pango_fc_fontset_get_type ()) |
139 | #define PANGO_FC_FONTSET(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), PANGO_FC_TYPE_FONTSET, PangoFcFontset)) |
140 | #define PANGO_FC_IS_FONTSET(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), PANGO_FC_TYPE_FONTSET)) |
141 | |
142 | struct _PangoFcFontMapPrivate |
143 | { |
144 | GHashTable *fontset_hash; /* Maps PangoFcFontsetKey -> PangoFcFontset */ |
145 | GQueue *fontset_cache; /* Recently used fontsets */ |
146 | |
147 | GHashTable *font_hash; /* Maps PangoFcFontKey -> PangoFcFont */ |
148 | |
149 | GHashTable *patterns_hash; /* Maps FcPattern -> PangoFcPatterns */ |
150 | |
151 | /* pattern_hash is used to make sure we only store one copy of |
152 | * each identical pattern. (Speeds up lookup). |
153 | */ |
154 | GHashTable *pattern_hash; |
155 | |
156 | GHashTable *font_face_data_hash; /* Maps font file name/id -> data */ |
157 | |
158 | /* List of all families available */ |
159 | PangoFcFamily **families; |
160 | int n_families; /* -1 == uninitialized */ |
161 | |
162 | double dpi; |
163 | |
164 | /* Decoders */ |
165 | GSList *findfuncs; |
166 | |
167 | guint closed : 1; |
168 | |
169 | FcConfig *config; |
170 | FcFontSet *fonts; |
171 | }; |
172 | |
173 | struct _PangoFcFontFaceData |
174 | { |
175 | /* Key */ |
176 | char *filename; |
177 | int id; /* needed to handle TTC files with multiple faces */ |
178 | |
179 | /* Data */ |
180 | FcPattern *pattern; /* Referenced pattern that owns filename */ |
181 | PangoCoverage *coverage; |
182 | PangoLanguage **languages; |
183 | |
184 | hb_face_t *hb_face; |
185 | }; |
186 | |
187 | struct _PangoFcFace |
188 | { |
189 | PangoFontFace parent_instance; |
190 | |
191 | PangoFcFamily *family; |
192 | char *style; |
193 | FcPattern *pattern; |
194 | |
195 | guint fake : 1; |
196 | guint regular : 1; |
197 | }; |
198 | |
199 | struct _PangoFcFamily |
200 | { |
201 | PangoFontFamily parent_instance; |
202 | |
203 | PangoFcFontMap *fontmap; |
204 | char *family_name; |
205 | |
206 | FcFontSet *patterns; |
207 | PangoFcFace **faces; |
208 | int n_faces; /* -1 == uninitialized */ |
209 | |
210 | int spacing; /* FC_SPACING */ |
211 | gboolean variable; |
212 | }; |
213 | |
214 | struct _PangoFcFindFuncInfo |
215 | { |
216 | PangoFcDecoderFindFunc findfunc; |
217 | gpointer user_data; |
218 | GDestroyNotify dnotify; |
219 | gpointer ddata; |
220 | }; |
221 | |
222 | static GType pango_fc_family_get_type (void); |
223 | static GType pango_fc_face_get_type (void); |
224 | static GType pango_fc_fontset_get_type (void); |
225 | |
226 | static void pango_fc_font_map_finalize (GObject *object); |
227 | static PangoFont * pango_fc_font_map_load_font (PangoFontMap *fontmap, |
228 | PangoContext *context, |
229 | const PangoFontDescription *description); |
230 | static PangoFontset *pango_fc_font_map_load_fontset (PangoFontMap *fontmap, |
231 | PangoContext *context, |
232 | const PangoFontDescription *desc, |
233 | PangoLanguage *language); |
234 | static void pango_fc_font_map_list_families (PangoFontMap *fontmap, |
235 | PangoFontFamily ***families, |
236 | int *n_families); |
237 | static PangoFontFamily *pango_fc_font_map_get_family (PangoFontMap *fontmap, |
238 | const char *name); |
239 | |
240 | static double pango_fc_font_map_get_resolution (PangoFcFontMap *fcfontmap, |
241 | PangoContext *context); |
242 | static PangoFont *pango_fc_font_map_new_font (PangoFcFontMap *fontmap, |
243 | PangoFcFontsetKey *fontset_key, |
244 | FcPattern *match); |
245 | |
246 | static PangoFontFace *pango_fc_font_map_get_face (PangoFontMap *fontmap, |
247 | PangoFont *font); |
248 | |
249 | static void pango_fc_font_map_changed (PangoFontMap *fontmap); |
250 | |
251 | static guint pango_fc_font_face_data_hash (PangoFcFontFaceData *key); |
252 | static gboolean pango_fc_font_face_data_equal (PangoFcFontFaceData *key1, |
253 | PangoFcFontFaceData *key2); |
254 | |
255 | static void pango_fc_fontset_key_init (PangoFcFontsetKey *key, |
256 | PangoFcFontMap *fcfontmap, |
257 | PangoContext *context, |
258 | const PangoFontDescription *desc, |
259 | PangoLanguage *language); |
260 | static PangoFcFontsetKey *pango_fc_fontset_key_copy (const PangoFcFontsetKey *key); |
261 | static void pango_fc_fontset_key_free (PangoFcFontsetKey *key); |
262 | static guint pango_fc_fontset_key_hash (const PangoFcFontsetKey *key); |
263 | static gboolean pango_fc_fontset_key_equal (const PangoFcFontsetKey *key_a, |
264 | const PangoFcFontsetKey *key_b); |
265 | |
266 | static void pango_fc_font_key_init (PangoFcFontKey *key, |
267 | PangoFcFontMap *fcfontmap, |
268 | PangoFcFontsetKey *fontset_key, |
269 | FcPattern *pattern); |
270 | static PangoFcFontKey *pango_fc_font_key_copy (const PangoFcFontKey *key); |
271 | static void pango_fc_font_key_free (PangoFcFontKey *key); |
272 | static guint pango_fc_font_key_hash (const PangoFcFontKey *key); |
273 | static gboolean pango_fc_font_key_equal (const PangoFcFontKey *key_a, |
274 | const PangoFcFontKey *key_b); |
275 | |
276 | static PangoFcPatterns *pango_fc_patterns_new (FcPattern *pat, |
277 | PangoFcFontMap *fontmap); |
278 | static PangoFcPatterns *pango_fc_patterns_ref (PangoFcPatterns *pats); |
279 | static void pango_fc_patterns_unref (PangoFcPatterns *pats); |
280 | static FcPattern *pango_fc_patterns_get_pattern (PangoFcPatterns *pats); |
281 | static FcPattern *pango_fc_patterns_get_font_pattern (PangoFcPatterns *pats, |
282 | int i, |
283 | gboolean *prepare); |
284 | |
285 | static FcPattern *uniquify_pattern (PangoFcFontMap *fcfontmap, |
286 | FcPattern *pattern); |
287 | |
288 | gpointer get_gravity_class (void); |
289 | |
290 | gpointer |
291 | get_gravity_class (void) |
292 | { |
293 | static GEnumClass *class = NULL; /* MT-safe */ |
294 | |
295 | if (g_once_init_enter (&class)) |
296 | g_once_init_leave (&class, (gpointer)g_type_class_ref (PANGO_TYPE_GRAVITY)); |
297 | |
298 | return class; |
299 | } |
300 | |
301 | static guint |
302 | pango_fc_font_face_data_hash (PangoFcFontFaceData *key) |
303 | { |
304 | return g_str_hash (v: key->filename) ^ key->id; |
305 | } |
306 | |
307 | static gboolean |
308 | pango_fc_font_face_data_equal (PangoFcFontFaceData *key1, |
309 | PangoFcFontFaceData *key2) |
310 | { |
311 | return key1->id == key2->id && |
312 | (key1 == key2 || 0 == strcmp (s1: key1->filename, s2: key2->filename)); |
313 | } |
314 | |
315 | static void |
316 | pango_fc_font_face_data_free (PangoFcFontFaceData *data) |
317 | { |
318 | FcPatternDestroy (p: data->pattern); |
319 | |
320 | if (data->coverage) |
321 | g_object_unref (object: data->coverage); |
322 | |
323 | g_free (mem: data->languages); |
324 | |
325 | hb_face_destroy (face: data->hb_face); |
326 | |
327 | g_slice_free (PangoFcFontFaceData, data); |
328 | } |
329 | |
330 | /* Fowler / Noll / Vo (FNV) Hash (http://www.isthe.com/chongo/tech/comp/fnv/) |
331 | * |
332 | * Not necessarily better than a lot of other hashes, but should be OK, and |
333 | * well tested with binary data. |
334 | */ |
335 | |
336 | #define FNV_32_PRIME ((guint32)0x01000193) |
337 | #define FNV1_32_INIT ((guint32)0x811c9dc5) |
338 | |
339 | static guint32 |
340 | hash_bytes_fnv (unsigned char *buffer, |
341 | int len, |
342 | guint32 hval) |
343 | { |
344 | while (len--) |
345 | { |
346 | hval *= FNV_32_PRIME; |
347 | hval ^= *buffer++; |
348 | } |
349 | |
350 | return hval; |
351 | } |
352 | |
353 | static void |
354 | get_context_matrix (PangoContext *context, |
355 | PangoMatrix *matrix) |
356 | { |
357 | const PangoMatrix *set_matrix; |
358 | const PangoMatrix identity = PANGO_MATRIX_INIT; |
359 | |
360 | set_matrix = context ? pango_context_get_matrix (context) : NULL; |
361 | *matrix = set_matrix ? *set_matrix : identity; |
362 | matrix->x0 = matrix->y0 = 0.; |
363 | } |
364 | |
365 | static int |
366 | get_scaled_size (PangoFcFontMap *fcfontmap, |
367 | PangoContext *context, |
368 | const PangoFontDescription *desc) |
369 | { |
370 | double size = pango_font_description_get_size (desc); |
371 | |
372 | if (!pango_font_description_get_size_is_absolute (desc)) |
373 | { |
374 | double dpi = pango_fc_font_map_get_resolution (fcfontmap, context); |
375 | |
376 | size = size * dpi / 72.; |
377 | } |
378 | |
379 | return .5 + pango_matrix_get_font_scale_factor (matrix: pango_context_get_matrix (context)) * size; |
380 | } |
381 | |
382 | |
383 | |
384 | struct _PangoFcFontsetKey { |
385 | PangoFcFontMap *fontmap; |
386 | PangoLanguage *language; |
387 | PangoFontDescription *desc; |
388 | PangoMatrix matrix; |
389 | int pixelsize; |
390 | double resolution; |
391 | gpointer context_key; |
392 | char *variations; |
393 | }; |
394 | |
395 | struct _PangoFcFontKey { |
396 | PangoFcFontMap *fontmap; |
397 | FcPattern *pattern; |
398 | PangoMatrix matrix; |
399 | gpointer context_key; |
400 | char *variations; |
401 | }; |
402 | |
403 | static void |
404 | pango_fc_fontset_key_init (PangoFcFontsetKey *key, |
405 | PangoFcFontMap *fcfontmap, |
406 | PangoContext *context, |
407 | const PangoFontDescription *desc, |
408 | PangoLanguage *language) |
409 | { |
410 | if (!language && context) |
411 | language = pango_context_get_language (context); |
412 | |
413 | key->fontmap = fcfontmap; |
414 | get_context_matrix (context, matrix: &key->matrix); |
415 | key->pixelsize = get_scaled_size (fcfontmap, context, desc); |
416 | key->resolution = pango_fc_font_map_get_resolution (fcfontmap, context); |
417 | key->language = language; |
418 | key->variations = g_strdup (str: pango_font_description_get_variations (desc)); |
419 | key->desc = pango_font_description_copy_static (desc); |
420 | pango_font_description_unset_fields (desc: key->desc, to_unset: PANGO_FONT_MASK_SIZE | PANGO_FONT_MASK_VARIATIONS); |
421 | |
422 | if (context && PANGO_FC_FONT_MAP_GET_CLASS (fcfontmap)->context_key_get) |
423 | key->context_key = (gpointer)PANGO_FC_FONT_MAP_GET_CLASS (fcfontmap)->context_key_get (fcfontmap, context); |
424 | else |
425 | key->context_key = NULL; |
426 | } |
427 | |
428 | static gboolean |
429 | pango_fc_fontset_key_equal (const PangoFcFontsetKey *key_a, |
430 | const PangoFcFontsetKey *key_b) |
431 | { |
432 | if (key_a->language == key_b->language && |
433 | key_a->pixelsize == key_b->pixelsize && |
434 | key_a->resolution == key_b->resolution && |
435 | ((key_a->variations == NULL && key_b->variations == NULL) || |
436 | (key_a->variations && key_b->variations && (strcmp (s1: key_a->variations, s2: key_b->variations) == 0))) && |
437 | pango_font_description_equal (desc1: key_a->desc, desc2: key_b->desc) && |
438 | 0 == memcmp (s1: &key_a->matrix, s2: &key_b->matrix, n: 4 * sizeof (double))) |
439 | { |
440 | if (key_a->context_key) |
441 | return PANGO_FC_FONT_MAP_GET_CLASS (key_a->fontmap)->context_key_equal (key_a->fontmap, |
442 | key_a->context_key, |
443 | key_b->context_key); |
444 | else |
445 | return key_a->context_key == key_b->context_key; |
446 | } |
447 | else |
448 | return FALSE; |
449 | } |
450 | |
451 | static guint |
452 | pango_fc_fontset_key_hash (const PangoFcFontsetKey *key) |
453 | { |
454 | guint32 hash = FNV1_32_INIT; |
455 | |
456 | /* We do a bytewise hash on the doubles */ |
457 | hash = hash_bytes_fnv (buffer: (unsigned char *)(&key->matrix), len: sizeof (double) * 4, hval: hash); |
458 | hash = hash_bytes_fnv (buffer: (unsigned char *)(&key->resolution), len: sizeof (double), hval: hash); |
459 | |
460 | hash ^= key->pixelsize; |
461 | |
462 | if (key->variations) |
463 | hash ^= g_str_hash (v: key->variations); |
464 | |
465 | if (key->context_key) |
466 | hash ^= PANGO_FC_FONT_MAP_GET_CLASS (key->fontmap)->context_key_hash (key->fontmap, |
467 | key->context_key); |
468 | |
469 | return (hash ^ |
470 | GPOINTER_TO_UINT (key->language) ^ |
471 | pango_font_description_hash (desc: key->desc)); |
472 | } |
473 | |
474 | static void |
475 | pango_fc_fontset_key_free (PangoFcFontsetKey *key) |
476 | { |
477 | pango_font_description_free (desc: key->desc); |
478 | g_free (mem: key->variations); |
479 | |
480 | if (key->context_key) |
481 | PANGO_FC_FONT_MAP_GET_CLASS (key->fontmap)->context_key_free (key->fontmap, |
482 | key->context_key); |
483 | |
484 | g_slice_free (PangoFcFontsetKey, key); |
485 | } |
486 | |
487 | static PangoFcFontsetKey * |
488 | pango_fc_fontset_key_copy (const PangoFcFontsetKey *old) |
489 | { |
490 | PangoFcFontsetKey *key = g_slice_new (PangoFcFontsetKey); |
491 | |
492 | key->fontmap = old->fontmap; |
493 | key->language = old->language; |
494 | key->desc = pango_font_description_copy (desc: old->desc); |
495 | key->matrix = old->matrix; |
496 | key->pixelsize = old->pixelsize; |
497 | key->resolution = old->resolution; |
498 | key->variations = g_strdup (str: old->variations); |
499 | |
500 | if (old->context_key) |
501 | key->context_key = PANGO_FC_FONT_MAP_GET_CLASS (key->fontmap)->context_key_copy (key->fontmap, |
502 | old->context_key); |
503 | else |
504 | key->context_key = NULL; |
505 | |
506 | return key; |
507 | } |
508 | |
509 | /** |
510 | * pango_fc_fontset_key_get_language: |
511 | * @key: the fontset key |
512 | * |
513 | * Gets the language member of @key. |
514 | * |
515 | * Returns: the language |
516 | * |
517 | * Since: 1.24 |
518 | */ |
519 | PangoLanguage * |
520 | pango_fc_fontset_key_get_language (const PangoFcFontsetKey *key) |
521 | { |
522 | return key->language; |
523 | } |
524 | |
525 | /** |
526 | * pango_fc_fontset_key_get_description: |
527 | * @key: the fontset key |
528 | * |
529 | * Gets the font description of @key. |
530 | * |
531 | * Returns: the font description, which is owned by @key and should not be modified. |
532 | * |
533 | * Since: 1.24 |
534 | */ |
535 | const PangoFontDescription * |
536 | pango_fc_fontset_key_get_description (const PangoFcFontsetKey *key) |
537 | { |
538 | return key->desc; |
539 | } |
540 | |
541 | /** |
542 | * pango_fc_fontset_key_get_matrix: |
543 | * @key: the fontset key |
544 | * |
545 | * Gets the matrix member of @key. |
546 | * |
547 | * Returns: the matrix, which is owned by @key and should not be modified. |
548 | * |
549 | * Since: 1.24 |
550 | */ |
551 | const PangoMatrix * |
552 | pango_fc_fontset_key_get_matrix (const PangoFcFontsetKey *key) |
553 | { |
554 | return &key->matrix; |
555 | } |
556 | |
557 | /** |
558 | * pango_fc_fontset_key_get_absolute_size: |
559 | * @key: the fontset key |
560 | * |
561 | * Gets the absolute font size of @key in Pango units. |
562 | * |
563 | * This is adjusted for both resolution and transformation matrix. |
564 | * |
565 | * Returns: the pixel size of @key. |
566 | * |
567 | * Since: 1.24 |
568 | */ |
569 | double |
570 | pango_fc_fontset_key_get_absolute_size (const PangoFcFontsetKey *key) |
571 | { |
572 | return key->pixelsize; |
573 | } |
574 | |
575 | /** |
576 | * pango_fc_fontset_key_get_resolution: |
577 | * @key: the fontset key |
578 | * |
579 | * Gets the resolution of @key |
580 | * |
581 | * Returns: the resolution of @key |
582 | * |
583 | * Since: 1.24 |
584 | */ |
585 | double |
586 | pango_fc_fontset_key_get_resolution (const PangoFcFontsetKey *key) |
587 | { |
588 | return key->resolution; |
589 | } |
590 | |
591 | /** |
592 | * pango_fc_fontset_key_get_context_key: |
593 | * @key: the font key |
594 | * |
595 | * Gets the context key member of @key. |
596 | * |
597 | * Returns: the context key, which is owned by @key and should not be modified. |
598 | * |
599 | * Since: 1.24 |
600 | */ |
601 | gpointer |
602 | pango_fc_fontset_key_get_context_key (const PangoFcFontsetKey *key) |
603 | { |
604 | return key->context_key; |
605 | } |
606 | |
607 | /* |
608 | * PangoFcFontKey |
609 | */ |
610 | |
611 | static gboolean |
612 | pango_fc_font_key_equal (const PangoFcFontKey *key_a, |
613 | const PangoFcFontKey *key_b) |
614 | { |
615 | if (key_a->pattern == key_b->pattern && |
616 | ((key_a->variations == NULL && key_b->variations == NULL) || |
617 | (key_a->variations && key_b->variations && (strcmp (s1: key_a->variations, s2: key_b->variations) == 0))) && |
618 | 0 == memcmp (s1: &key_a->matrix, s2: &key_b->matrix, n: 4 * sizeof (double))) |
619 | { |
620 | if (key_a->context_key && key_b->context_key) |
621 | return PANGO_FC_FONT_MAP_GET_CLASS (key_a->fontmap)->context_key_equal (key_a->fontmap, |
622 | key_a->context_key, |
623 | key_b->context_key); |
624 | else |
625 | return key_a->context_key == key_b->context_key; |
626 | } |
627 | else |
628 | return FALSE; |
629 | } |
630 | |
631 | static guint |
632 | pango_fc_font_key_hash (const PangoFcFontKey *key) |
633 | { |
634 | guint32 hash = FNV1_32_INIT; |
635 | |
636 | /* We do a bytewise hash on the doubles */ |
637 | hash = hash_bytes_fnv (buffer: (unsigned char *)(&key->matrix), len: sizeof (double) * 4, hval: hash); |
638 | |
639 | if (key->variations) |
640 | hash ^= g_str_hash (v: key->variations); |
641 | |
642 | if (key->context_key) |
643 | hash ^= PANGO_FC_FONT_MAP_GET_CLASS (key->fontmap)->context_key_hash (key->fontmap, |
644 | key->context_key); |
645 | |
646 | return (hash ^ GPOINTER_TO_UINT (key->pattern)); |
647 | } |
648 | |
649 | static void |
650 | pango_fc_font_key_free (PangoFcFontKey *key) |
651 | { |
652 | if (key->pattern) |
653 | FcPatternDestroy (p: key->pattern); |
654 | |
655 | if (key->context_key) |
656 | PANGO_FC_FONT_MAP_GET_CLASS (key->fontmap)->context_key_free (key->fontmap, |
657 | key->context_key); |
658 | |
659 | g_free (mem: key->variations); |
660 | |
661 | g_slice_free (PangoFcFontKey, key); |
662 | } |
663 | |
664 | static PangoFcFontKey * |
665 | pango_fc_font_key_copy (const PangoFcFontKey *old) |
666 | { |
667 | PangoFcFontKey *key = g_slice_new (PangoFcFontKey); |
668 | |
669 | key->fontmap = old->fontmap; |
670 | FcPatternReference (p: old->pattern); |
671 | key->pattern = old->pattern; |
672 | key->matrix = old->matrix; |
673 | key->variations = g_strdup (str: old->variations); |
674 | if (old->context_key) |
675 | key->context_key = PANGO_FC_FONT_MAP_GET_CLASS (key->fontmap)->context_key_copy (key->fontmap, |
676 | old->context_key); |
677 | else |
678 | key->context_key = NULL; |
679 | |
680 | return key; |
681 | } |
682 | |
683 | static void |
684 | pango_fc_font_key_init (PangoFcFontKey *key, |
685 | PangoFcFontMap *fcfontmap, |
686 | PangoFcFontsetKey *fontset_key, |
687 | FcPattern *pattern) |
688 | { |
689 | key->fontmap = fcfontmap; |
690 | key->pattern = pattern; |
691 | key->matrix = *pango_fc_fontset_key_get_matrix (key: fontset_key); |
692 | key->variations = fontset_key->variations; |
693 | key->context_key = pango_fc_fontset_key_get_context_key (key: fontset_key); |
694 | } |
695 | |
696 | /* Public API */ |
697 | |
698 | /** |
699 | * pango_fc_font_key_get_pattern: |
700 | * @key: the font key |
701 | * |
702 | * Gets the fontconfig pattern member of @key. |
703 | * |
704 | * Returns: the pattern, which is owned by @key and should not be modified. |
705 | * |
706 | * Since: 1.24 |
707 | */ |
708 | const FcPattern * |
709 | pango_fc_font_key_get_pattern (const PangoFcFontKey *key) |
710 | { |
711 | return key->pattern; |
712 | } |
713 | |
714 | /** |
715 | * pango_fc_font_key_get_matrix: |
716 | * @key: the font key |
717 | * |
718 | * Gets the matrix member of @key. |
719 | * |
720 | * Returns: the matrix, which is owned by @key and should not be modified. |
721 | * |
722 | * Since: 1.24 |
723 | */ |
724 | const PangoMatrix * |
725 | pango_fc_font_key_get_matrix (const PangoFcFontKey *key) |
726 | { |
727 | return &key->matrix; |
728 | } |
729 | |
730 | /** |
731 | * pango_fc_font_key_get_context_key: |
732 | * @key: the font key |
733 | * |
734 | * Gets the context key member of @key. |
735 | * |
736 | * Returns: the context key, which is owned by @key and should not be modified. |
737 | * |
738 | * Since: 1.24 |
739 | */ |
740 | gpointer |
741 | pango_fc_font_key_get_context_key (const PangoFcFontKey *key) |
742 | { |
743 | return key->context_key; |
744 | } |
745 | |
746 | const char * |
747 | pango_fc_font_key_get_variations (const PangoFcFontKey *key) |
748 | { |
749 | return key->variations; |
750 | } |
751 | |
752 | /* |
753 | * PangoFcPatterns |
754 | */ |
755 | |
756 | struct _PangoFcPatterns { |
757 | PangoFcFontMap *fontmap; |
758 | |
759 | /* match and fontset are initialized in a thread, |
760 | * and are protected by a mutex. The thread signals |
761 | * the cond when match or fontset become available. |
762 | */ |
763 | GMutex mutex; |
764 | GCond cond; |
765 | |
766 | FcPattern *pattern; |
767 | FcPattern *match; |
768 | FcFontSet *fontset; |
769 | }; |
770 | |
771 | static FcFontSet * |
772 | font_set_copy (FcFontSet *fontset) |
773 | { |
774 | FcFontSet *copy; |
775 | int i; |
776 | |
777 | if (!fontset) |
778 | return NULL; |
779 | |
780 | copy = malloc (size: sizeof (FcFontSet)); |
781 | copy->sfont = copy->nfont = fontset->nfont; |
782 | copy->fonts = malloc (size: sizeof (FcPattern *) * copy->nfont); |
783 | memcpy (dest: copy->fonts, src: fontset->fonts, n: sizeof (FcPattern *) * copy->nfont); |
784 | for (i = 0; i < copy->nfont; i++) |
785 | FcPatternReference (p: copy->fonts[i]); |
786 | |
787 | return copy; |
788 | } |
789 | |
790 | typedef struct { |
791 | FcConfig *config; |
792 | FcFontSet *fonts; |
793 | FcPattern *pattern; |
794 | PangoFcPatterns *patterns; |
795 | } ThreadData; |
796 | |
797 | static FcFontSet *pango_fc_font_map_get_config_fonts (PangoFcFontMap *fcfontmap); |
798 | |
799 | static ThreadData * |
800 | thread_data_new (PangoFcPatterns *patterns) |
801 | { |
802 | ThreadData *td; |
803 | PangoFcFontMap *fontmap = patterns->fontmap; |
804 | |
805 | /* We don't want the fontmap dying on us */ |
806 | g_object_ref (fontmap); |
807 | |
808 | td = g_new (ThreadData, 1); |
809 | td->patterns = pango_fc_patterns_ref (pats: patterns); |
810 | td->pattern = FcPatternDuplicate (p: patterns->pattern); |
811 | td->config = FcConfigReference (config: pango_fc_font_map_get_config (fcfontmap: patterns->fontmap)); |
812 | td->fonts = font_set_copy (fontset: pango_fc_font_map_get_config_fonts (fcfontmap: patterns->fontmap)); |
813 | |
814 | return td; |
815 | } |
816 | |
817 | static void |
818 | thread_data_free (gpointer data) |
819 | { |
820 | ThreadData *td = data; |
821 | PangoFcFontMap *fontmap = td->patterns->fontmap; |
822 | |
823 | g_clear_pointer (&td->fonts, FcFontSetDestroy); |
824 | FcPatternDestroy (p: td->pattern); |
825 | FcConfigDestroy (config: td->config); |
826 | pango_fc_patterns_unref (pats: td->patterns); |
827 | g_free (mem: td); |
828 | |
829 | g_object_unref (object: fontmap); |
830 | } |
831 | |
832 | static gpointer |
833 | match_in_thread (gpointer task_data) |
834 | { |
835 | ThreadData *td = task_data; |
836 | FcResult result; |
837 | FcPattern *match; |
838 | gint64 before G_GNUC_UNUSED; |
839 | |
840 | before = PANGO_TRACE_CURRENT_TIME; |
841 | |
842 | match = FcFontSetMatch (config: td->config, |
843 | sets: &td->fonts, nsets: 1, |
844 | p: td->pattern, |
845 | result: &result); |
846 | |
847 | pango_trace_mark (before, "FcFontSetMatch" , NULL); |
848 | |
849 | g_mutex_lock (mutex: &td->patterns->mutex); |
850 | td->patterns->match = match; |
851 | g_cond_signal (cond: &td->patterns->cond); |
852 | g_mutex_unlock (mutex: &td->patterns->mutex); |
853 | |
854 | thread_data_free (data: td); |
855 | |
856 | return NULL; |
857 | } |
858 | |
859 | static gpointer |
860 | sort_in_thread (gpointer task_data) |
861 | { |
862 | ThreadData *td = task_data; |
863 | FcResult result; |
864 | FcFontSet *fontset; |
865 | gint64 before G_GNUC_UNUSED; |
866 | |
867 | before = PANGO_TRACE_CURRENT_TIME; |
868 | |
869 | fontset = FcFontSetSort (config: td->config, |
870 | sets: &td->fonts, nsets: 1, |
871 | p: td->pattern, |
872 | FcTrue, |
873 | NULL, |
874 | result: &result); |
875 | |
876 | pango_trace_mark (before, "FcFontSetSort" , NULL); |
877 | |
878 | g_mutex_lock (mutex: &td->patterns->mutex); |
879 | td->patterns->fontset = fontset; |
880 | g_cond_signal (cond: &td->patterns->cond); |
881 | g_mutex_unlock (mutex: &td->patterns->mutex); |
882 | |
883 | thread_data_free (data: td); |
884 | |
885 | return NULL; |
886 | } |
887 | |
888 | static PangoFcPatterns * |
889 | pango_fc_patterns_new (FcPattern *pat, PangoFcFontMap *fontmap) |
890 | { |
891 | PangoFcPatterns *pats; |
892 | GThread *thread; |
893 | |
894 | pat = uniquify_pattern (fcfontmap: fontmap, pattern: pat); |
895 | pats = g_hash_table_lookup (hash_table: fontmap->priv->patterns_hash, key: pat); |
896 | if (pats) |
897 | return pango_fc_patterns_ref (pats); |
898 | |
899 | pats = g_atomic_rc_box_new0 (PangoFcPatterns); |
900 | |
901 | pats->fontmap = fontmap; |
902 | |
903 | FcPatternReference (p: pat); |
904 | pats->pattern = pat; |
905 | |
906 | g_mutex_init (mutex: &pats->mutex); |
907 | g_cond_init (cond: &pats->cond); |
908 | |
909 | thread = g_thread_new (name: "[pango] FcFontSetMatch" , func: match_in_thread, data: thread_data_new (patterns: pats)); |
910 | g_thread_unref (thread); |
911 | |
912 | thread = g_thread_new (name: "[pango] FcFontSetSort" , func: sort_in_thread, data: thread_data_new (patterns: pats)); |
913 | g_thread_unref (thread); |
914 | |
915 | g_hash_table_insert (hash_table: fontmap->priv->patterns_hash, |
916 | key: pats->pattern, value: pats); |
917 | |
918 | return pats; |
919 | } |
920 | |
921 | static PangoFcPatterns * |
922 | pango_fc_patterns_ref (PangoFcPatterns *pats) |
923 | { |
924 | return g_atomic_rc_box_acquire (pats); |
925 | } |
926 | |
927 | static void |
928 | free_patterns (gpointer data) |
929 | { |
930 | PangoFcPatterns *pats = data; |
931 | |
932 | /* Only remove from fontmap hash if we are in it. This is not necessarily |
933 | * the case after a cache_clear() call. */ |
934 | if (pats->fontmap->priv->patterns_hash && |
935 | pats == g_hash_table_lookup (hash_table: pats->fontmap->priv->patterns_hash, key: pats->pattern)) |
936 | g_hash_table_remove (hash_table: pats->fontmap->priv->patterns_hash, |
937 | key: pats->pattern); |
938 | |
939 | if (pats->pattern) |
940 | FcPatternDestroy (p: pats->pattern); |
941 | |
942 | if (pats->match) |
943 | FcPatternDestroy (p: pats->match); |
944 | |
945 | if (pats->fontset) |
946 | FcFontSetDestroy (s: pats->fontset); |
947 | |
948 | g_cond_clear (cond: &pats->cond); |
949 | g_mutex_clear (mutex: &pats->mutex); |
950 | } |
951 | |
952 | static void |
953 | pango_fc_patterns_unref (PangoFcPatterns *pats) |
954 | { |
955 | g_atomic_rc_box_release_full (mem_block: pats, clear_func: free_patterns); |
956 | } |
957 | |
958 | static FcPattern * |
959 | pango_fc_patterns_get_pattern (PangoFcPatterns *pats) |
960 | { |
961 | return pats->pattern; |
962 | } |
963 | |
964 | static gboolean |
965 | pango_fc_is_supported_font_format (FcPattern* pattern) |
966 | { |
967 | FcResult res; |
968 | const char *fontformat; |
969 | const char *file; |
970 | |
971 | /* Harfbuzz loads woff fonts, but we don't get any glyphs */ |
972 | res = FcPatternGetString (p: pattern, FC_FILE, n: 0, s: (FcChar8 **)(void*)&file); |
973 | if (res == FcResultMatch && |
974 | (g_str_has_suffix (str: file, suffix: ".woff" ) || |
975 | g_str_has_suffix (str: file, suffix: ".woff2" ))) |
976 | return FALSE; |
977 | |
978 | res = FcPatternGetString (p: pattern, FC_FONTFORMAT, n: 0, s: (FcChar8 **)(void*)&fontformat); |
979 | if (res != FcResultMatch) |
980 | return FALSE; |
981 | |
982 | /* Harfbuzz supports only SFNT fonts. */ |
983 | /* FIXME: "CFF" is used for both CFF in OpenType and bare CFF files, but |
984 | * HarfBuzz does not support the later and FontConfig does not seem |
985 | * to have a way to tell them apart. |
986 | */ |
987 | if (g_ascii_strcasecmp (s1: fontformat, s2: "TrueType" ) == 0 || |
988 | g_ascii_strcasecmp (s1: fontformat, s2: "CFF" ) == 0) |
989 | return TRUE; |
990 | return FALSE; |
991 | } |
992 | |
993 | static FcFontSet * |
994 | filter_by_format (FcFontSet **sets, int nsets) |
995 | { |
996 | FcFontSet *result; |
997 | int set; |
998 | |
999 | result = FcFontSetCreate (); |
1000 | |
1001 | for (set = 0; set < nsets; set++) |
1002 | { |
1003 | FcFontSet *fontset = sets[set]; |
1004 | int i; |
1005 | |
1006 | if (!fontset) |
1007 | continue; |
1008 | |
1009 | for (i = 0; i < fontset->nfont; i++) |
1010 | { |
1011 | if (!pango_fc_is_supported_font_format (pattern: fontset->fonts[i])) |
1012 | continue; |
1013 | |
1014 | FcPatternReference (p: fontset->fonts[i]); |
1015 | FcFontSetAdd (s: result, font: fontset->fonts[i]); |
1016 | } |
1017 | } |
1018 | |
1019 | return result; |
1020 | } |
1021 | |
1022 | static FcPattern * |
1023 | pango_fc_patterns_get_font_pattern (PangoFcPatterns *pats, int i, gboolean *prepare) |
1024 | { |
1025 | FcPattern *match = NULL; |
1026 | FcFontSet *fontset = NULL; |
1027 | |
1028 | if (i == 0) |
1029 | { |
1030 | gint64 before G_GNUC_UNUSED; |
1031 | gboolean waited = FALSE; |
1032 | |
1033 | before = PANGO_TRACE_CURRENT_TIME; |
1034 | |
1035 | g_mutex_lock (mutex: &pats->mutex); |
1036 | |
1037 | while (!pats->match && !pats->fontset) |
1038 | { |
1039 | waited = TRUE; |
1040 | g_cond_wait (cond: &pats->cond, mutex: &pats->mutex); |
1041 | } |
1042 | |
1043 | match = pats->match; |
1044 | fontset = pats->fontset; |
1045 | |
1046 | g_mutex_unlock (mutex: &pats->mutex); |
1047 | |
1048 | if (waited) |
1049 | pango_trace_mark (before, "wait for FcFontMatch" , NULL); |
1050 | |
1051 | if (match) |
1052 | { |
1053 | *prepare = FALSE; |
1054 | return match; |
1055 | } |
1056 | } |
1057 | else |
1058 | { |
1059 | gint64 before G_GNUC_UNUSED; |
1060 | gboolean waited = FALSE; |
1061 | |
1062 | before = PANGO_TRACE_CURRENT_TIME; |
1063 | |
1064 | g_mutex_lock (mutex: &pats->mutex); |
1065 | |
1066 | while (!pats->fontset) |
1067 | { |
1068 | waited = TRUE; |
1069 | g_cond_wait (cond: &pats->cond, mutex: &pats->mutex); |
1070 | } |
1071 | |
1072 | fontset = pats->fontset; |
1073 | |
1074 | g_mutex_unlock (mutex: &pats->mutex); |
1075 | |
1076 | if (waited) |
1077 | pango_trace_mark (before, "wait for FcFontSort" , NULL); |
1078 | } |
1079 | |
1080 | if (fontset) |
1081 | { |
1082 | if (i < fontset->nfont) |
1083 | { |
1084 | *prepare = TRUE; |
1085 | return fontset->fonts[i]; |
1086 | } |
1087 | } |
1088 | |
1089 | return NULL; |
1090 | } |
1091 | |
1092 | |
1093 | /* |
1094 | * PangoFcFontset |
1095 | */ |
1096 | |
1097 | static void pango_fc_fontset_finalize (GObject *object); |
1098 | static PangoLanguage * pango_fc_fontset_get_language (PangoFontset *fontset); |
1099 | static PangoFont * pango_fc_fontset_get_font (PangoFontset *fontset, |
1100 | guint wc); |
1101 | static void pango_fc_fontset_foreach (PangoFontset *fontset, |
1102 | PangoFontsetForeachFunc func, |
1103 | gpointer data); |
1104 | |
1105 | struct _PangoFcFontset |
1106 | { |
1107 | PangoFontset parent_instance; |
1108 | |
1109 | PangoFcFontsetKey *key; |
1110 | |
1111 | PangoFcPatterns *patterns; |
1112 | int patterns_i; |
1113 | |
1114 | GPtrArray *fonts; |
1115 | GPtrArray *coverages; |
1116 | |
1117 | GList *cache_link; |
1118 | }; |
1119 | |
1120 | typedef PangoFontsetClass PangoFcFontsetClass; |
1121 | |
1122 | G_DEFINE_TYPE (PangoFcFontset, pango_fc_fontset, PANGO_TYPE_FONTSET) |
1123 | |
1124 | static PangoFcFontset * |
1125 | pango_fc_fontset_new (PangoFcFontsetKey *key, |
1126 | PangoFcPatterns *patterns) |
1127 | { |
1128 | PangoFcFontset *fontset; |
1129 | |
1130 | fontset = g_object_new (PANGO_FC_TYPE_FONTSET, NULL); |
1131 | |
1132 | fontset->key = pango_fc_fontset_key_copy (old: key); |
1133 | fontset->patterns = pango_fc_patterns_ref (pats: patterns); |
1134 | |
1135 | return fontset; |
1136 | } |
1137 | |
1138 | static PangoFcFontsetKey * |
1139 | pango_fc_fontset_get_key (PangoFcFontset *fontset) |
1140 | { |
1141 | return fontset->key; |
1142 | } |
1143 | |
1144 | static PangoFont * |
1145 | pango_fc_fontset_load_next_font (PangoFcFontset *fontset) |
1146 | { |
1147 | FcPattern *pattern, *font_pattern; |
1148 | PangoFont *font; |
1149 | gboolean prepare; |
1150 | |
1151 | pattern = pango_fc_patterns_get_pattern (pats: fontset->patterns); |
1152 | font_pattern = pango_fc_patterns_get_font_pattern (pats: fontset->patterns, |
1153 | i: fontset->patterns_i++, |
1154 | prepare: &prepare); |
1155 | if (G_UNLIKELY (!font_pattern)) |
1156 | return NULL; |
1157 | |
1158 | if (prepare) |
1159 | { |
1160 | font_pattern = FcFontRenderPrepare (config: fontset->key->fontmap->priv->config, pat: pattern, font: font_pattern); |
1161 | |
1162 | if (G_UNLIKELY (!font_pattern)) |
1163 | return NULL; |
1164 | } |
1165 | |
1166 | font = pango_fc_font_map_new_font (fontmap: fontset->key->fontmap, |
1167 | fontset_key: fontset->key, |
1168 | match: font_pattern); |
1169 | |
1170 | if (prepare) |
1171 | FcPatternDestroy (p: font_pattern); |
1172 | |
1173 | return font; |
1174 | } |
1175 | |
1176 | static PangoFont * |
1177 | pango_fc_fontset_get_font_at (PangoFcFontset *fontset, |
1178 | unsigned int i) |
1179 | { |
1180 | while (i >= fontset->fonts->len) |
1181 | { |
1182 | PangoFont *font = pango_fc_fontset_load_next_font (fontset); |
1183 | g_ptr_array_add (array: fontset->fonts, data: font); |
1184 | g_ptr_array_add (array: fontset->coverages, NULL); |
1185 | if (!font) |
1186 | return NULL; |
1187 | } |
1188 | |
1189 | return g_ptr_array_index (fontset->fonts, i); |
1190 | } |
1191 | |
1192 | static void |
1193 | pango_fc_fontset_class_init (PangoFcFontsetClass *class) |
1194 | { |
1195 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
1196 | PangoFontsetClass *fontset_class = PANGO_FONTSET_CLASS (class); |
1197 | |
1198 | object_class->finalize = pango_fc_fontset_finalize; |
1199 | |
1200 | fontset_class->get_font = pango_fc_fontset_get_font; |
1201 | fontset_class->get_language = pango_fc_fontset_get_language; |
1202 | fontset_class->foreach = pango_fc_fontset_foreach; |
1203 | } |
1204 | |
1205 | static void |
1206 | pango_fc_fontset_init (PangoFcFontset *fontset) |
1207 | { |
1208 | fontset->fonts = g_ptr_array_new (); |
1209 | fontset->coverages = g_ptr_array_new (); |
1210 | } |
1211 | |
1212 | static void |
1213 | pango_fc_fontset_finalize (GObject *object) |
1214 | { |
1215 | PangoFcFontset *fontset = PANGO_FC_FONTSET (object); |
1216 | unsigned int i; |
1217 | |
1218 | for (i = 0; i < fontset->fonts->len; i++) |
1219 | { |
1220 | PangoFont *font = g_ptr_array_index(fontset->fonts, i); |
1221 | if (font) |
1222 | g_object_unref (object: font); |
1223 | } |
1224 | g_ptr_array_free (array: fontset->fonts, TRUE); |
1225 | |
1226 | for (i = 0; i < fontset->coverages->len; i++) |
1227 | { |
1228 | PangoCoverage *coverage = g_ptr_array_index (fontset->coverages, i); |
1229 | if (coverage) |
1230 | g_object_unref (object: coverage); |
1231 | } |
1232 | g_ptr_array_free (array: fontset->coverages, TRUE); |
1233 | |
1234 | if (fontset->key) |
1235 | pango_fc_fontset_key_free (key: fontset->key); |
1236 | |
1237 | if (fontset->patterns) |
1238 | pango_fc_patterns_unref (pats: fontset->patterns); |
1239 | |
1240 | G_OBJECT_CLASS (pango_fc_fontset_parent_class)->finalize (object); |
1241 | } |
1242 | |
1243 | static PangoLanguage * |
1244 | pango_fc_fontset_get_language (PangoFontset *fontset) |
1245 | { |
1246 | PangoFcFontset *fcfontset = PANGO_FC_FONTSET (fontset); |
1247 | |
1248 | return pango_fc_fontset_key_get_language (key: pango_fc_fontset_get_key (fontset: fcfontset)); |
1249 | } |
1250 | |
1251 | static PangoFont * |
1252 | pango_fc_fontset_get_font (PangoFontset *fontset, |
1253 | guint wc) |
1254 | { |
1255 | PangoFcFontset *fcfontset = PANGO_FC_FONTSET (fontset); |
1256 | PangoCoverageLevel best_level = PANGO_COVERAGE_NONE; |
1257 | PangoCoverageLevel level; |
1258 | PangoFont *font; |
1259 | PangoCoverage *coverage; |
1260 | int result = -1; |
1261 | unsigned int i; |
1262 | |
1263 | for (i = 0; |
1264 | pango_fc_fontset_get_font_at (fontset: fcfontset, i); |
1265 | i++) |
1266 | { |
1267 | coverage = g_ptr_array_index (fcfontset->coverages, i); |
1268 | |
1269 | if (coverage == NULL) |
1270 | { |
1271 | font = g_ptr_array_index (fcfontset->fonts, i); |
1272 | |
1273 | coverage = pango_font_get_coverage (font, language: fcfontset->key->language); |
1274 | g_ptr_array_index (fcfontset->coverages, i) = coverage; |
1275 | } |
1276 | |
1277 | level = pango_coverage_get (coverage, index_: wc); |
1278 | |
1279 | if (result == -1 || level > best_level) |
1280 | { |
1281 | result = i; |
1282 | best_level = level; |
1283 | if (level == PANGO_COVERAGE_EXACT) |
1284 | break; |
1285 | } |
1286 | } |
1287 | |
1288 | if (G_UNLIKELY (result == -1)) |
1289 | return NULL; |
1290 | |
1291 | font = g_ptr_array_index (fcfontset->fonts, result); |
1292 | return g_object_ref (font); |
1293 | } |
1294 | |
1295 | static void |
1296 | pango_fc_fontset_foreach (PangoFontset *fontset, |
1297 | PangoFontsetForeachFunc func, |
1298 | gpointer data) |
1299 | { |
1300 | PangoFcFontset *fcfontset = PANGO_FC_FONTSET (fontset); |
1301 | PangoFont *font; |
1302 | unsigned int i; |
1303 | |
1304 | for (i = 0; |
1305 | (font = pango_fc_fontset_get_font_at (fontset: fcfontset, i)); |
1306 | i++) |
1307 | { |
1308 | if ((*func) (fontset, font, data)) |
1309 | return; |
1310 | } |
1311 | } |
1312 | |
1313 | |
1314 | /* |
1315 | * PangoFcFontMap |
1316 | */ |
1317 | |
1318 | static GType |
1319 | pango_fc_font_map_get_item_type (GListModel *list) |
1320 | { |
1321 | return PANGO_TYPE_FONT_FAMILY; |
1322 | } |
1323 | |
1324 | static void ensure_families (PangoFcFontMap *fcfontmap); |
1325 | |
1326 | static guint |
1327 | pango_fc_font_map_get_n_items (GListModel *list) |
1328 | { |
1329 | PangoFcFontMap *fcfontmap = PANGO_FC_FONT_MAP (list); |
1330 | |
1331 | ensure_families (fcfontmap); |
1332 | |
1333 | return fcfontmap->priv->n_families; |
1334 | } |
1335 | |
1336 | static gpointer |
1337 | pango_fc_font_map_get_item (GListModel *list, |
1338 | guint position) |
1339 | { |
1340 | PangoFcFontMap *fcfontmap = PANGO_FC_FONT_MAP (list); |
1341 | |
1342 | ensure_families (fcfontmap); |
1343 | |
1344 | if (position < fcfontmap->priv->n_families) |
1345 | return g_object_ref (fcfontmap->priv->families[position]); |
1346 | |
1347 | return NULL; |
1348 | } |
1349 | |
1350 | static void |
1351 | pango_fc_font_map_list_model_init (GListModelInterface *iface) |
1352 | { |
1353 | iface->get_item_type = pango_fc_font_map_get_item_type; |
1354 | iface->get_n_items = pango_fc_font_map_get_n_items; |
1355 | iface->get_item = pango_fc_font_map_get_item; |
1356 | } |
1357 | |
1358 | G_DEFINE_ABSTRACT_TYPE_WITH_CODE (PangoFcFontMap, pango_fc_font_map, PANGO_TYPE_FONT_MAP, |
1359 | G_ADD_PRIVATE (PangoFcFontMap) |
1360 | G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, pango_fc_font_map_list_model_init)) |
1361 | |
1362 | static gpointer |
1363 | init_in_thread (gpointer task_data) |
1364 | { |
1365 | gint64 before G_GNUC_UNUSED; |
1366 | |
1367 | before = PANGO_TRACE_CURRENT_TIME; |
1368 | |
1369 | FcInit (); |
1370 | |
1371 | pango_trace_mark (before, "FcInit" , NULL); |
1372 | |
1373 | g_mutex_lock (mutex: &fc_init_mutex); |
1374 | fc_initialized = DEFAULT_CONFIG_INITIALIZED; |
1375 | g_cond_broadcast (cond: &fc_init_cond); |
1376 | g_mutex_unlock (mutex: &fc_init_mutex); |
1377 | |
1378 | return NULL; |
1379 | } |
1380 | |
1381 | static void |
1382 | start_init_in_thread (PangoFcFontMap *fcfontmap) |
1383 | { |
1384 | g_mutex_lock (mutex: &fc_init_mutex); |
1385 | |
1386 | if (fc_initialized == DEFAULT_CONFIG_NOT_INITIALIZED) |
1387 | { |
1388 | GThread *thread; |
1389 | |
1390 | fc_initialized = DEFAULT_CONFIG_INITIALIZING; |
1391 | thread = g_thread_new (name: "[pango] FcInit" , func: init_in_thread, NULL); |
1392 | g_thread_unref (thread); |
1393 | } |
1394 | |
1395 | g_mutex_unlock (mutex: &fc_init_mutex); |
1396 | } |
1397 | |
1398 | static void |
1399 | wait_for_fc_init (void) |
1400 | { |
1401 | gint64 before G_GNUC_UNUSED; |
1402 | gboolean waited = FALSE; |
1403 | |
1404 | before = PANGO_TRACE_CURRENT_TIME; |
1405 | |
1406 | g_mutex_lock (mutex: &fc_init_mutex); |
1407 | while (fc_initialized < DEFAULT_CONFIG_INITIALIZED) |
1408 | { |
1409 | waited = TRUE; |
1410 | g_cond_wait (cond: &fc_init_cond, mutex: &fc_init_mutex); |
1411 | } |
1412 | g_mutex_unlock (mutex: &fc_init_mutex); |
1413 | |
1414 | if (waited) |
1415 | pango_trace_mark (before, "wait for FcInit" , NULL); |
1416 | } |
1417 | |
1418 | static void |
1419 | pango_fc_font_map_init (PangoFcFontMap *fcfontmap) |
1420 | { |
1421 | PangoFcFontMapPrivate *priv; |
1422 | |
1423 | priv = fcfontmap->priv = pango_fc_font_map_get_instance_private (self: fcfontmap); |
1424 | |
1425 | priv->n_families = -1; |
1426 | |
1427 | priv->font_hash = g_hash_table_new (hash_func: (GHashFunc)pango_fc_font_key_hash, |
1428 | key_equal_func: (GEqualFunc)pango_fc_font_key_equal); |
1429 | |
1430 | priv->fontset_hash = g_hash_table_new_full (hash_func: (GHashFunc)pango_fc_fontset_key_hash, |
1431 | key_equal_func: (GEqualFunc)pango_fc_fontset_key_equal, |
1432 | NULL, |
1433 | value_destroy_func: (GDestroyNotify)g_object_unref); |
1434 | priv->fontset_cache = g_queue_new (); |
1435 | |
1436 | priv->patterns_hash = g_hash_table_new (NULL, NULL); |
1437 | |
1438 | priv->pattern_hash = g_hash_table_new_full (hash_func: (GHashFunc) FcPatternHash, |
1439 | key_equal_func: (GEqualFunc) FcPatternEqual, |
1440 | key_destroy_func: (GDestroyNotify) FcPatternDestroy, |
1441 | NULL); |
1442 | |
1443 | priv->font_face_data_hash = g_hash_table_new_full (hash_func: (GHashFunc)pango_fc_font_face_data_hash, |
1444 | key_equal_func: (GEqualFunc)pango_fc_font_face_data_equal, |
1445 | key_destroy_func: (GDestroyNotify)pango_fc_font_face_data_free, |
1446 | NULL); |
1447 | priv->dpi = -1; |
1448 | |
1449 | start_init_in_thread (fcfontmap); |
1450 | } |
1451 | |
1452 | static void |
1453 | pango_fc_font_map_fini (PangoFcFontMap *fcfontmap) |
1454 | { |
1455 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
1456 | int i; |
1457 | |
1458 | g_clear_pointer (&priv->fonts, FcFontSetDestroy); |
1459 | |
1460 | g_queue_free (queue: priv->fontset_cache); |
1461 | priv->fontset_cache = NULL; |
1462 | |
1463 | g_hash_table_destroy (hash_table: priv->fontset_hash); |
1464 | priv->fontset_hash = NULL; |
1465 | |
1466 | g_hash_table_destroy (hash_table: priv->patterns_hash); |
1467 | priv->patterns_hash = NULL; |
1468 | |
1469 | g_hash_table_destroy (hash_table: priv->font_hash); |
1470 | priv->font_hash = NULL; |
1471 | |
1472 | g_hash_table_destroy (hash_table: priv->font_face_data_hash); |
1473 | priv->font_face_data_hash = NULL; |
1474 | |
1475 | g_hash_table_destroy (hash_table: priv->pattern_hash); |
1476 | priv->pattern_hash = NULL; |
1477 | |
1478 | for (i = 0; i < priv->n_families; i++) |
1479 | g_object_unref (object: priv->families[i]); |
1480 | g_free (mem: priv->families); |
1481 | priv->n_families = -1; |
1482 | priv->families = NULL; |
1483 | } |
1484 | |
1485 | static void |
1486 | pango_fc_font_map_class_init (PangoFcFontMapClass *class) |
1487 | { |
1488 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
1489 | PangoFontMapClass *fontmap_class = PANGO_FONT_MAP_CLASS (class); |
1490 | |
1491 | object_class->finalize = pango_fc_font_map_finalize; |
1492 | fontmap_class->load_font = pango_fc_font_map_load_font; |
1493 | fontmap_class->load_fontset = pango_fc_font_map_load_fontset; |
1494 | fontmap_class->list_families = pango_fc_font_map_list_families; |
1495 | fontmap_class->get_family = pango_fc_font_map_get_family; |
1496 | fontmap_class->get_face = pango_fc_font_map_get_face; |
1497 | fontmap_class->shape_engine_type = PANGO_RENDER_TYPE_FC; |
1498 | fontmap_class->changed = pango_fc_font_map_changed; |
1499 | } |
1500 | |
1501 | |
1502 | /** |
1503 | * pango_fc_font_map_add_decoder_find_func: |
1504 | * @fcfontmap: The `PangoFcFontMap` to add this method to. |
1505 | * @findfunc: The `PangoFcDecoderFindFunc` callback function |
1506 | * @user_data: User data. |
1507 | * @dnotify: A `GDestroyNotify` callback that will be called when the |
1508 | * fontmap is finalized and the decoder is released. |
1509 | * |
1510 | * This function saves a callback method in the `PangoFcFontMap` that |
1511 | * will be called whenever new fonts are created. |
1512 | * |
1513 | * If the function returns a `PangoFcDecoder`, that decoder will be used |
1514 | * to determine both coverage via a `FcCharSet` and a one-to-one mapping |
1515 | * of characters to glyphs. This will allow applications to have |
1516 | * application-specific encodings for various fonts. |
1517 | * |
1518 | * Since: 1.6 |
1519 | */ |
1520 | void |
1521 | pango_fc_font_map_add_decoder_find_func (PangoFcFontMap *fcfontmap, |
1522 | PangoFcDecoderFindFunc findfunc, |
1523 | gpointer user_data, |
1524 | GDestroyNotify dnotify) |
1525 | { |
1526 | PangoFcFontMapPrivate *priv; |
1527 | PangoFcFindFuncInfo *info; |
1528 | |
1529 | g_return_if_fail (PANGO_IS_FC_FONT_MAP (fcfontmap)); |
1530 | |
1531 | priv = fcfontmap->priv; |
1532 | |
1533 | info = g_slice_new (PangoFcFindFuncInfo); |
1534 | |
1535 | info->findfunc = findfunc; |
1536 | info->user_data = user_data; |
1537 | info->dnotify = dnotify; |
1538 | |
1539 | priv->findfuncs = g_slist_append (list: priv->findfuncs, data: info); |
1540 | } |
1541 | |
1542 | /** |
1543 | * pango_fc_font_map_find_decoder: |
1544 | * @fcfontmap: The `PangoFcFontMap` to use. |
1545 | * @pattern: The `FcPattern` to find the decoder for. |
1546 | * |
1547 | * Finds the decoder to use for @pattern. |
1548 | * |
1549 | * Decoders can be added to a font map using |
1550 | * [method@PangoFc.FontMap.add_decoder_find_func]. |
1551 | * |
1552 | * Returns: (transfer full) (nullable): a newly created `PangoFcDecoder` |
1553 | * object or %NULL if no decoder is set for @pattern. |
1554 | * |
1555 | * Since: 1.26 |
1556 | */ |
1557 | PangoFcDecoder * |
1558 | pango_fc_font_map_find_decoder (PangoFcFontMap *fcfontmap, |
1559 | FcPattern *pattern) |
1560 | { |
1561 | GSList *l; |
1562 | |
1563 | g_return_val_if_fail (PANGO_IS_FC_FONT_MAP (fcfontmap), NULL); |
1564 | g_return_val_if_fail (pattern != NULL, NULL); |
1565 | |
1566 | for (l = fcfontmap->priv->findfuncs; l && l->data; l = l->next) |
1567 | { |
1568 | PangoFcFindFuncInfo *info = l->data; |
1569 | PangoFcDecoder *decoder; |
1570 | |
1571 | decoder = info->findfunc (pattern, info->user_data); |
1572 | if (decoder) |
1573 | return decoder; |
1574 | } |
1575 | |
1576 | return NULL; |
1577 | } |
1578 | |
1579 | static void |
1580 | pango_fc_font_map_finalize (GObject *object) |
1581 | { |
1582 | PangoFcFontMap *fcfontmap = PANGO_FC_FONT_MAP (object); |
1583 | |
1584 | pango_fc_font_map_shutdown (fcfontmap); |
1585 | |
1586 | if (fcfontmap->substitute_destroy) |
1587 | fcfontmap->substitute_destroy (fcfontmap->substitute_data); |
1588 | |
1589 | G_OBJECT_CLASS (pango_fc_font_map_parent_class)->finalize (object); |
1590 | } |
1591 | |
1592 | /* Add a mapping from key to fcfont */ |
1593 | static void |
1594 | pango_fc_font_map_add (PangoFcFontMap *fcfontmap, |
1595 | PangoFcFontKey *key, |
1596 | PangoFcFont *fcfont) |
1597 | { |
1598 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
1599 | PangoFcFontKey *key_copy; |
1600 | |
1601 | key_copy = pango_fc_font_key_copy (old: key); |
1602 | _pango_fc_font_set_font_key (fcfont, key: key_copy); |
1603 | g_hash_table_insert (hash_table: priv->font_hash, key: key_copy, value: fcfont); |
1604 | } |
1605 | |
1606 | /* Remove mapping from fcfont->key to fcfont */ |
1607 | /* Closely related to shutdown_font() */ |
1608 | void |
1609 | _pango_fc_font_map_remove (PangoFcFontMap *fcfontmap, |
1610 | PangoFcFont *fcfont) |
1611 | { |
1612 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
1613 | PangoFcFontKey *key; |
1614 | |
1615 | key = _pango_fc_font_get_font_key (fcfont); |
1616 | if (key) |
1617 | { |
1618 | /* Only remove from fontmap hash if we are in it. This is not necessarily |
1619 | * the case after a cache_clear() call. */ |
1620 | if (priv->font_hash && |
1621 | fcfont == g_hash_table_lookup (hash_table: priv->font_hash, key)) |
1622 | { |
1623 | g_hash_table_remove (hash_table: priv->font_hash, key); |
1624 | } |
1625 | _pango_fc_font_set_font_key (fcfont, NULL); |
1626 | pango_fc_font_key_free (key); |
1627 | } |
1628 | } |
1629 | |
1630 | static PangoFcFamily * |
1631 | create_family (PangoFcFontMap *fcfontmap, |
1632 | const char *family_name, |
1633 | int spacing) |
1634 | { |
1635 | PangoFcFamily *family = g_object_new (PANGO_FC_TYPE_FAMILY, NULL); |
1636 | family->fontmap = fcfontmap; |
1637 | family->family_name = g_strdup (str: family_name); |
1638 | family->spacing = spacing; |
1639 | family->variable = FALSE; |
1640 | family->patterns = FcFontSetCreate (); |
1641 | |
1642 | return family; |
1643 | } |
1644 | |
1645 | static gboolean |
1646 | is_alias_family (const char *family_name) |
1647 | { |
1648 | switch (family_name[0]) |
1649 | { |
1650 | case 'c': |
1651 | case 'C': |
1652 | return (g_ascii_strcasecmp (s1: family_name, s2: "cursive" ) == 0); |
1653 | case 'f': |
1654 | case 'F': |
1655 | return (g_ascii_strcasecmp (s1: family_name, s2: "fantasy" ) == 0); |
1656 | case 'm': |
1657 | case 'M': |
1658 | return (g_ascii_strcasecmp (s1: family_name, s2: "monospace" ) == 0); |
1659 | case 's': |
1660 | case 'S': |
1661 | return (g_ascii_strcasecmp (s1: family_name, s2: "sans" ) == 0 || |
1662 | g_ascii_strcasecmp (s1: family_name, s2: "serif" ) == 0 || |
1663 | g_ascii_strcasecmp (s1: family_name, s2: "system-ui" ) == 0); |
1664 | default: |
1665 | return FALSE; |
1666 | } |
1667 | |
1668 | return FALSE; |
1669 | } |
1670 | |
1671 | static void |
1672 | ensure_families (PangoFcFontMap *fcfontmap) |
1673 | { |
1674 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
1675 | FcFontSet *fontset; |
1676 | int i; |
1677 | int count; |
1678 | |
1679 | wait_for_fc_init (); |
1680 | |
1681 | if (priv->n_families < 0) |
1682 | { |
1683 | FcObjectSet *os = FcObjectSetBuild (FC_FAMILY, FC_SPACING, FC_STYLE, FC_WEIGHT, FC_WIDTH, FC_SLANT, |
1684 | FC_VARIABLE, |
1685 | FC_FONTFORMAT, |
1686 | NULL); |
1687 | FcPattern *pat = FcPatternCreate (); |
1688 | GHashTable *temp_family_hash; |
1689 | FcFontSet *fonts; |
1690 | |
1691 | fonts = pango_fc_font_map_get_config_fonts (fcfontmap); |
1692 | fontset = FcFontSetList (config: priv->config, sets: &fonts, nsets: 1, p: pat, os); |
1693 | |
1694 | FcPatternDestroy (p: pat); |
1695 | FcObjectSetDestroy (os); |
1696 | |
1697 | priv->families = g_new (PangoFcFamily *, fontset->nfont + 4); /* 4 standard aliases */ |
1698 | temp_family_hash = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal, key_destroy_func: g_free, NULL); |
1699 | |
1700 | count = 0; |
1701 | for (i = 0; i < fontset->nfont; i++) |
1702 | { |
1703 | char *s; |
1704 | FcResult res; |
1705 | int spacing; |
1706 | int variable; |
1707 | PangoFcFamily *temp_family; |
1708 | |
1709 | res = FcPatternGetString (p: fontset->fonts[i], FC_FAMILY, n: 0, s: (FcChar8 **)(void*)&s); |
1710 | g_assert (res == FcResultMatch); |
1711 | |
1712 | temp_family = g_hash_table_lookup (hash_table: temp_family_hash, key: s); |
1713 | if (!is_alias_family (family_name: s) && !temp_family) |
1714 | { |
1715 | res = FcPatternGetInteger (p: fontset->fonts[i], FC_SPACING, n: 0, i: &spacing); |
1716 | g_assert (res == FcResultMatch || res == FcResultNoMatch); |
1717 | if (res == FcResultNoMatch) |
1718 | spacing = FC_PROPORTIONAL; |
1719 | |
1720 | temp_family = create_family (fcfontmap, family_name: s, spacing); |
1721 | g_hash_table_insert (hash_table: temp_family_hash, key: g_strdup (str: s), value: temp_family); |
1722 | priv->families[count++] = temp_family; |
1723 | } |
1724 | |
1725 | if (temp_family) |
1726 | { |
1727 | variable = FALSE; |
1728 | variable = FcPatternGetBool (p: fontset->fonts[i], FC_VARIABLE, n: 0, b: &variable); |
1729 | if (variable) |
1730 | temp_family->variable = TRUE; |
1731 | |
1732 | FcPatternReference (p: fontset->fonts[i]); |
1733 | FcFontSetAdd (s: temp_family->patterns, font: fontset->fonts[i]); |
1734 | } |
1735 | } |
1736 | |
1737 | FcFontSetDestroy (s: fontset); |
1738 | g_hash_table_destroy (hash_table: temp_family_hash); |
1739 | |
1740 | priv->families[count++] = create_family (fcfontmap, family_name: "Sans" , FC_PROPORTIONAL); |
1741 | priv->families[count++] = create_family (fcfontmap, family_name: "Serif" , FC_PROPORTIONAL); |
1742 | priv->families[count++] = create_family (fcfontmap, family_name: "Monospace" , FC_MONO); |
1743 | priv->families[count++] = create_family (fcfontmap, family_name: "System-ui" , FC_PROPORTIONAL); |
1744 | |
1745 | priv->n_families = count; |
1746 | } |
1747 | } |
1748 | |
1749 | static void |
1750 | pango_fc_font_map_list_families (PangoFontMap *fontmap, |
1751 | PangoFontFamily ***families, |
1752 | int *n_families) |
1753 | { |
1754 | PangoFcFontMap *fcfontmap = PANGO_FC_FONT_MAP (fontmap); |
1755 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
1756 | |
1757 | if (priv->closed) |
1758 | { |
1759 | if (families) |
1760 | *families = NULL; |
1761 | if (n_families) |
1762 | *n_families = 0; |
1763 | |
1764 | return; |
1765 | } |
1766 | |
1767 | ensure_families (fcfontmap); |
1768 | |
1769 | if (n_families) |
1770 | *n_families = priv->n_families; |
1771 | |
1772 | if (families) |
1773 | *families = g_memdup2 (mem: priv->families, byte_size: priv->n_families * sizeof (PangoFontFamily *)); |
1774 | } |
1775 | |
1776 | static PangoFontFamily * |
1777 | pango_fc_font_map_get_family (PangoFontMap *fontmap, |
1778 | const char *name) |
1779 | { |
1780 | PangoFcFontMap *fcfontmap = PANGO_FC_FONT_MAP (fontmap); |
1781 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
1782 | int i; |
1783 | |
1784 | if (priv->closed) |
1785 | return NULL; |
1786 | |
1787 | ensure_families (fcfontmap); |
1788 | |
1789 | for (i = 0; i < priv->n_families; i++) |
1790 | { |
1791 | PangoFontFamily *family = PANGO_FONT_FAMILY (priv->families[i]); |
1792 | if (strcmp (s1: name, s2: pango_font_family_get_name (family)) == 0) |
1793 | return family; |
1794 | } |
1795 | |
1796 | return NULL; |
1797 | } |
1798 | |
1799 | static double |
1800 | pango_fc_convert_weight_to_fc (PangoWeight pango_weight) |
1801 | { |
1802 | return FcWeightFromOpenTypeDouble (ot_weight: pango_weight); |
1803 | } |
1804 | |
1805 | static int |
1806 | pango_fc_convert_slant_to_fc (PangoStyle pango_style) |
1807 | { |
1808 | switch (pango_style) |
1809 | { |
1810 | case PANGO_STYLE_NORMAL: |
1811 | return FC_SLANT_ROMAN; |
1812 | case PANGO_STYLE_ITALIC: |
1813 | return FC_SLANT_ITALIC; |
1814 | case PANGO_STYLE_OBLIQUE: |
1815 | return FC_SLANT_OBLIQUE; |
1816 | default: |
1817 | return FC_SLANT_ROMAN; |
1818 | } |
1819 | } |
1820 | |
1821 | static int |
1822 | pango_fc_convert_width_to_fc (PangoStretch pango_stretch) |
1823 | { |
1824 | switch (pango_stretch) |
1825 | { |
1826 | case PANGO_STRETCH_NORMAL: |
1827 | return FC_WIDTH_NORMAL; |
1828 | case PANGO_STRETCH_ULTRA_CONDENSED: |
1829 | return FC_WIDTH_ULTRACONDENSED; |
1830 | case PANGO_STRETCH_EXTRA_CONDENSED: |
1831 | return FC_WIDTH_EXTRACONDENSED; |
1832 | case PANGO_STRETCH_CONDENSED: |
1833 | return FC_WIDTH_CONDENSED; |
1834 | case PANGO_STRETCH_SEMI_CONDENSED: |
1835 | return FC_WIDTH_SEMICONDENSED; |
1836 | case PANGO_STRETCH_SEMI_EXPANDED: |
1837 | return FC_WIDTH_SEMIEXPANDED; |
1838 | case PANGO_STRETCH_EXPANDED: |
1839 | return FC_WIDTH_EXPANDED; |
1840 | case PANGO_STRETCH_EXTRA_EXPANDED: |
1841 | return FC_WIDTH_EXTRAEXPANDED; |
1842 | case PANGO_STRETCH_ULTRA_EXPANDED: |
1843 | return FC_WIDTH_ULTRAEXPANDED; |
1844 | default: |
1845 | return FC_WIDTH_NORMAL; |
1846 | } |
1847 | } |
1848 | |
1849 | static FcPattern * |
1850 | pango_fc_make_pattern (const PangoFontDescription *description, |
1851 | PangoLanguage *language, |
1852 | int pixel_size, |
1853 | double dpi, |
1854 | const char *variations) |
1855 | { |
1856 | FcPattern *pattern; |
1857 | const char *prgname; |
1858 | int slant; |
1859 | double weight; |
1860 | PangoGravity gravity; |
1861 | PangoVariant variant; |
1862 | char **families; |
1863 | int i; |
1864 | int width; |
1865 | |
1866 | prgname = g_get_prgname (); |
1867 | slant = pango_fc_convert_slant_to_fc (pango_style: pango_font_description_get_style (desc: description)); |
1868 | weight = pango_fc_convert_weight_to_fc (pango_weight: pango_font_description_get_weight (desc: description)); |
1869 | width = pango_fc_convert_width_to_fc (pango_stretch: pango_font_description_get_stretch (desc: description)); |
1870 | |
1871 | gravity = pango_font_description_get_gravity (desc: description); |
1872 | variant = pango_font_description_get_variant (desc: description); |
1873 | |
1874 | /* The reason for passing in FC_SIZE as well as FC_PIXEL_SIZE is |
1875 | * to work around a bug in libgnomeprint where it doesn't look |
1876 | * for FC_PIXEL_SIZE. See http://bugzilla.gnome.org/show_bug.cgi?id=169020 |
1877 | * |
1878 | * Putting FC_SIZE in here slightly reduces the efficiency |
1879 | * of caching of patterns and fonts when working with multiple different |
1880 | * dpi values. |
1881 | * |
1882 | * Do not pass FC_VERTICAL_LAYOUT true as HarfBuzz shaping assumes false. |
1883 | */ |
1884 | pattern = FcPatternBuild (NULL, |
1885 | PANGO_FC_VERSION, FcTypeInteger, pango_version(), |
1886 | FC_WEIGHT, FcTypeDouble, weight, |
1887 | FC_SLANT, FcTypeInteger, slant, |
1888 | FC_WIDTH, FcTypeInteger, width, |
1889 | FC_VARIABLE, FcTypeBool, FcDontCare, |
1890 | FC_DPI, FcTypeDouble, dpi, |
1891 | FC_SIZE, FcTypeDouble, pixel_size * (72. / 1024. / dpi), |
1892 | FC_PIXEL_SIZE, FcTypeDouble, pixel_size / 1024., |
1893 | NULL); |
1894 | |
1895 | if (variations) |
1896 | FcPatternAddString (p: pattern, FC_FONT_VARIATIONS, s: (FcChar8*) variations); |
1897 | |
1898 | if (pango_font_description_get_family (desc: description)) |
1899 | { |
1900 | families = g_strsplit (string: pango_font_description_get_family (desc: description), delimiter: "," , max_tokens: -1); |
1901 | |
1902 | for (i = 0; families[i]; i++) |
1903 | FcPatternAddString (p: pattern, FC_FAMILY, s: (FcChar8*) families[i]); |
1904 | |
1905 | g_strfreev (str_array: families); |
1906 | } |
1907 | |
1908 | if (language) |
1909 | FcPatternAddString (p: pattern, FC_LANG, s: (FcChar8 *) pango_language_to_string (language)); |
1910 | |
1911 | if (gravity != PANGO_GRAVITY_SOUTH) |
1912 | { |
1913 | GEnumValue *value = g_enum_get_value (enum_class: get_gravity_class (), value: gravity); |
1914 | FcPatternAddString (p: pattern, PANGO_FC_GRAVITY, s: (FcChar8*) value->value_nick); |
1915 | } |
1916 | |
1917 | if (prgname) |
1918 | FcPatternAddString (p: pattern, PANGO_FC_PRGNAME, s: (FcChar8*) prgname); |
1919 | |
1920 | switch (variant) |
1921 | { |
1922 | case PANGO_VARIANT_SMALL_CAPS: |
1923 | FcPatternAddString (p: pattern, FC_FONT_FEATURES, s: (FcChar8*) "smcp=1" ); |
1924 | break; |
1925 | case PANGO_VARIANT_ALL_SMALL_CAPS: |
1926 | FcPatternAddString (p: pattern, FC_FONT_FEATURES, s: (FcChar8*) "smcp=1" ); |
1927 | FcPatternAddString (p: pattern, FC_FONT_FEATURES, s: (FcChar8*) "c2sc=1" ); |
1928 | break; |
1929 | case PANGO_VARIANT_PETITE_CAPS: |
1930 | FcPatternAddString (p: pattern, FC_FONT_FEATURES, s: (FcChar8*) "pcap=1" ); |
1931 | break; |
1932 | case PANGO_VARIANT_ALL_PETITE_CAPS: |
1933 | FcPatternAddString (p: pattern, FC_FONT_FEATURES, s: (FcChar8*) "pcap=1" ); |
1934 | FcPatternAddString (p: pattern, FC_FONT_FEATURES, s: (FcChar8*) "c2pc=1" ); |
1935 | break; |
1936 | case PANGO_VARIANT_UNICASE: |
1937 | FcPatternAddString (p: pattern, FC_FONT_FEATURES, s: (FcChar8*) "unic=1" ); |
1938 | break; |
1939 | case PANGO_VARIANT_TITLE_CAPS: |
1940 | FcPatternAddString (p: pattern, FC_FONT_FEATURES, s: (FcChar8*) "titl=1" ); |
1941 | break; |
1942 | case PANGO_VARIANT_NORMAL: |
1943 | break; |
1944 | default: |
1945 | g_assert_not_reached (); |
1946 | } |
1947 | |
1948 | return pattern; |
1949 | } |
1950 | |
1951 | static FcPattern * |
1952 | uniquify_pattern (PangoFcFontMap *fcfontmap, |
1953 | FcPattern *pattern) |
1954 | { |
1955 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
1956 | FcPattern *old_pattern; |
1957 | |
1958 | old_pattern = g_hash_table_lookup (hash_table: priv->pattern_hash, key: pattern); |
1959 | if (old_pattern) |
1960 | { |
1961 | return old_pattern; |
1962 | } |
1963 | else |
1964 | { |
1965 | FcPatternReference (p: pattern); |
1966 | g_hash_table_insert (hash_table: priv->pattern_hash, key: pattern, value: pattern); |
1967 | return pattern; |
1968 | } |
1969 | } |
1970 | |
1971 | static PangoFont * |
1972 | pango_fc_font_map_new_font (PangoFcFontMap *fcfontmap, |
1973 | PangoFcFontsetKey *fontset_key, |
1974 | FcPattern *match) |
1975 | { |
1976 | PangoFcFontMapClass *class; |
1977 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
1978 | FcPattern *pattern; |
1979 | PangoFcFont *fcfont; |
1980 | PangoFcFontKey key; |
1981 | |
1982 | if (priv->closed) |
1983 | return NULL; |
1984 | |
1985 | match = uniquify_pattern (fcfontmap, pattern: match); |
1986 | |
1987 | pango_fc_font_key_init (key: &key, fcfontmap, fontset_key, pattern: match); |
1988 | |
1989 | fcfont = g_hash_table_lookup (hash_table: priv->font_hash, key: &key); |
1990 | if (fcfont) |
1991 | return g_object_ref (PANGO_FONT (fcfont)); |
1992 | |
1993 | class = PANGO_FC_FONT_MAP_GET_CLASS (fcfontmap); |
1994 | |
1995 | if (class->create_font) |
1996 | { |
1997 | fcfont = class->create_font (fcfontmap, &key); |
1998 | } |
1999 | else |
2000 | { |
2001 | const PangoMatrix *pango_matrix = pango_fc_fontset_key_get_matrix (key: fontset_key); |
2002 | FcMatrix fc_matrix, *fc_matrix_val; |
2003 | int i; |
2004 | |
2005 | /* Fontconfig has the Y axis pointing up, Pango, down. |
2006 | */ |
2007 | fc_matrix.xx = pango_matrix->xx; |
2008 | fc_matrix.xy = - pango_matrix->xy; |
2009 | fc_matrix.yx = - pango_matrix->yx; |
2010 | fc_matrix.yy = pango_matrix->yy; |
2011 | |
2012 | pattern = FcPatternDuplicate (p: match); |
2013 | |
2014 | for (i = 0; FcPatternGetMatrix (p: pattern, FC_MATRIX, n: i, s: &fc_matrix_val) == FcResultMatch; i++) |
2015 | FcMatrixMultiply (result: &fc_matrix, a: &fc_matrix, b: fc_matrix_val); |
2016 | |
2017 | FcPatternDel (p: pattern, FC_MATRIX); |
2018 | FcPatternAddMatrix (p: pattern, FC_MATRIX, s: &fc_matrix); |
2019 | |
2020 | fcfont = class->new_font (fcfontmap, uniquify_pattern (fcfontmap, pattern)); |
2021 | |
2022 | FcPatternDestroy (p: pattern); |
2023 | } |
2024 | |
2025 | if (!fcfont) |
2026 | return NULL; |
2027 | |
2028 | /* In case the backend didn't set the fontmap */ |
2029 | if (!fcfont->fontmap) |
2030 | g_object_set (object: fcfont, |
2031 | first_property_name: "fontmap" , fcfontmap, |
2032 | NULL); |
2033 | |
2034 | /* cache it on fontmap */ |
2035 | pango_fc_font_map_add (fcfontmap, key: &key, fcfont); |
2036 | |
2037 | return (PangoFont *)fcfont; |
2038 | } |
2039 | |
2040 | static PangoFontFace * |
2041 | pango_fc_font_map_get_face (PangoFontMap *fontmap, |
2042 | PangoFont *font) |
2043 | { |
2044 | PangoFcFont *fcfont = PANGO_FC_FONT (font); |
2045 | FcResult res; |
2046 | const char *s; |
2047 | PangoFontFamily *family; |
2048 | |
2049 | res = FcPatternGetString (p: fcfont->font_pattern, FC_FAMILY, n: 0, s: (FcChar8 **) &s); |
2050 | g_assert (res == FcResultMatch); |
2051 | |
2052 | family = pango_font_map_get_family (fontmap, name: s); |
2053 | |
2054 | res = FcPatternGetString (p: fcfont->font_pattern, FC_STYLE, n: 0, s: (FcChar8 **)(void*)&s); |
2055 | g_assert (res == FcResultMatch); |
2056 | |
2057 | return pango_font_family_get_face (family, name: s); |
2058 | } |
2059 | |
2060 | static void |
2061 | pango_fc_default_substitute (PangoFcFontMap *fontmap, |
2062 | PangoFcFontsetKey *fontsetkey, |
2063 | FcPattern *pattern) |
2064 | { |
2065 | if (PANGO_FC_FONT_MAP_GET_CLASS (fontmap)->fontset_key_substitute) |
2066 | PANGO_FC_FONT_MAP_GET_CLASS (fontmap)->fontset_key_substitute (fontmap, fontsetkey, pattern); |
2067 | else if (PANGO_FC_FONT_MAP_GET_CLASS (fontmap)->default_substitute) |
2068 | PANGO_FC_FONT_MAP_GET_CLASS (fontmap)->default_substitute (fontmap, pattern); |
2069 | } |
2070 | |
2071 | void |
2072 | pango_fc_font_map_set_default_substitute (PangoFcFontMap *fontmap, |
2073 | PangoFcSubstituteFunc func, |
2074 | gpointer data, |
2075 | GDestroyNotify notify) |
2076 | { |
2077 | if (fontmap->substitute_destroy) |
2078 | fontmap->substitute_destroy (fontmap->substitute_data); |
2079 | |
2080 | fontmap->substitute_func = func; |
2081 | fontmap->substitute_data = data; |
2082 | fontmap->substitute_destroy = notify; |
2083 | |
2084 | pango_fc_font_map_substitute_changed (fontmap); |
2085 | } |
2086 | |
2087 | void |
2088 | pango_fc_font_map_substitute_changed (PangoFcFontMap *fontmap) { |
2089 | pango_fc_font_map_cache_clear(fcfontmap: fontmap); |
2090 | pango_font_map_changed(PANGO_FONT_MAP (fontmap)); |
2091 | } |
2092 | |
2093 | static double |
2094 | pango_fc_font_map_get_resolution (PangoFcFontMap *fcfontmap, |
2095 | PangoContext *context) |
2096 | { |
2097 | if (PANGO_FC_FONT_MAP_GET_CLASS (fcfontmap)->get_resolution) |
2098 | return PANGO_FC_FONT_MAP_GET_CLASS (fcfontmap)->get_resolution (fcfontmap, context); |
2099 | |
2100 | if (fcfontmap->priv->dpi < 0) |
2101 | { |
2102 | FcResult result = FcResultNoMatch; |
2103 | FcPattern *tmp = FcPatternBuild (NULL, |
2104 | FC_FAMILY, FcTypeString, "Sans" , |
2105 | FC_SIZE, FcTypeDouble, 10., |
2106 | NULL); |
2107 | if (tmp) |
2108 | { |
2109 | pango_fc_default_substitute (fontmap: fcfontmap, NULL, pattern: tmp); |
2110 | result = FcPatternGetDouble (p: tmp, FC_DPI, n: 0, d: &fcfontmap->priv->dpi); |
2111 | FcPatternDestroy (p: tmp); |
2112 | } |
2113 | |
2114 | if (result != FcResultMatch) |
2115 | { |
2116 | g_warning ("Error getting DPI from fontconfig, using 72.0" ); |
2117 | fcfontmap->priv->dpi = 72.0; |
2118 | } |
2119 | } |
2120 | |
2121 | return fcfontmap->priv->dpi; |
2122 | } |
2123 | |
2124 | static FcPattern * |
2125 | pango_fc_fontset_key_make_pattern (PangoFcFontsetKey *key) |
2126 | { |
2127 | return pango_fc_make_pattern (description: key->desc, |
2128 | language: key->language, |
2129 | pixel_size: key->pixelsize, |
2130 | dpi: key->resolution, |
2131 | variations: key->variations); |
2132 | } |
2133 | |
2134 | static PangoFcPatterns * |
2135 | pango_fc_font_map_get_patterns (PangoFontMap *fontmap, |
2136 | PangoFcFontsetKey *key) |
2137 | { |
2138 | PangoFcFontMap *fcfontmap = (PangoFcFontMap *)fontmap; |
2139 | PangoFcPatterns *patterns; |
2140 | FcPattern *pattern; |
2141 | |
2142 | pattern = pango_fc_fontset_key_make_pattern (key); |
2143 | pango_fc_default_substitute (fontmap: fcfontmap, fontsetkey: key, pattern); |
2144 | |
2145 | patterns = pango_fc_patterns_new (pat: pattern, fontmap: fcfontmap); |
2146 | |
2147 | FcPatternDestroy (p: pattern); |
2148 | |
2149 | return patterns; |
2150 | } |
2151 | |
2152 | static gboolean |
2153 | get_first_font (PangoFontset *fontset G_GNUC_UNUSED, |
2154 | PangoFont *font, |
2155 | gpointer data) |
2156 | { |
2157 | *(PangoFont **)data = font; |
2158 | |
2159 | return TRUE; |
2160 | } |
2161 | |
2162 | static PangoFont * |
2163 | pango_fc_font_map_load_font (PangoFontMap *fontmap, |
2164 | PangoContext *context, |
2165 | const PangoFontDescription *description) |
2166 | { |
2167 | PangoLanguage *language; |
2168 | PangoFontset *fontset; |
2169 | PangoFont *font = NULL; |
2170 | |
2171 | if (context) |
2172 | language = pango_context_get_language (context); |
2173 | else |
2174 | language = NULL; |
2175 | |
2176 | fontset = pango_font_map_load_fontset (fontmap, context, desc: description, language); |
2177 | |
2178 | if (fontset) |
2179 | { |
2180 | pango_fontset_foreach (fontset, func: get_first_font, data: &font); |
2181 | |
2182 | if (font) |
2183 | g_object_ref (font); |
2184 | |
2185 | g_object_unref (object: fontset); |
2186 | } |
2187 | |
2188 | return font; |
2189 | } |
2190 | |
2191 | static void |
2192 | pango_fc_fontset_cache (PangoFcFontset *fontset, |
2193 | PangoFcFontMap *fcfontmap) |
2194 | { |
2195 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
2196 | GQueue *cache = priv->fontset_cache; |
2197 | |
2198 | if (fontset->cache_link) |
2199 | { |
2200 | if (fontset->cache_link == cache->head) |
2201 | return; |
2202 | |
2203 | /* Already in cache, move to head |
2204 | */ |
2205 | if (fontset->cache_link == cache->tail) |
2206 | cache->tail = fontset->cache_link->prev; |
2207 | |
2208 | cache->head = g_list_remove_link (list: cache->head, llink: fontset->cache_link); |
2209 | cache->length--; |
2210 | } |
2211 | else |
2212 | { |
2213 | /* Add to cache initially |
2214 | */ |
2215 | if (cache->length == FONTSET_CACHE_SIZE) |
2216 | { |
2217 | PangoFcFontset *tmp_fontset = g_queue_pop_tail (queue: cache); |
2218 | tmp_fontset->cache_link = NULL; |
2219 | g_hash_table_remove (hash_table: priv->fontset_hash, key: tmp_fontset->key); |
2220 | } |
2221 | |
2222 | fontset->cache_link = g_list_prepend (NULL, data: fontset); |
2223 | } |
2224 | |
2225 | g_queue_push_head_link (queue: cache, link_: fontset->cache_link); |
2226 | } |
2227 | |
2228 | static PangoFontset * |
2229 | pango_fc_font_map_load_fontset (PangoFontMap *fontmap, |
2230 | PangoContext *context, |
2231 | const PangoFontDescription *desc, |
2232 | PangoLanguage *language) |
2233 | { |
2234 | PangoFcFontMap *fcfontmap = (PangoFcFontMap *)fontmap; |
2235 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
2236 | PangoFcFontset *fontset; |
2237 | PangoFcFontsetKey key; |
2238 | |
2239 | pango_fc_fontset_key_init (key: &key, fcfontmap, context, desc, language); |
2240 | |
2241 | fontset = g_hash_table_lookup (hash_table: priv->fontset_hash, key: &key); |
2242 | |
2243 | if (G_UNLIKELY (!fontset)) |
2244 | { |
2245 | PangoFcPatterns *patterns = pango_fc_font_map_get_patterns (fontmap, key: &key); |
2246 | |
2247 | if (!patterns) |
2248 | return NULL; |
2249 | |
2250 | fontset = pango_fc_fontset_new (key: &key, patterns); |
2251 | g_hash_table_insert (hash_table: priv->fontset_hash, key: pango_fc_fontset_get_key (fontset), value: fontset); |
2252 | |
2253 | pango_fc_patterns_unref (pats: patterns); |
2254 | } |
2255 | |
2256 | pango_fc_fontset_cache (fontset, fcfontmap); |
2257 | |
2258 | pango_font_description_free (desc: key.desc); |
2259 | g_free (mem: key.variations); |
2260 | |
2261 | return g_object_ref (PANGO_FONTSET (fontset)); |
2262 | } |
2263 | |
2264 | /** |
2265 | * pango_fc_font_map_cache_clear: |
2266 | * @fcfontmap: a `PangoFcFontMap` |
2267 | * |
2268 | * Clear all cached information and fontsets for this font map. |
2269 | * |
2270 | * This should be called whenever there is a change in the |
2271 | * output of the default_substitute() virtual function of the |
2272 | * font map, or if fontconfig has been reinitialized to new |
2273 | * configuration. |
2274 | * |
2275 | * Since: 1.4 |
2276 | */ |
2277 | void |
2278 | pango_fc_font_map_cache_clear (PangoFcFontMap *fcfontmap) |
2279 | { |
2280 | guint removed, added; |
2281 | |
2282 | if (G_UNLIKELY (fcfontmap->priv->closed)) |
2283 | return; |
2284 | |
2285 | removed = fcfontmap->priv->n_families; |
2286 | |
2287 | pango_fc_font_map_fini (fcfontmap); |
2288 | pango_fc_font_map_init (fcfontmap); |
2289 | |
2290 | ensure_families (fcfontmap); |
2291 | |
2292 | added = fcfontmap->priv->n_families; |
2293 | |
2294 | g_list_model_items_changed (list: G_LIST_MODEL (ptr: fcfontmap), position: 0, removed, added); |
2295 | if (removed != added) |
2296 | g_object_notify (G_OBJECT (fcfontmap), property_name: "n-items" ); |
2297 | |
2298 | pango_font_map_changed (PANGO_FONT_MAP (fcfontmap)); |
2299 | } |
2300 | |
2301 | static void |
2302 | pango_fc_font_map_changed (PangoFontMap *fontmap) |
2303 | { |
2304 | /* we emit GListModel::changed in pango_fc_font_map_cache_clear() */ |
2305 | } |
2306 | |
2307 | /** |
2308 | * pango_fc_font_map_config_changed: |
2309 | * @fcfontmap: a `PangoFcFontMap` |
2310 | * |
2311 | * Informs font map that the fontconfig configuration (i.e., FcConfig |
2312 | * object) used by this font map has changed. |
2313 | * |
2314 | * This currently calls [method@PangoFc.FontMap.cache_clear] which |
2315 | * ensures that list of fonts, etc will be regenerated using the |
2316 | * updated configuration. |
2317 | * |
2318 | * Since: 1.38 |
2319 | */ |
2320 | void |
2321 | pango_fc_font_map_config_changed (PangoFcFontMap *fcfontmap) |
2322 | { |
2323 | pango_fc_font_map_cache_clear (fcfontmap); |
2324 | } |
2325 | |
2326 | /** |
2327 | * pango_fc_font_map_set_config: (skip) |
2328 | * @fcfontmap: a `PangoFcFontMap` |
2329 | * @fcconfig: (nullable): a `FcConfig` |
2330 | * |
2331 | * Set the `FcConfig` for this font map to use. |
2332 | * |
2333 | * The default value |
2334 | * is %NULL, which causes Fontconfig to use its global "current config". |
2335 | * You can create a new `FcConfig` object and use this API to attach it |
2336 | * to a font map. |
2337 | * |
2338 | * This is particularly useful for example, if you want to use application |
2339 | * fonts with Pango. For that, you would create a fresh `FcConfig`, add your |
2340 | * app fonts to it, and attach it to a new Pango font map. |
2341 | * |
2342 | * If @fcconfig is different from the previous config attached to the font map, |
2343 | * [method@PangoFc.FontMap.config_changed] is called. |
2344 | * |
2345 | * This function acquires a reference to the `FcConfig` object; the caller |
2346 | * does **not** need to retain a reference. |
2347 | * |
2348 | * Since: 1.38 |
2349 | */ |
2350 | void |
2351 | pango_fc_font_map_set_config (PangoFcFontMap *fcfontmap, |
2352 | FcConfig *fcconfig) |
2353 | { |
2354 | FcConfig *oldconfig; |
2355 | |
2356 | g_return_if_fail (PANGO_IS_FC_FONT_MAP (fcfontmap)); |
2357 | |
2358 | oldconfig = fcfontmap->priv->config; |
2359 | |
2360 | if (fcconfig) |
2361 | FcConfigReference (config: fcconfig); |
2362 | |
2363 | fcfontmap->priv->config = fcconfig; |
2364 | |
2365 | g_clear_pointer (&fcfontmap->priv->fonts, FcFontSetDestroy); |
2366 | |
2367 | if (oldconfig != fcconfig) |
2368 | pango_fc_font_map_config_changed (fcfontmap); |
2369 | |
2370 | if (oldconfig) |
2371 | FcConfigDestroy (config: oldconfig); |
2372 | } |
2373 | |
2374 | /** |
2375 | * pango_fc_font_map_get_config: (skip) |
2376 | * @fcfontmap: a `PangoFcFontMap` |
2377 | * |
2378 | * Fetches the `FcConfig` attached to a font map. |
2379 | * |
2380 | * See also: [method@PangoFc.FontMap.set_config]. |
2381 | * |
2382 | * Returns: (nullable): the `FcConfig` object attached to |
2383 | * @fcfontmap, which might be %NULL. The return value is |
2384 | * owned by Pango and should not be freed. |
2385 | * |
2386 | * Since: 1.38 |
2387 | */ |
2388 | FcConfig * |
2389 | pango_fc_font_map_get_config (PangoFcFontMap *fcfontmap) |
2390 | { |
2391 | g_return_val_if_fail (PANGO_IS_FC_FONT_MAP (fcfontmap), NULL); |
2392 | |
2393 | wait_for_fc_init (); |
2394 | |
2395 | return fcfontmap->priv->config; |
2396 | } |
2397 | |
2398 | static FcFontSet * |
2399 | pango_fc_font_map_get_config_fonts (PangoFcFontMap *fcfontmap) |
2400 | { |
2401 | if (fcfontmap->priv->fonts == NULL) |
2402 | { |
2403 | FcFontSet *sets[2]; |
2404 | |
2405 | wait_for_fc_init (); |
2406 | |
2407 | sets[0] = FcConfigGetFonts (config: fcfontmap->priv->config, set: 0); |
2408 | sets[1] = FcConfigGetFonts (config: fcfontmap->priv->config, set: 1); |
2409 | fcfontmap->priv->fonts = filter_by_format (sets, nsets: 2); |
2410 | } |
2411 | |
2412 | return fcfontmap->priv->fonts; |
2413 | } |
2414 | |
2415 | static PangoFcFontFaceData * |
2416 | pango_fc_font_map_get_font_face_data (PangoFcFontMap *fcfontmap, |
2417 | FcPattern *font_pattern) |
2418 | { |
2419 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
2420 | PangoFcFontFaceData key; |
2421 | PangoFcFontFaceData *data; |
2422 | |
2423 | if (FcPatternGetString (p: font_pattern, FC_FILE, n: 0, s: (FcChar8 **)(void*)&key.filename) != FcResultMatch) |
2424 | return NULL; |
2425 | |
2426 | if (FcPatternGetInteger (p: font_pattern, FC_INDEX, n: 0, i: &key.id) != FcResultMatch) |
2427 | return NULL; |
2428 | |
2429 | data = g_hash_table_lookup (hash_table: priv->font_face_data_hash, key: &key); |
2430 | if (G_LIKELY (data)) |
2431 | return data; |
2432 | |
2433 | data = g_slice_new0 (PangoFcFontFaceData); |
2434 | data->filename = key.filename; |
2435 | data->id = key.id; |
2436 | |
2437 | data->pattern = font_pattern; |
2438 | FcPatternReference (p: data->pattern); |
2439 | |
2440 | g_hash_table_insert (hash_table: priv->font_face_data_hash, key: data, value: data); |
2441 | |
2442 | return data; |
2443 | } |
2444 | |
2445 | typedef struct { |
2446 | PangoCoverage parent_instance; |
2447 | |
2448 | FcCharSet *charset; |
2449 | } PangoFcCoverage; |
2450 | |
2451 | typedef struct { |
2452 | PangoCoverageClass parent_class; |
2453 | } PangoFcCoverageClass; |
2454 | |
2455 | GType pango_fc_coverage_get_type (void) G_GNUC_CONST; |
2456 | |
2457 | G_DEFINE_TYPE (PangoFcCoverage, pango_fc_coverage, PANGO_TYPE_COVERAGE) |
2458 | |
2459 | static void |
2460 | pango_fc_coverage_init (PangoFcCoverage *coverage) |
2461 | { |
2462 | } |
2463 | |
2464 | static PangoCoverageLevel |
2465 | pango_fc_coverage_real_get (PangoCoverage *coverage, |
2466 | int index) |
2467 | { |
2468 | PangoFcCoverage *fc_coverage = (PangoFcCoverage*)coverage; |
2469 | |
2470 | return FcCharSetHasChar (fcs: fc_coverage->charset, ucs4: index) |
2471 | ? PANGO_COVERAGE_EXACT |
2472 | : PANGO_COVERAGE_NONE; |
2473 | } |
2474 | |
2475 | static void |
2476 | pango_fc_coverage_real_set (PangoCoverage *coverage, |
2477 | int index, |
2478 | PangoCoverageLevel level) |
2479 | { |
2480 | PangoFcCoverage *fc_coverage = (PangoFcCoverage*)coverage; |
2481 | |
2482 | if (level == PANGO_COVERAGE_NONE) |
2483 | FcCharSetDelChar (fcs: fc_coverage->charset, ucs4: index); |
2484 | else |
2485 | FcCharSetAddChar (fcs: fc_coverage->charset, ucs4: index); |
2486 | } |
2487 | |
2488 | static PangoCoverage * |
2489 | pango_fc_coverage_real_copy (PangoCoverage *coverage) |
2490 | { |
2491 | PangoFcCoverage *fc_coverage = (PangoFcCoverage*)coverage; |
2492 | PangoFcCoverage *copy; |
2493 | |
2494 | copy = g_object_new (object_type: pango_fc_coverage_get_type (), NULL); |
2495 | copy->charset = FcCharSetCopy (src: fc_coverage->charset); |
2496 | |
2497 | return (PangoCoverage *)copy; |
2498 | } |
2499 | |
2500 | static void |
2501 | pango_fc_coverage_finalize (GObject *object) |
2502 | { |
2503 | PangoFcCoverage *fc_coverage = (PangoFcCoverage*)object; |
2504 | |
2505 | FcCharSetDestroy (fcs: fc_coverage->charset); |
2506 | |
2507 | G_OBJECT_CLASS (pango_fc_coverage_parent_class)->finalize (object); |
2508 | } |
2509 | |
2510 | static void |
2511 | pango_fc_coverage_class_init (PangoFcCoverageClass *class) |
2512 | { |
2513 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
2514 | PangoCoverageClass *coverage_class = PANGO_COVERAGE_CLASS (class); |
2515 | |
2516 | object_class->finalize = pango_fc_coverage_finalize; |
2517 | |
2518 | coverage_class->get = pango_fc_coverage_real_get; |
2519 | coverage_class->set = pango_fc_coverage_real_set; |
2520 | coverage_class->copy = pango_fc_coverage_real_copy; |
2521 | } |
2522 | |
2523 | PangoCoverage * |
2524 | _pango_fc_font_map_get_coverage (PangoFcFontMap *fcfontmap, |
2525 | PangoFcFont *fcfont) |
2526 | { |
2527 | PangoFcFontFaceData *data; |
2528 | FcCharSet *charset; |
2529 | |
2530 | data = pango_fc_font_map_get_font_face_data (fcfontmap, font_pattern: fcfont->font_pattern); |
2531 | if (G_UNLIKELY (!data)) |
2532 | return NULL; |
2533 | |
2534 | if (G_UNLIKELY (data->coverage == NULL)) |
2535 | { |
2536 | /* |
2537 | * Pull the coverage out of the pattern, this |
2538 | * doesn't require loading the font |
2539 | */ |
2540 | if (FcPatternGetCharSet (p: fcfont->font_pattern, FC_CHARSET, n: 0, c: &charset) != FcResultMatch) |
2541 | return NULL; |
2542 | |
2543 | data->coverage = _pango_fc_font_map_fc_to_coverage (charset); |
2544 | } |
2545 | |
2546 | return g_object_ref (data->coverage); |
2547 | } |
2548 | |
2549 | /** |
2550 | * _pango_fc_font_map_fc_to_coverage: |
2551 | * @charset: `FcCharSet` to convert to a `PangoCoverage` object. |
2552 | * |
2553 | * Convert the given `FcCharSet` into a new `PangoCoverage` object. |
2554 | * |
2555 | * The caller is responsible for freeing the newly created object. |
2556 | * |
2557 | * Since: 1.6 |
2558 | */ |
2559 | PangoCoverage * |
2560 | _pango_fc_font_map_fc_to_coverage (FcCharSet *charset) |
2561 | { |
2562 | PangoFcCoverage *coverage; |
2563 | |
2564 | coverage = g_object_new (object_type: pango_fc_coverage_get_type (), NULL); |
2565 | coverage->charset = FcCharSetCopy (src: charset); |
2566 | |
2567 | return (PangoCoverage *)coverage; |
2568 | } |
2569 | |
2570 | static PangoLanguage ** |
2571 | _pango_fc_font_map_fc_to_languages (FcLangSet *langset) |
2572 | { |
2573 | FcStrSet *strset; |
2574 | FcStrList *list; |
2575 | FcChar8 *s; |
2576 | GPtrArray *langs; |
2577 | |
2578 | langs = g_ptr_array_new (); |
2579 | |
2580 | strset = FcLangSetGetLangs (ls: langset); |
2581 | list = FcStrListCreate (set: strset); |
2582 | |
2583 | FcStrListFirst (list); |
2584 | while ((s = FcStrListNext (list))) |
2585 | { |
2586 | PangoLanguage *l = pango_language_from_string (language: (const char *)s); |
2587 | g_ptr_array_add (array: langs, data: l); |
2588 | } |
2589 | |
2590 | FcStrListDone (list); |
2591 | FcStrSetDestroy (set: strset); |
2592 | |
2593 | g_ptr_array_add (array: langs, NULL); |
2594 | |
2595 | return (PangoLanguage **) g_ptr_array_free (array: langs, FALSE); |
2596 | } |
2597 | |
2598 | PangoLanguage ** |
2599 | _pango_fc_font_map_get_languages (PangoFcFontMap *fcfontmap, |
2600 | PangoFcFont *fcfont) |
2601 | { |
2602 | PangoFcFontFaceData *data; |
2603 | FcLangSet *langset; |
2604 | |
2605 | data = pango_fc_font_map_get_font_face_data (fcfontmap, font_pattern: fcfont->font_pattern); |
2606 | if (G_UNLIKELY (!data)) |
2607 | return NULL; |
2608 | |
2609 | if (G_UNLIKELY (data->languages == NULL)) |
2610 | { |
2611 | /* |
2612 | * Pull the languages out of the pattern, this |
2613 | * doesn't require loading the font |
2614 | */ |
2615 | if (FcPatternGetLangSet (p: fcfont->font_pattern, FC_LANG, n: 0, ls: &langset) != FcResultMatch) |
2616 | return NULL; |
2617 | |
2618 | data->languages = _pango_fc_font_map_fc_to_languages (langset); |
2619 | } |
2620 | |
2621 | return data->languages; |
2622 | } |
2623 | |
2624 | /** |
2625 | * pango_fc_font_map_create_context: |
2626 | * @fcfontmap: a `PangoFcFontMap` |
2627 | * |
2628 | * Creates a new context for this fontmap. |
2629 | * |
2630 | * This function is intended only for backend implementations deriving |
2631 | * from `PangoFcFontMap`; it is possible that a backend will store |
2632 | * additional information needed for correct operation on the `PangoContext` |
2633 | * after calling this function. |
2634 | * |
2635 | * Return value: (transfer full): a new `PangoContext` |
2636 | * |
2637 | * Since: 1.4 |
2638 | * |
2639 | * Deprecated: 1.22: Use pango_font_map_create_context() instead. |
2640 | */ |
2641 | PangoContext * |
2642 | pango_fc_font_map_create_context (PangoFcFontMap *fcfontmap) |
2643 | { |
2644 | g_return_val_if_fail (PANGO_IS_FC_FONT_MAP (fcfontmap), NULL); |
2645 | |
2646 | return pango_font_map_create_context (PANGO_FONT_MAP (fcfontmap)); |
2647 | } |
2648 | |
2649 | static void |
2650 | shutdown_font (gpointer key, |
2651 | PangoFcFont *fcfont, |
2652 | PangoFcFontMap *fcfontmap) |
2653 | { |
2654 | _pango_fc_font_shutdown (fcfont); |
2655 | |
2656 | _pango_fc_font_set_font_key (fcfont, NULL); |
2657 | pango_fc_font_key_free (key); |
2658 | } |
2659 | |
2660 | /** |
2661 | * pango_fc_font_map_shutdown: |
2662 | * @fcfontmap: a `PangoFcFontMap` |
2663 | * |
2664 | * Clears all cached information for the fontmap and marks |
2665 | * all fonts open for the fontmap as dead. |
2666 | * |
2667 | * See the shutdown() virtual function of `PangoFcFont`. |
2668 | * |
2669 | * This function might be used by a backend when the underlying |
2670 | * windowing system for the font map exits. This function is only |
2671 | * intended to be called only for backend implementations deriving |
2672 | * from `PangoFcFontMap`. |
2673 | * |
2674 | * Since: 1.4 |
2675 | */ |
2676 | void |
2677 | pango_fc_font_map_shutdown (PangoFcFontMap *fcfontmap) |
2678 | { |
2679 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
2680 | int i; |
2681 | |
2682 | if (priv->closed) |
2683 | return; |
2684 | |
2685 | g_hash_table_foreach (hash_table: priv->font_hash, func: (GHFunc) shutdown_font, user_data: fcfontmap); |
2686 | for (i = 0; i < priv->n_families; i++) |
2687 | priv->families[i]->fontmap = NULL; |
2688 | |
2689 | pango_fc_font_map_fini (fcfontmap); |
2690 | |
2691 | while (priv->findfuncs) |
2692 | { |
2693 | PangoFcFindFuncInfo *info; |
2694 | info = priv->findfuncs->data; |
2695 | if (info->dnotify) |
2696 | info->dnotify (info->user_data); |
2697 | |
2698 | g_slice_free (PangoFcFindFuncInfo, info); |
2699 | priv->findfuncs = g_slist_delete_link (list: priv->findfuncs, link_: priv->findfuncs); |
2700 | } |
2701 | |
2702 | priv->closed = TRUE; |
2703 | } |
2704 | |
2705 | static PangoWeight |
2706 | pango_fc_convert_weight_to_pango (double fc_weight) |
2707 | { |
2708 | return FcWeightToOpenTypeDouble (fc_weight); |
2709 | } |
2710 | |
2711 | static PangoStyle |
2712 | pango_fc_convert_slant_to_pango (int fc_style) |
2713 | { |
2714 | switch (fc_style) |
2715 | { |
2716 | case FC_SLANT_ROMAN: |
2717 | return PANGO_STYLE_NORMAL; |
2718 | case FC_SLANT_ITALIC: |
2719 | return PANGO_STYLE_ITALIC; |
2720 | case FC_SLANT_OBLIQUE: |
2721 | return PANGO_STYLE_OBLIQUE; |
2722 | default: |
2723 | return PANGO_STYLE_NORMAL; |
2724 | } |
2725 | } |
2726 | |
2727 | static PangoStretch |
2728 | pango_fc_convert_width_to_pango (int fc_stretch) |
2729 | { |
2730 | switch (fc_stretch) |
2731 | { |
2732 | case FC_WIDTH_NORMAL: |
2733 | return PANGO_STRETCH_NORMAL; |
2734 | case FC_WIDTH_ULTRACONDENSED: |
2735 | return PANGO_STRETCH_ULTRA_CONDENSED; |
2736 | case FC_WIDTH_EXTRACONDENSED: |
2737 | return PANGO_STRETCH_EXTRA_CONDENSED; |
2738 | case FC_WIDTH_CONDENSED: |
2739 | return PANGO_STRETCH_CONDENSED; |
2740 | case FC_WIDTH_SEMICONDENSED: |
2741 | return PANGO_STRETCH_SEMI_CONDENSED; |
2742 | case FC_WIDTH_SEMIEXPANDED: |
2743 | return PANGO_STRETCH_SEMI_EXPANDED; |
2744 | case FC_WIDTH_EXPANDED: |
2745 | return PANGO_STRETCH_EXPANDED; |
2746 | case FC_WIDTH_EXTRAEXPANDED: |
2747 | return PANGO_STRETCH_EXTRA_EXPANDED; |
2748 | case FC_WIDTH_ULTRAEXPANDED: |
2749 | return PANGO_STRETCH_ULTRA_EXPANDED; |
2750 | default: |
2751 | return PANGO_STRETCH_NORMAL; |
2752 | } |
2753 | } |
2754 | |
2755 | /** |
2756 | * pango_fc_font_description_from_pattern: |
2757 | * @pattern: a `FcPattern` |
2758 | * @include_size: if %TRUE, the pattern will include the size from |
2759 | * the @pattern; otherwise the resulting pattern will be unsized. |
2760 | * (only %FC_SIZE is examined, not %FC_PIXEL_SIZE) |
2761 | * |
2762 | * Creates a `PangoFontDescription` that matches the specified |
2763 | * Fontconfig pattern as closely as possible. |
2764 | * |
2765 | * Many possible Fontconfig pattern values, such as %FC_RASTERIZER |
2766 | * or %FC_DPI, don't make sense in the context of `PangoFontDescription`, |
2767 | * so will be ignored. |
2768 | * |
2769 | * Return value: a new `PangoFontDescription`. Free with |
2770 | * pango_font_description_free(). |
2771 | * |
2772 | * Since: 1.4 |
2773 | */ |
2774 | PangoFontDescription * |
2775 | pango_fc_font_description_from_pattern (FcPattern *pattern, gboolean include_size) |
2776 | { |
2777 | return font_description_from_pattern (pattern, include_size, FALSE); |
2778 | } |
2779 | |
2780 | PangoFontDescription * |
2781 | font_description_from_pattern (FcPattern *pattern, |
2782 | gboolean include_size, |
2783 | gboolean shallow) |
2784 | { |
2785 | PangoFontDescription *desc; |
2786 | PangoStyle style; |
2787 | PangoWeight weight; |
2788 | PangoStretch stretch; |
2789 | double size; |
2790 | PangoGravity gravity; |
2791 | PangoVariant variant; |
2792 | gboolean all_caps; |
2793 | const char *s; |
2794 | int i; |
2795 | double d; |
2796 | FcResult res; |
2797 | |
2798 | desc = pango_font_description_new (); |
2799 | |
2800 | res = FcPatternGetString (p: pattern, FC_FAMILY, n: 0, s: (FcChar8 **) &s); |
2801 | g_assert (res == FcResultMatch); |
2802 | |
2803 | if (shallow) |
2804 | pango_font_description_set_family_static (desc, family: s); |
2805 | else |
2806 | pango_font_description_set_family (desc, family: s); |
2807 | |
2808 | if (FcPatternGetInteger (p: pattern, FC_SLANT, n: 0, i: &i) == FcResultMatch) |
2809 | style = pango_fc_convert_slant_to_pango (fc_style: i); |
2810 | else |
2811 | style = PANGO_STYLE_NORMAL; |
2812 | |
2813 | pango_font_description_set_style (desc, style); |
2814 | |
2815 | if (FcPatternGetDouble (p: pattern, FC_WEIGHT, n: 0, d: &d) == FcResultMatch) |
2816 | weight = pango_fc_convert_weight_to_pango (fc_weight: d); |
2817 | else |
2818 | weight = PANGO_WEIGHT_NORMAL; |
2819 | |
2820 | pango_font_description_set_weight (desc, weight); |
2821 | |
2822 | if (FcPatternGetInteger (p: pattern, FC_WIDTH, n: 0, i: &i) == FcResultMatch) |
2823 | stretch = pango_fc_convert_width_to_pango (fc_stretch: i); |
2824 | else |
2825 | stretch = PANGO_STRETCH_NORMAL; |
2826 | |
2827 | pango_font_description_set_stretch (desc, stretch); |
2828 | |
2829 | variant = PANGO_VARIANT_NORMAL; |
2830 | all_caps = FALSE; |
2831 | |
2832 | for (int i = 0; i < 32; i++) |
2833 | { |
2834 | if (FcPatternGetString (p: pattern, FC_FONT_FEATURES, n: i, s: (FcChar8 **)&s) == FcResultMatch) |
2835 | { |
2836 | if (strcmp (s1: s, s2: "smcp=1" ) == 0) |
2837 | { |
2838 | if (all_caps) |
2839 | variant = PANGO_VARIANT_ALL_SMALL_CAPS; |
2840 | else |
2841 | variant = PANGO_VARIANT_SMALL_CAPS; |
2842 | } |
2843 | else if (strcmp (s1: s, s2: "c2sc=1" ) == 0) |
2844 | { |
2845 | if (variant == PANGO_VARIANT_SMALL_CAPS) |
2846 | variant = PANGO_VARIANT_ALL_SMALL_CAPS; |
2847 | else |
2848 | all_caps = TRUE; |
2849 | } |
2850 | else if (strcmp (s1: s, s2: "pcap=1" ) == 0) |
2851 | { |
2852 | if (all_caps) |
2853 | variant = PANGO_VARIANT_ALL_PETITE_CAPS; |
2854 | else |
2855 | variant = PANGO_VARIANT_PETITE_CAPS; |
2856 | } |
2857 | else if (strcmp (s1: s, s2: "c2pc=1" ) == 0) |
2858 | { |
2859 | if (variant == PANGO_VARIANT_PETITE_CAPS) |
2860 | variant = PANGO_VARIANT_ALL_PETITE_CAPS; |
2861 | else |
2862 | all_caps = TRUE; |
2863 | } |
2864 | else if (strcmp (s1: s, s2: "unic=1" ) == 0) |
2865 | { |
2866 | variant = PANGO_VARIANT_UNICASE; |
2867 | } |
2868 | else if (strcmp (s1: s, s2: "titl=1" ) == 0) |
2869 | { |
2870 | variant = PANGO_VARIANT_TITLE_CAPS; |
2871 | } |
2872 | } |
2873 | else |
2874 | break; |
2875 | } |
2876 | |
2877 | pango_font_description_set_variant (desc, variant); |
2878 | |
2879 | if (include_size && FcPatternGetDouble (p: pattern, FC_SIZE, n: 0, d: &size) == FcResultMatch) |
2880 | { |
2881 | FcMatrix *fc_matrix; |
2882 | double scale_factor = 1; |
2883 | volatile double scaled_size; |
2884 | |
2885 | if (FcPatternGetMatrix (p: pattern, FC_MATRIX, n: 0, s: &fc_matrix) == FcResultMatch) |
2886 | { |
2887 | PangoMatrix mat = PANGO_MATRIX_INIT; |
2888 | |
2889 | mat.xx = fc_matrix->xx; |
2890 | mat.xy = fc_matrix->xy; |
2891 | mat.yx = fc_matrix->yx; |
2892 | mat.yy = fc_matrix->yy; |
2893 | |
2894 | scale_factor = pango_matrix_get_font_scale_factor (matrix: &mat); |
2895 | } |
2896 | |
2897 | /* We need to use a local variable to ensure that the compiler won't |
2898 | * implicitly cast it to integer while the result is kept in registers, |
2899 | * leading to a wrong approximation in i386 (with 387 FPU) |
2900 | */ |
2901 | scaled_size = scale_factor * size * PANGO_SCALE; |
2902 | pango_font_description_set_size (desc, size: scaled_size); |
2903 | } |
2904 | |
2905 | /* gravity is a bit different. we don't want to set it if it was not set on |
2906 | * the pattern */ |
2907 | if (FcPatternGetString (p: pattern, PANGO_FC_GRAVITY, n: 0, s: (FcChar8 **)&s) == FcResultMatch) |
2908 | { |
2909 | GEnumValue *value = g_enum_get_value_by_nick (enum_class: get_gravity_class (), nick: (char *)s); |
2910 | gravity = value->value; |
2911 | |
2912 | pango_font_description_set_gravity (desc, gravity); |
2913 | } |
2914 | |
2915 | if (include_size && FcPatternGetString (p: pattern, FC_FONT_VARIATIONS, n: 0, s: (FcChar8 **)&s) == FcResultMatch) |
2916 | { |
2917 | if (s && *s) |
2918 | { |
2919 | if (shallow) |
2920 | pango_font_description_set_variations_static (desc, variations: s); |
2921 | else |
2922 | pango_font_description_set_variations (desc, variations: s); |
2923 | } |
2924 | } |
2925 | |
2926 | return desc; |
2927 | } |
2928 | |
2929 | /* |
2930 | * PangoFcFace |
2931 | */ |
2932 | |
2933 | typedef PangoFontFaceClass PangoFcFaceClass; |
2934 | |
2935 | G_DEFINE_TYPE (PangoFcFace, pango_fc_face, PANGO_TYPE_FONT_FACE) |
2936 | |
2937 | static PangoFontDescription * |
2938 | make_alias_description (PangoFcFamily *fcfamily, |
2939 | gboolean bold, |
2940 | gboolean italic) |
2941 | { |
2942 | PangoFontDescription *desc = pango_font_description_new (); |
2943 | |
2944 | pango_font_description_set_family (desc, family: fcfamily->family_name); |
2945 | pango_font_description_set_style (desc, style: italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); |
2946 | pango_font_description_set_weight (desc, weight: bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL); |
2947 | |
2948 | return desc; |
2949 | } |
2950 | |
2951 | static PangoFontDescription * |
2952 | pango_fc_face_describe (PangoFontFace *face) |
2953 | { |
2954 | PangoFcFace *fcface = PANGO_FC_FACE (face); |
2955 | PangoFcFamily *fcfamily = fcface->family; |
2956 | PangoFontDescription *desc = NULL; |
2957 | |
2958 | if (G_UNLIKELY (!fcfamily)) |
2959 | return pango_font_description_new (); |
2960 | |
2961 | if (fcface->fake) |
2962 | { |
2963 | if (strcmp (s1: fcface->style, s2: "Regular" ) == 0) |
2964 | return make_alias_description (fcfamily, FALSE, FALSE); |
2965 | else if (strcmp (s1: fcface->style, s2: "Bold" ) == 0) |
2966 | return make_alias_description (fcfamily, TRUE, FALSE); |
2967 | else if (strcmp (s1: fcface->style, s2: "Italic" ) == 0) |
2968 | return make_alias_description (fcfamily, FALSE, TRUE); |
2969 | else /* Bold Italic */ |
2970 | return make_alias_description (fcfamily, TRUE, TRUE); |
2971 | } |
2972 | |
2973 | g_assert (fcface->pattern); |
2974 | desc = pango_fc_font_description_from_pattern (pattern: fcface->pattern, FALSE); |
2975 | |
2976 | return desc; |
2977 | } |
2978 | |
2979 | static const char * |
2980 | pango_fc_face_get_face_name (PangoFontFace *face) |
2981 | { |
2982 | PangoFcFace *fcface = PANGO_FC_FACE (face); |
2983 | |
2984 | return fcface->style; |
2985 | } |
2986 | |
2987 | static int |
2988 | compare_ints (gconstpointer ap, |
2989 | gconstpointer bp) |
2990 | { |
2991 | int a = *(int *)ap; |
2992 | int b = *(int *)bp; |
2993 | |
2994 | if (a == b) |
2995 | return 0; |
2996 | else if (a > b) |
2997 | return 1; |
2998 | else |
2999 | return -1; |
3000 | } |
3001 | |
3002 | static void |
3003 | pango_fc_face_list_sizes (PangoFontFace *face, |
3004 | int **sizes, |
3005 | int *n_sizes) |
3006 | { |
3007 | PangoFcFace *fcface = PANGO_FC_FACE (face); |
3008 | FcPattern *pattern; |
3009 | FcFontSet *fontset; |
3010 | FcObjectSet *objectset; |
3011 | FcFontSet *fonts; |
3012 | |
3013 | if (sizes) |
3014 | *sizes = NULL; |
3015 | *n_sizes = 0; |
3016 | if (G_UNLIKELY (!fcface->family || !fcface->family->fontmap)) |
3017 | return; |
3018 | |
3019 | pattern = FcPatternCreate (); |
3020 | FcPatternAddString (p: pattern, FC_FAMILY, s: (FcChar8*)(void*)fcface->family->family_name); |
3021 | FcPatternAddString (p: pattern, FC_STYLE, s: (FcChar8*)(void*)fcface->style); |
3022 | |
3023 | objectset = FcObjectSetCreate (); |
3024 | FcObjectSetAdd (os: objectset, FC_PIXEL_SIZE); |
3025 | |
3026 | fonts = pango_fc_font_map_get_config_fonts (fcfontmap: fcface->family->fontmap); |
3027 | fontset = FcFontSetList (config: fcface->family->fontmap->priv->config, sets: &fonts, nsets: 1, p: pattern, os: objectset); |
3028 | |
3029 | if (fontset) |
3030 | { |
3031 | GArray *size_array; |
3032 | double size, dpi = -1.0; |
3033 | int i, size_i, j; |
3034 | |
3035 | size_array = g_array_new (FALSE, FALSE, element_size: sizeof (int)); |
3036 | |
3037 | for (i = 0; i < fontset->nfont; i++) |
3038 | { |
3039 | for (j = 0; |
3040 | FcPatternGetDouble (p: fontset->fonts[i], FC_PIXEL_SIZE, n: j, d: &size) == FcResultMatch; |
3041 | j++) |
3042 | { |
3043 | if (dpi < 0) |
3044 | dpi = pango_fc_font_map_get_resolution (fcfontmap: fcface->family->fontmap, NULL); |
3045 | |
3046 | size_i = (int) (PANGO_SCALE * size * 72.0 / dpi); |
3047 | g_array_append_val (size_array, size_i); |
3048 | } |
3049 | } |
3050 | |
3051 | g_array_sort (array: size_array, compare_func: compare_ints); |
3052 | |
3053 | if (size_array->len == 0) |
3054 | { |
3055 | *n_sizes = 0; |
3056 | if (sizes) |
3057 | *sizes = NULL; |
3058 | g_array_free (array: size_array, TRUE); |
3059 | } |
3060 | else |
3061 | { |
3062 | *n_sizes = size_array->len; |
3063 | if (sizes) |
3064 | { |
3065 | *sizes = (int *) size_array->data; |
3066 | g_array_free (array: size_array, FALSE); |
3067 | } |
3068 | else |
3069 | g_array_free (array: size_array, TRUE); |
3070 | } |
3071 | |
3072 | FcFontSetDestroy (s: fontset); |
3073 | } |
3074 | else |
3075 | { |
3076 | *n_sizes = 0; |
3077 | if (sizes) |
3078 | *sizes = NULL; |
3079 | } |
3080 | |
3081 | FcPatternDestroy (p: pattern); |
3082 | FcObjectSetDestroy (os: objectset); |
3083 | } |
3084 | |
3085 | static gboolean |
3086 | pango_fc_face_is_synthesized (PangoFontFace *face) |
3087 | { |
3088 | PangoFcFace *fcface = PANGO_FC_FACE (face); |
3089 | |
3090 | return fcface->fake; |
3091 | } |
3092 | |
3093 | static PangoFontFamily * |
3094 | pango_fc_face_get_family (PangoFontFace *face) |
3095 | { |
3096 | PangoFcFace *fcface = PANGO_FC_FACE (face); |
3097 | |
3098 | return PANGO_FONT_FAMILY (fcface->family); |
3099 | } |
3100 | |
3101 | static void |
3102 | pango_fc_face_finalize (GObject *object) |
3103 | { |
3104 | PangoFcFace *fcface = PANGO_FC_FACE (object); |
3105 | |
3106 | g_free (mem: fcface->style); |
3107 | FcPatternDestroy (p: fcface->pattern); |
3108 | |
3109 | G_OBJECT_CLASS (pango_fc_face_parent_class)->finalize (object); |
3110 | } |
3111 | |
3112 | static void |
3113 | pango_fc_face_init (PangoFcFace *self) |
3114 | { |
3115 | } |
3116 | |
3117 | static void |
3118 | pango_fc_face_class_init (PangoFcFaceClass *class) |
3119 | { |
3120 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
3121 | |
3122 | object_class->finalize = pango_fc_face_finalize; |
3123 | |
3124 | class->describe = pango_fc_face_describe; |
3125 | class->get_face_name = pango_fc_face_get_face_name; |
3126 | class->list_sizes = pango_fc_face_list_sizes; |
3127 | class->is_synthesized = pango_fc_face_is_synthesized; |
3128 | class->get_family = pango_fc_face_get_family; |
3129 | } |
3130 | |
3131 | |
3132 | /* |
3133 | * PangoFcFamily |
3134 | */ |
3135 | |
3136 | typedef PangoFontFamilyClass PangoFcFamilyClass; |
3137 | |
3138 | static GType |
3139 | pango_fc_family_get_item_type (GListModel *list) |
3140 | { |
3141 | return PANGO_TYPE_FONT_FACE; |
3142 | } |
3143 | |
3144 | static void ensure_faces (PangoFcFamily *family); |
3145 | |
3146 | static guint |
3147 | pango_fc_family_get_n_items (GListModel *list) |
3148 | { |
3149 | PangoFcFamily *fcfamily = PANGO_FC_FAMILY (list); |
3150 | |
3151 | ensure_faces (family: fcfamily); |
3152 | |
3153 | return (guint)fcfamily->n_faces; |
3154 | } |
3155 | |
3156 | static gpointer |
3157 | pango_fc_family_get_item (GListModel *list, |
3158 | guint position) |
3159 | { |
3160 | PangoFcFamily *fcfamily = PANGO_FC_FAMILY (list); |
3161 | |
3162 | ensure_faces (family: fcfamily); |
3163 | |
3164 | if (position < fcfamily->n_faces) |
3165 | return g_object_ref (fcfamily->faces[position]); |
3166 | |
3167 | return NULL; |
3168 | } |
3169 | |
3170 | static void |
3171 | pango_fc_family_list_model_init (GListModelInterface *iface) |
3172 | { |
3173 | iface->get_item_type = pango_fc_family_get_item_type; |
3174 | iface->get_n_items = pango_fc_family_get_n_items; |
3175 | iface->get_item = pango_fc_family_get_item; |
3176 | } |
3177 | |
3178 | G_DEFINE_TYPE_WITH_CODE (PangoFcFamily, pango_fc_family, PANGO_TYPE_FONT_FAMILY, |
3179 | G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, pango_fc_family_list_model_init)) |
3180 | |
3181 | static PangoFcFace * |
3182 | create_face (PangoFcFamily *fcfamily, |
3183 | const char *style, |
3184 | FcPattern *pattern, |
3185 | gboolean fake) |
3186 | { |
3187 | PangoFcFace *face = g_object_new (PANGO_FC_TYPE_FACE, NULL); |
3188 | face->style = g_strdup (str: style); |
3189 | if (pattern) |
3190 | FcPatternReference (p: pattern); |
3191 | face->pattern = pattern; |
3192 | face->family = fcfamily; |
3193 | face->fake = fake; |
3194 | |
3195 | return face; |
3196 | } |
3197 | |
3198 | static int |
3199 | compare_face (const void *p1, const void *p2) |
3200 | { |
3201 | const PangoFcFace *f1 = *(const void **)p1; |
3202 | const PangoFcFace *f2 = *(const void **)p2; |
3203 | int w1, w2; |
3204 | int s1, s2; |
3205 | |
3206 | if (FcPatternGetInteger (p: f1->pattern, FC_WEIGHT, n: 0, i: &w1) != FcResultMatch) |
3207 | w1 = FC_WEIGHT_MEDIUM; |
3208 | |
3209 | if (FcPatternGetInteger (p: f1->pattern, FC_SLANT, n: 0, i: &s1) != FcResultMatch) |
3210 | s1 = FC_SLANT_ROMAN; |
3211 | |
3212 | if (FcPatternGetInteger (p: f2->pattern, FC_WEIGHT, n: 0, i: &w2) != FcResultMatch) |
3213 | w2 = FC_WEIGHT_MEDIUM; |
3214 | |
3215 | if (FcPatternGetInteger (p: f2->pattern, FC_SLANT, n: 0, i: &s2) != FcResultMatch) |
3216 | s2 = FC_SLANT_ROMAN; |
3217 | |
3218 | if (s1 != s2) |
3219 | return s1 - s2; /* roman < italic < oblique */ |
3220 | |
3221 | return w1 - w2; /* from light to heavy */ |
3222 | } |
3223 | |
3224 | static void |
3225 | ensure_faces (PangoFcFamily *fcfamily) |
3226 | { |
3227 | PangoFcFontMap *fcfontmap = fcfamily->fontmap; |
3228 | PangoFcFontMapPrivate *priv = fcfontmap->priv; |
3229 | |
3230 | if (fcfamily->n_faces < 0) |
3231 | { |
3232 | FcFontSet *fontset; |
3233 | int i; |
3234 | |
3235 | if (is_alias_family (family_name: fcfamily->family_name) || priv->closed) |
3236 | { |
3237 | fcfamily->n_faces = 4; |
3238 | fcfamily->faces = g_new (PangoFcFace *, fcfamily->n_faces); |
3239 | |
3240 | i = 0; |
3241 | fcfamily->faces[i++] = create_face (fcfamily, style: "Regular" , NULL, TRUE); |
3242 | fcfamily->faces[i++] = create_face (fcfamily, style: "Bold" , NULL, TRUE); |
3243 | fcfamily->faces[i++] = create_face (fcfamily, style: "Italic" , NULL, TRUE); |
3244 | fcfamily->faces[i++] = create_face (fcfamily, style: "Bold Italic" , NULL, TRUE); |
3245 | fcfamily->faces[0]->regular = 1; |
3246 | } |
3247 | else |
3248 | { |
3249 | enum { |
3250 | REGULAR, |
3251 | ITALIC, |
3252 | BOLD, |
3253 | BOLD_ITALIC |
3254 | }; |
3255 | /* Regular, Italic, Bold, Bold Italic */ |
3256 | gboolean has_face [4] = { FALSE, FALSE, FALSE, FALSE }; |
3257 | PangoFcFace **faces; |
3258 | gint num = 0; |
3259 | int regular_weight; |
3260 | int regular_idx; |
3261 | |
3262 | fontset = fcfamily->patterns; |
3263 | |
3264 | /* at most we have 3 additional artificial faces */ |
3265 | faces = g_new (PangoFcFace *, fontset->nfont + 3); |
3266 | |
3267 | regular_weight = 0; |
3268 | regular_idx = -1; |
3269 | |
3270 | for (i = 0; i < fontset->nfont; i++) |
3271 | { |
3272 | const char *style, *font_style = NULL; |
3273 | int weight, slant; |
3274 | |
3275 | if (FcPatternGetInteger(p: fontset->fonts[i], FC_WEIGHT, n: 0, i: &weight) != FcResultMatch) |
3276 | weight = FC_WEIGHT_MEDIUM; |
3277 | |
3278 | if (FcPatternGetInteger(p: fontset->fonts[i], FC_SLANT, n: 0, i: &slant) != FcResultMatch) |
3279 | slant = FC_SLANT_ROMAN; |
3280 | |
3281 | { |
3282 | gboolean variable; |
3283 | if (FcPatternGetBool(p: fontset->fonts[i], FC_VARIABLE, n: 0, b: &variable) != FcResultMatch) |
3284 | variable = FALSE; |
3285 | if (variable) /* skip the variable face */ |
3286 | continue; |
3287 | } |
3288 | |
3289 | if (FcPatternGetString (p: fontset->fonts[i], FC_STYLE, n: 0, s: (FcChar8 **)(void*)&font_style) != FcResultMatch) |
3290 | font_style = NULL; |
3291 | |
3292 | if (font_style && strcmp (s1: font_style, s2: "Regular" ) == 0) |
3293 | { |
3294 | regular_weight = FC_WEIGHT_MEDIUM; |
3295 | regular_idx = num; |
3296 | } |
3297 | |
3298 | if (weight <= FC_WEIGHT_MEDIUM) |
3299 | { |
3300 | if (slant == FC_SLANT_ROMAN) |
3301 | { |
3302 | has_face[REGULAR] = TRUE; |
3303 | style = "Regular" ; |
3304 | if (weight > regular_weight) |
3305 | { |
3306 | regular_weight = weight; |
3307 | regular_idx = num; |
3308 | } |
3309 | } |
3310 | else |
3311 | { |
3312 | has_face[ITALIC] = TRUE; |
3313 | style = "Italic" ; |
3314 | } |
3315 | } |
3316 | else |
3317 | { |
3318 | if (slant == FC_SLANT_ROMAN) |
3319 | { |
3320 | has_face[BOLD] = TRUE; |
3321 | style = "Bold" ; |
3322 | } |
3323 | else |
3324 | { |
3325 | has_face[BOLD_ITALIC] = TRUE; |
3326 | style = "Bold Italic" ; |
3327 | } |
3328 | } |
3329 | |
3330 | if (!font_style) |
3331 | font_style = style; |
3332 | faces[num++] = create_face (fcfamily, style: font_style, pattern: fontset->fonts[i], FALSE); |
3333 | } |
3334 | |
3335 | if (has_face[REGULAR]) |
3336 | { |
3337 | if (!has_face[ITALIC]) |
3338 | faces[num++] = create_face (fcfamily, style: "Italic" , NULL, TRUE); |
3339 | if (!has_face[BOLD]) |
3340 | faces[num++] = create_face (fcfamily, style: "Bold" , NULL, TRUE); |
3341 | |
3342 | } |
3343 | if ((has_face[REGULAR] || has_face[ITALIC] || has_face[BOLD]) && !has_face[BOLD_ITALIC]) |
3344 | faces[num++] = create_face (fcfamily, style: "Bold Italic" , NULL, TRUE); |
3345 | |
3346 | if (regular_idx != -1) |
3347 | faces[regular_idx]->regular = 1; |
3348 | |
3349 | faces = g_renew (PangoFcFace *, faces, num); |
3350 | |
3351 | qsort (base: faces, nmemb: num, size: sizeof (PangoFcFace *), compar: compare_face); |
3352 | |
3353 | fcfamily->n_faces = num; |
3354 | fcfamily->faces = faces; |
3355 | } |
3356 | } |
3357 | } |
3358 | |
3359 | static void |
3360 | pango_fc_family_list_faces (PangoFontFamily *family, |
3361 | PangoFontFace ***faces, |
3362 | int *n_faces) |
3363 | { |
3364 | PangoFcFamily *fcfamily = PANGO_FC_FAMILY (family); |
3365 | |
3366 | if (faces) |
3367 | *faces = NULL; |
3368 | |
3369 | if (n_faces) |
3370 | *n_faces = 0; |
3371 | |
3372 | if (G_UNLIKELY (!fcfamily->fontmap)) |
3373 | return; |
3374 | |
3375 | ensure_faces (fcfamily); |
3376 | |
3377 | if (n_faces) |
3378 | *n_faces = fcfamily->n_faces; |
3379 | |
3380 | if (faces) |
3381 | *faces = g_memdup2 (mem: fcfamily->faces, byte_size: fcfamily->n_faces * sizeof (PangoFontFace *)); |
3382 | } |
3383 | |
3384 | static PangoFontFace * |
3385 | pango_fc_family_get_face (PangoFontFamily *family, |
3386 | const char *name) |
3387 | { |
3388 | PangoFcFamily *fcfamily = PANGO_FC_FAMILY (family); |
3389 | int i; |
3390 | |
3391 | ensure_faces (fcfamily); |
3392 | |
3393 | for (i = 0; i < fcfamily->n_faces; i++) |
3394 | { |
3395 | PangoFontFace *face = PANGO_FONT_FACE (fcfamily->faces[i]); |
3396 | |
3397 | if ((name != NULL && strcmp (s1: name, s2: pango_font_face_get_face_name (face)) == 0) || |
3398 | (name == NULL && PANGO_FC_FACE (face)->regular)) |
3399 | return face; |
3400 | } |
3401 | |
3402 | return NULL; |
3403 | } |
3404 | |
3405 | static const char * |
3406 | pango_fc_family_get_name (PangoFontFamily *family) |
3407 | { |
3408 | PangoFcFamily *fcfamily = PANGO_FC_FAMILY (family); |
3409 | |
3410 | return fcfamily->family_name; |
3411 | } |
3412 | |
3413 | static gboolean |
3414 | pango_fc_family_is_monospace (PangoFontFamily *family) |
3415 | { |
3416 | PangoFcFamily *fcfamily = PANGO_FC_FAMILY (family); |
3417 | |
3418 | return fcfamily->spacing == FC_MONO || |
3419 | fcfamily->spacing == FC_DUAL || |
3420 | fcfamily->spacing == FC_CHARCELL; |
3421 | } |
3422 | |
3423 | static gboolean |
3424 | pango_fc_family_is_variable (PangoFontFamily *family) |
3425 | { |
3426 | PangoFcFamily *fcfamily = PANGO_FC_FAMILY (family); |
3427 | |
3428 | return fcfamily->variable; |
3429 | } |
3430 | |
3431 | static void |
3432 | pango_fc_family_finalize (GObject *object) |
3433 | { |
3434 | int i; |
3435 | PangoFcFamily *fcfamily = PANGO_FC_FAMILY (object); |
3436 | |
3437 | g_free (mem: fcfamily->family_name); |
3438 | |
3439 | for (i = 0; i < fcfamily->n_faces; i++) |
3440 | { |
3441 | fcfamily->faces[i]->family = NULL; |
3442 | g_object_unref (object: fcfamily->faces[i]); |
3443 | } |
3444 | FcFontSetDestroy (s: fcfamily->patterns); |
3445 | g_free (mem: fcfamily->faces); |
3446 | |
3447 | G_OBJECT_CLASS (pango_fc_family_parent_class)->finalize (object); |
3448 | } |
3449 | |
3450 | static void |
3451 | pango_fc_family_class_init (PangoFcFamilyClass *class) |
3452 | { |
3453 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
3454 | |
3455 | object_class->finalize = pango_fc_family_finalize; |
3456 | |
3457 | class->list_faces = pango_fc_family_list_faces; |
3458 | class->get_face = pango_fc_family_get_face; |
3459 | class->get_name = pango_fc_family_get_name; |
3460 | class->is_monospace = pango_fc_family_is_monospace; |
3461 | class->is_variable = pango_fc_family_is_variable; |
3462 | } |
3463 | |
3464 | static void |
3465 | pango_fc_family_init (PangoFcFamily *fcfamily) |
3466 | { |
3467 | fcfamily->n_faces = -1; |
3468 | } |
3469 | |
3470 | /** |
3471 | * pango_fc_font_map_get_hb_face: (skip) |
3472 | * @fcfontmap: a `PangoFcFontMap` |
3473 | * @fcfont: a `PangoFcFont` |
3474 | * |
3475 | * Retrieves the `hb_face_t` for the given `PangoFcFont`. |
3476 | * |
3477 | * Returns: (transfer none) (nullable): the `hb_face_t` |
3478 | * for the given font |
3479 | * |
3480 | * Since: 1.44 |
3481 | */ |
3482 | hb_face_t * |
3483 | pango_fc_font_map_get_hb_face (PangoFcFontMap *fcfontmap, |
3484 | PangoFcFont *fcfont) |
3485 | { |
3486 | PangoFcFontFaceData *data; |
3487 | |
3488 | data = pango_fc_font_map_get_font_face_data (fcfontmap, font_pattern: fcfont->font_pattern); |
3489 | |
3490 | if (!data->hb_face) |
3491 | { |
3492 | hb_blob_t *blob; |
3493 | |
3494 | blob = hb_blob_create_from_file (file_name: data->filename); |
3495 | data->hb_face = hb_face_create (blob, index: data->id); |
3496 | hb_blob_destroy (blob); |
3497 | } |
3498 | |
3499 | return data->hb_face; |
3500 | } |
3501 | |