Coverage Report

Created: 2026-10-22 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libass/libass/ass_fontconfig.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2011 Grigori Goronzy <greg@chown.ath.cx>
3
 *
4
 * This file is part of libass.
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include "config.h"
20
#include "ass_compat.h"
21
22
#include <stdio.h>
23
#include <stdlib.h>
24
#include <sys/types.h>
25
26
#include <fontconfig/fontconfig.h>
27
#include <fontconfig/fcfreetype.h>
28
29
#include "ass_fontconfig.h"
30
#include "ass_fontselect.h"
31
#include "ass_utils.h"
32
33
2.64M
#define MAX_NAME 100
34
35
typedef struct fc_private {
36
    FcConfig *config;
37
    FcFontSet *fallbacks;
38
    FcCharSet *fallback_chars;
39
} ProviderPrivate;
40
41
static bool check_postscript(void *priv)
42
0
{
43
0
    FcPattern *pat = (FcPattern *)priv;
44
0
    char *format;
45
46
0
    FcResult result =
47
0
        FcPatternGetString(pat, FC_FONTFORMAT, 0, (FcChar8 **)&format);
48
0
    if (result != FcResultMatch)
49
0
        return false;
50
51
0
    return !strcmp(format, "Type 1") || !strcmp(format, "Type 42") ||
52
0
           !strcmp(format, "CID Type 1") || !strcmp(format, "CFF");
53
0
}
54
55
static bool check_glyph(void *priv, uint32_t code)
56
11.1k
{
57
11.1k
    FcPattern *pat = (FcPattern *)priv;
58
11.1k
    FcCharSet *charset;
59
60
11.1k
    if (!pat)
61
0
        return true;
62
63
11.1k
    if (code == 0)
64
11.1k
        return true;
65
66
43
    FcResult result = FcPatternGetCharSet(pat, FC_CHARSET, 0, &charset);
67
43
    if (result != FcResultMatch)
68
0
        return false;
69
43
    if (FcCharSetHasChar(charset, code) == FcTrue)
70
31
        return true;
71
12
    return false;
72
43
}
73
74
static void destroy_font(void *priv)
75
264k
{
76
264k
    FcPatternDestroy((FcPattern *) priv);
77
264k
}
78
79
static void destroy(void *priv)
80
12.0k
{
81
12.0k
    ProviderPrivate *fc = (ProviderPrivate *)priv;
82
83
12.0k
    if (fc->fallback_chars)
84
6.88k
        FcCharSetDestroy(fc->fallback_chars);
85
12.0k
    if (fc->fallbacks)
86
6.88k
        FcFontSetDestroy(fc->fallbacks);
87
12.0k
    FcConfigDestroy(fc->config);
88
12.0k
    free(fc);
89
12.0k
}
90
91
static bool scan_fonts(FcConfig *config, ASS_FontProvider *provider)
92
12.0k
{
93
12.0k
    int i;
94
12.0k
    FcFontSet *fonts;
95
12.0k
    ASS_FontProviderMetaData meta = {0};
96
97
    // get list of fonts;
98
    // sorting by default pattern prefers regular variants
99
12.0k
    FcPattern *pat = FcPatternCreate();
100
12.0k
    if (!pat)
101
0
        return false;
102
103
#if FC_VERSION >= 21700
104
    FcConfigSetDefaultSubstitute(config, pat);
105
#else
106
12.0k
    FcDefaultSubstitute(pat);
107
12.0k
#endif
108
12.0k
    FcResult res;
109
    // trim=FcFalse returns all system fonts
110
12.0k
    fonts = FcFontSort(config, pat, FcFalse, NULL, &res);
111
12.0k
    FcPatternDestroy(pat);
112
12.0k
    if (res != FcResultMatch) {
113
0
        if (fonts)
114
0
            FcFontSetDestroy(fonts);
115
0
        return false;
116
0
    }
117
118
    // fill font_info list
119
276k
    for (i = 0; i < fonts->nfont; i++) {
120
264k
        FcPattern *pat = fonts->fonts[i];
121
264k
        FcBool outline;
122
264k
        int index;
123
264k
        double weight;
124
264k
        int slant;
125
264k
        char *path;
126
264k
        char *fullnames[MAX_NAME];
127
264k
        char *families[MAX_NAME];
128
129
        // skip non-outline fonts
130
264k
        FcResult result = FcPatternGetBool(pat, FC_OUTLINE, 0, &outline);
131
264k
        if (result != FcResultMatch || outline != FcTrue)
132
0
            continue;
133
134
        // simple types
135
264k
        result  = FcPatternGetInteger(pat, FC_SLANT, 0, &slant);
136
264k
        result |= FcPatternGetDouble(pat, FC_WEIGHT, 0, &weight);
137
264k
        result |= FcPatternGetInteger(pat, FC_INDEX, 0, &index);
138
264k
        if (result != FcResultMatch)
139
0
            continue;
140
141
        // fontconfig uses its own weight scale, apparently derived
142
        // from typographical weight. we're using truetype weights, so
143
        // convert appropriately.
144
264k
#if FC_VERSION >= 21292
145
264k
        meta.weight = FcWeightToOpenTypeDouble(weight) + 0.5;
146
#elif FC_VERSION >= 21191
147
        // Versions prior to 2.12.92 only had integer precision.
148
        meta.weight = FcWeightToOpenType(weight + 0.5) + 0.5;
149
#else
150
        // On older fontconfig, FcWeightToOpenType is unavailable, and its inverse was
151
        // implemented more simply, using an if/else ladder instead of linear interpolation.
152
        // We implement an inverse of that ladder here.
153
        // We don't expect actual FC caches from these versions to have intermediate
154
        // values, so the average checks are only for completeness.
155
#define AVG(x, y) (((double)x + y) / 2)
156
#ifndef FC_WEIGHT_SEMILIGHT
157
#define FC_WEIGHT_SEMILIGHT 55
158
#endif
159
        if (weight < AVG(FC_WEIGHT_THIN, FC_WEIGHT_EXTRALIGHT))
160
            meta.weight = 100;
161
        else if (weight < AVG(FC_WEIGHT_EXTRALIGHT, FC_WEIGHT_LIGHT))
162
            meta.weight = 200;
163
        else if (weight < AVG(FC_WEIGHT_LIGHT, FC_WEIGHT_SEMILIGHT))
164
            meta.weight = 300;
165
        else if (weight < AVG(FC_WEIGHT_SEMILIGHT, FC_WEIGHT_BOOK))
166
            meta.weight = 350;
167
        else if (weight < AVG(FC_WEIGHT_BOOK, FC_WEIGHT_REGULAR))
168
            meta.weight = 380;
169
        else if (weight < AVG(FC_WEIGHT_REGULAR, FC_WEIGHT_MEDIUM))
170
            meta.weight = 400;
171
        else if (weight < AVG(FC_WEIGHT_MEDIUM, FC_WEIGHT_SEMIBOLD))
172
            meta.weight = 500;
173
        else if (weight < AVG(FC_WEIGHT_SEMIBOLD, FC_WEIGHT_BOLD))
174
            meta.weight = 600;
175
        else if (weight < AVG(FC_WEIGHT_BOLD, FC_WEIGHT_EXTRABOLD))
176
            meta.weight = 700;
177
        else if (weight < AVG(FC_WEIGHT_EXTRABOLD, FC_WEIGHT_BLACK))
178
            meta.weight = 800;
179
        else if (weight < AVG(FC_WEIGHT_BLACK, FC_WEIGHT_EXTRABLACK))
180
            meta.weight = 900;
181
        else
182
            meta.weight = 1000;
183
#endif
184
185
        // Take a guess at the italic flag
186
264k
        meta.style_flags = (slant >= FC_SLANT_ITALIC) ? FT_STYLE_FLAG_ITALIC : 0;
187
188
        // path
189
264k
        result = FcPatternGetString(pat, FC_FILE, 0, (FcChar8 **)&path);
190
264k
        if (result != FcResultMatch)
191
0
            continue;
192
193
        // read family names
194
264k
        meta.n_family = 0;
195
638k
        while (meta.n_family < MAX_NAME &&
196
638k
                FcPatternGetString(pat, FC_FAMILY, meta.n_family,
197
638k
                    (FcChar8 **)&families[meta.n_family]) == FcResultMatch)
198
373k
            meta.n_family++;
199
264k
        meta.families = families;
200
201
        // read fullnames
202
264k
        meta.n_fullname = 0;
203
529k
        while (meta.n_fullname < MAX_NAME &&
204
529k
                FcPatternGetString(pat, FC_FULLNAME, meta.n_fullname,
205
529k
                    (FcChar8 **)&fullnames[meta.n_fullname]) == FcResultMatch)
206
264k
            meta.n_fullname++;
207
264k
        meta.fullnames = fullnames;
208
209
        // read PostScript name
210
264k
        result = FcPatternGetString(pat, FC_POSTSCRIPT_NAME, 0,
211
264k
                (FcChar8 **)&meta.postscript_name);
212
264k
        if (result != FcResultMatch)
213
0
            meta.postscript_name = NULL;
214
215
264k
        FcPatternReference(pat);
216
264k
        ass_font_provider_add_font(provider, &meta, path, index, (void *)pat);
217
264k
    }
218
219
12.0k
    FcFontSetDestroy(fonts);
220
12.0k
    return true;
221
12.0k
}
222
223
static void cache_fallbacks(ProviderPrivate *fc)
224
25.5k
{
225
25.5k
    FcResult result;
226
227
25.5k
    if (fc->fallbacks)
228
18.6k
        return;
229
230
    // Create a suitable pattern
231
6.88k
    FcPattern *pat = FcPatternCreate();
232
6.88k
    if (!pat)
233
0
        return;
234
6.88k
    FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)"sans-serif");
235
6.88k
    FcPatternAddBool(pat, FC_OUTLINE, FcTrue);
236
6.88k
    FcConfigSubstitute(fc->config, pat, FcMatchPattern);
237
#if FC_VERSION >= 21700
238
    FcConfigSetDefaultSubstitute(fc->config, pat);
239
#else
240
6.88k
    FcDefaultSubstitute(pat);
241
6.88k
#endif
242
243
    // FC_LANG is automatically set according to locale, but this results
244
    // in strange sorting sometimes, so remove the attribute completely.
245
6.88k
    FcPatternDel(pat, FC_LANG);
246
247
    // Sort installed fonts and eliminate duplicates; this can be very
248
    // expensive.
249
6.88k
    fc->fallbacks = FcFontSort(fc->config, pat, FcTrue, &fc->fallback_chars,
250
6.88k
            &result);
251
252
    // If this fails, just add an empty set
253
    // (if it fails, cache_fallbacks will just be reattempted later)
254
6.88k
    if (result != FcResultMatch) {
255
0
        if (fc->fallbacks)
256
0
            FcFontSetDestroy(fc->fallbacks);
257
0
        fc->fallbacks = FcFontSetCreate();
258
0
    }
259
260
6.88k
    FcPatternDestroy(pat);
261
6.88k
}
262
263
static char *get_fallback(void *priv, ASS_Library *lib,
264
                          const char *family, uint32_t codepoint)
265
25.5k
{
266
25.5k
    ProviderPrivate *fc = (ProviderPrivate *)priv;
267
25.5k
    FcResult result;
268
269
25.5k
    cache_fallbacks(fc);
270
271
25.5k
    if (!fc->fallbacks || fc->fallbacks->nfont == 0)
272
0
        return NULL;
273
274
25.5k
    if (codepoint == 0) {
275
11.1k
        char *family = NULL;
276
11.1k
        result = FcPatternGetString(fc->fallbacks->fonts[0], FC_FAMILY, 0,
277
11.1k
                (FcChar8 **)&family);
278
11.1k
        if (result == FcResultMatch) {
279
11.1k
            return strdup(family);
280
11.1k
        } else {
281
0
            return NULL;
282
0
        }
283
11.1k
    }
284
285
    // fallback_chars is the union of all available charsets, so
286
    // if we can't find the glyph in there, the system does not
287
    // have any font to render this glyph.
288
14.4k
    if (FcCharSetHasChar(fc->fallback_chars, codepoint) == FcFalse)
289
14.4k
        return NULL;
290
291
44
    for (int j = 0; j < fc->fallbacks->nfont; j++) {
292
44
        FcPattern *pattern = fc->fallbacks->fonts[j];
293
294
44
        FcCharSet *charset;
295
44
        result = FcPatternGetCharSet(pattern, FC_CHARSET, 0, &charset);
296
297
44
        if (result == FcResultMatch && FcCharSetHasChar(charset,
298
44
                    codepoint)) {
299
22
            char *family = NULL;
300
22
            result = FcPatternGetString(pattern, FC_FAMILY, 0,
301
22
                    (FcChar8 **)&family);
302
22
            if (result == FcResultMatch) {
303
22
                return strdup(family);
304
22
            } else {
305
0
                return NULL;
306
0
            }
307
22
        }
308
44
    }
309
310
    // we shouldn't get here
311
0
    return NULL;
312
22
}
313
314
static void get_substitutions(void *priv, const char *name,
315
                              ASS_FontProviderMetaData *meta)
316
61.8k
{
317
61.8k
    ProviderPrivate *fc = (ProviderPrivate *)priv;
318
319
61.8k
    FcPattern *pat = FcPatternCreate();
320
61.8k
    if (!pat)
321
0
        return;
322
323
61.8k
    FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)name);
324
61.8k
    FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)"__libass_delimiter");
325
61.8k
    FcPatternAddBool(pat, FC_OUTLINE, FcTrue);
326
61.8k
    if (!FcConfigSubstitute(fc->config, pat, FcMatchPattern))
327
0
        goto cleanup;
328
329
    // read and strdup fullnames
330
61.8k
    meta->n_fullname = 0;
331
61.8k
    meta->fullnames = calloc(MAX_NAME, sizeof(char *));
332
61.8k
    if (!meta->fullnames)
333
0
        goto cleanup;
334
335
61.8k
    char *alias = NULL;
336
123k
    while (FcPatternGetString(pat, FC_FAMILY, meta->n_fullname,
337
123k
                (FcChar8 **)&alias) == FcResultMatch
338
123k
                && meta->n_fullname < MAX_NAME
339
123k
                && strcmp(alias, "__libass_delimiter") != 0) {
340
61.7k
        alias = strdup(alias);
341
61.7k
        if (!alias)
342
0
            goto cleanup;
343
61.7k
        meta->fullnames[meta->n_fullname] = alias;
344
61.7k
        meta->n_fullname++;
345
61.7k
    }
346
347
61.8k
cleanup:
348
61.8k
    FcPatternDestroy(pat);
349
61.8k
}
350
351
static ASS_FontProviderFuncs fontconfig_callbacks = {
352
    .check_postscript   = check_postscript,
353
    .check_glyph        = check_glyph,
354
    .destroy_font       = destroy_font,
355
    .destroy_provider   = destroy,
356
    .get_substitutions  = get_substitutions,
357
    .get_fallback       = get_fallback,
358
};
359
360
ASS_FontProvider *
361
ass_fontconfig_add_provider(ASS_Library *lib, ASS_FontSelector *selector,
362
                            const char *config, FT_Library ftlib)
363
12.0k
{
364
12.0k
    int rc = FcResultNoMatch;
365
12.0k
    ProviderPrivate *fc = NULL;
366
12.0k
    ASS_FontProvider *provider = NULL;
367
368
12.0k
    fc = calloc(1, sizeof(ProviderPrivate));
369
12.0k
    if (fc == NULL)
370
0
        return NULL;
371
372
    // build and load fontconfig configuration
373
12.0k
    fc->config = FcConfigCreate();
374
375
12.0k
    if (fc->config)
376
12.0k
        rc = FcConfigParseAndLoad(fc->config, (unsigned char *) config, FcTrue);
377
12.0k
    if (!rc) {
378
12.0k
        ass_msg(lib, MSGL_WARN, "No usable fontconfig configuration "
379
12.0k
                "file found, using fallback.");
380
12.0k
        FcConfigDestroy(fc->config);
381
12.0k
        fc->config = FcInitLoadConfig();
382
12.0k
    }
383
384
12.0k
    if (fc->config)
385
12.0k
        rc = FcConfigBuildFonts(fc->config);
386
387
12.0k
    if (!rc || !fc->config) {
388
0
        ass_msg(lib, MSGL_ERR,
389
0
                "No valid fontconfig configuration found!");
390
0
        FcConfigDestroy(fc->config);
391
0
        free(fc);
392
0
        return NULL;
393
0
    }
394
395
    // create font provider
396
12.0k
    provider = ass_font_provider_new(selector, &fontconfig_callbacks, fc);
397
398
    // build database from system fonts
399
12.0k
    if (!scan_fonts(fc->config, provider))
400
0
        ass_msg(lib, MSGL_ERR, "Failed to load fontconfig fonts!");
401
402
12.0k
    return provider;
403
12.0k
}