Coverage Report

Created: 2025-11-09 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libass/libass/ass_fontselect.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
3
 * Copyright (C) 2011 Grigori Goronzy <greg@chown.ath.cx>
4
 *
5
 * This file is part of libass.
6
 *
7
 * Permission to use, copy, modify, and distribute this software for any
8
 * purpose with or without fee is hereby granted, provided that the above
9
 * copyright notice and this permission notice appear in all copies.
10
 *
11
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
 */
19
20
#include "config.h"
21
#include "ass_compat.h"
22
23
#include <stdlib.h>
24
#include <stdio.h>
25
#include <limits.h>
26
#include <assert.h>
27
#include <string.h>
28
#include <sys/types.h>
29
#include <sys/stat.h>
30
#include <inttypes.h>
31
#include <limits.h>
32
#include <ft2build.h>
33
#include <sys/types.h>
34
#include FT_FREETYPE_H
35
#include FT_SFNT_NAMES_H
36
#include FT_TRUETYPE_IDS_H
37
#include FT_TRUETYPE_TABLES_H
38
39
#include "ass_utils.h"
40
#include "ass.h"
41
#include "ass_library.h"
42
#include "ass_filesystem.h"
43
#include "ass_fontselect.h"
44
#include "ass_fontconfig.h"
45
#include "ass_coretext.h"
46
#include "ass_directwrite.h"
47
#include "ass_font.h"
48
#include "ass_string.h"
49
50
10.2k
#define ABS(x) ((x) < 0 ? -(x) : (x))
51
0
#define MAX_FULLNAME 100
52
53
// internal font database element
54
// all strings are utf-8
55
struct font_info {
56
    int uid;            // unique font face id
57
58
    char **families;    // family name
59
    char **fullnames;   // list of localized fullnames (e.g. Arial Bold Italic)
60
    int n_family;
61
    int n_fullname;
62
63
    FT_Long style_flags;
64
    int weight;           // TrueType scale, 100-900
65
66
    // how to access this face
67
    char *path;            // absolute path
68
    int index;             // font index inside font collections
69
70
    char *postscript_name; // can be used as an alternative to index to
71
                           // identify a font inside a collection
72
73
    char *extended_family;
74
75
    // font source
76
    ASS_FontProvider *provider;
77
78
    // private data for callbacks
79
    void *priv;
80
81
    // unused if the provider has a check_postscript function
82
    bool is_postscript;
83
};
84
85
struct font_selector {
86
    ASS_Library *library;
87
    FT_Library ftlibrary;
88
89
    // uid counter
90
    int uid;
91
92
    // fallbacks
93
    char *family_default;
94
    char *path_default;
95
    int index_default;
96
97
    // font database
98
    int n_font;
99
    int alloc_font;
100
    ASS_FontInfo *font_infos;
101
102
    ASS_FontProvider *default_provider;
103
    ASS_FontProvider *embedded_provider;
104
};
105
106
struct font_provider {
107
    ASS_FontSelector *parent;
108
    ASS_FontProviderFuncs funcs;
109
    void *priv;
110
};
111
112
typedef struct font_data_ft FontDataFT;
113
struct font_data_ft {
114
    ASS_Library *lib;
115
    FT_Face face;
116
    int idx;
117
};
118
119
static bool check_glyph_ft(void *data, uint32_t codepoint)
120
0
{
121
0
    FontDataFT *fd = (FontDataFT *)data;
122
123
0
    if (!codepoint)
124
0
        return true;
125
126
0
    return !!FT_Get_Char_Index(fd->face, codepoint);
127
0
}
128
129
static void destroy_font_ft(void *data)
130
0
{
131
0
    FontDataFT *fd = (FontDataFT *)data;
132
133
0
    FT_Done_Face(fd->face);
134
0
    free(fd);
135
0
}
136
137
static size_t
138
get_data_embedded(void *data, unsigned char *buf, size_t offset, size_t len)
139
0
{
140
0
    FontDataFT *ft = (FontDataFT *)data;
141
0
    ASS_Fontdata *fd = ft->lib->fontdata;
142
0
    int i = ft->idx;
143
144
0
    if (buf == NULL)
145
0
        return fd[i].size;
146
147
0
    if (offset >= fd[i].size)
148
0
        return 0;
149
150
0
    if (len > fd[i].size - offset)
151
0
        len = fd[i].size - offset;
152
153
0
    memcpy(buf, fd[i].data + offset, len);
154
0
    return len;
155
0
}
156
157
static ASS_FontProviderFuncs ft_funcs = {
158
    .get_data          = get_data_embedded,
159
    .check_glyph       = check_glyph_ft,
160
    .destroy_font      = destroy_font_ft,
161
};
162
163
static void load_fonts_from_dir(ASS_Library *library, const char *dir)
164
0
{
165
0
    ASS_Dir d;
166
0
    if (!ass_open_dir(&d, dir))
167
0
        return;
168
0
    while (true) {
169
0
        const char *name = ass_read_dir(&d);
170
0
        if (!name)
171
0
            break;
172
0
        if (name[0] == '.')
173
0
            continue;
174
0
        const char *path = ass_current_file_path(&d);
175
0
        if (!path)
176
0
            continue;
177
0
        ass_msg(library, MSGL_INFO, "Loading font file '%s'", path);
178
0
        size_t size = 0;
179
0
        void *data = ass_load_file(library, path, FN_DIR_LIST, &size);
180
0
        if (data) {
181
0
            ass_add_font(library, name, data, size);
182
0
            free(data);
183
0
        }
184
0
    }
185
0
    ass_close_dir(&d);
186
0
}
187
188
/**
189
 * \brief Create a bare font provider.
190
 * \param selector parent selector. The provider will be attached to it.
191
 * \param funcs callback/destroy functions
192
 * \param data private data of the provider
193
 * \return the font provider
194
 */
195
ASS_FontProvider *
196
ass_font_provider_new(ASS_FontSelector *selector, ASS_FontProviderFuncs *funcs,
197
                      void *data)
198
23.4k
{
199
23.4k
    assert(funcs->check_glyph && funcs->destroy_font);
200
201
23.4k
    ASS_FontProvider *provider = calloc(1, sizeof(ASS_FontProvider));
202
23.4k
    if (provider == NULL)
203
0
        return NULL;
204
205
23.4k
    provider->parent   = selector;
206
23.4k
    provider->funcs    = *funcs;
207
23.4k
    provider->priv     = data;
208
209
23.4k
    return provider;
210
23.4k
}
211
212
/**
213
 * Free all data associated with a FontInfo struct. Handles FontInfo structs
214
 * with incomplete allocations well.
215
 *
216
 * \param info FontInfo struct to free associated data from
217
 */
218
static void ass_font_provider_free_fontinfo(ASS_FontInfo *info)
219
258k
{
220
258k
    int j;
221
222
258k
    if (info->fullnames) {
223
516k
        for (j = 0; j < info->n_fullname; j++)
224
258k
            free(info->fullnames[j]);
225
258k
        free(info->fullnames);
226
258k
    }
227
228
258k
    if (info->families) {
229
621k
        for (j = 0; j < info->n_family; j++)
230
363k
            free(info->families[j]);
231
258k
        free(info->families);
232
258k
    }
233
234
258k
    if (info->path)
235
258k
        free(info->path);
236
237
258k
    if (info->postscript_name)
238
258k
        free(info->postscript_name);
239
240
258k
    if (info->extended_family)
241
0
        free(info->extended_family);
242
258k
}
243
244
/**
245
 * \brief Read basic metadata (names, weight, slant) from a FreeType face,
246
 * as required for the FontSelector for matching and sorting.
247
 * \param lib FreeType library
248
 * \param face FreeType face
249
 * \param fallback_family_name family name from outside source, used as last resort
250
 * \param info metadata, returned here
251
 * \return success
252
 */
253
static bool
254
get_font_info(FT_Library lib, FT_Face face, const char *fallback_family_name,
255
              ASS_FontProviderMetaData *info)
256
0
{
257
0
    int i;
258
0
    int num_fullname = 0;
259
0
    int num_family   = 0;
260
0
    int num_names = FT_Get_Sfnt_Name_Count(face);
261
0
    char *fullnames[MAX_FULLNAME];
262
0
    char *families[MAX_FULLNAME];
263
264
    // we're only interested in outlines
265
0
    if (!(face->face_flags & FT_FACE_FLAG_SCALABLE))
266
0
        return false;
267
268
0
    for (i = 0; i < num_names; i++) {
269
0
        FT_SfntName name;
270
271
0
        if (FT_Get_Sfnt_Name(face, i, &name))
272
0
            continue;
273
274
0
        if (name.platform_id == TT_PLATFORM_MICROSOFT &&
275
0
                (name.name_id == TT_NAME_ID_FULL_NAME ||
276
0
                 name.name_id == TT_NAME_ID_FONT_FAMILY)) {
277
0
            char buf[1024];
278
0
            ass_utf16be_to_utf8(buf, sizeof(buf), (uint8_t *)name.string,
279
0
                                name.string_len);
280
281
0
            if (name.name_id == TT_NAME_ID_FULL_NAME && num_fullname < MAX_FULLNAME) {
282
0
                fullnames[num_fullname] = strdup(buf);
283
0
                if (fullnames[num_fullname] == NULL)
284
0
                    goto error;
285
0
                num_fullname++;
286
0
            }
287
288
0
            if (name.name_id == TT_NAME_ID_FONT_FAMILY && num_family < MAX_FULLNAME) {
289
0
                families[num_family] = strdup(buf);
290
0
                if (families[num_family] == NULL)
291
0
                    goto error;
292
0
                num_family++;
293
0
            }
294
0
        }
295
296
0
    }
297
298
    // check if we got a valid family - if not, use
299
    // whatever the font provider or FreeType gives us
300
0
    if (num_family == 0 && (fallback_family_name || face->family_name)) {
301
0
        families[0] =
302
0
            strdup(fallback_family_name ? fallback_family_name : face->family_name);
303
0
        if (families[0] == NULL)
304
0
            goto error;
305
0
        num_family++;
306
0
    }
307
308
    // we absolutely need a name
309
0
    if (num_family == 0)
310
0
        goto error;
311
312
    // calculate sensible weight
313
0
    info->weight = ass_face_get_weight(face);
314
0
    info->style_flags = ass_face_get_style_flags(face);
315
316
0
    info->postscript_name = (char *)FT_Get_Postscript_Name(face);
317
0
    info->is_postscript = ass_face_is_postscript(face);
318
319
0
    if (num_family) {
320
0
        info->families = calloc(num_family, sizeof(char *));
321
0
        if (info->families == NULL)
322
0
            goto error;
323
0
        memcpy(info->families, &families, sizeof(char *) * num_family);
324
0
        info->n_family = num_family;
325
0
    }
326
327
0
    if (num_fullname) {
328
0
        info->fullnames = calloc(num_fullname, sizeof(char *));
329
0
        if (info->fullnames == NULL)
330
0
            goto error;
331
0
        memcpy(info->fullnames, &fullnames, sizeof(char *) * num_fullname);
332
0
        info->n_fullname = num_fullname;
333
0
    }
334
335
0
    return true;
336
337
0
error:
338
0
    for (i = 0; i < num_family; i++)
339
0
        free(families[i]);
340
341
0
    for (i = 0; i < num_fullname; i++)
342
0
        free(fullnames[i]);
343
344
0
    free(info->families);
345
0
    free(info->fullnames);
346
347
0
    info->families = info->fullnames = NULL;
348
0
    info->n_family = info->n_fullname = 0;
349
350
0
    return false;
351
0
}
352
353
/**
354
 * \brief Free the dynamically allocated fields of metadata
355
 * created by get_font_info.
356
 * \param meta metadata created by get_font_info
357
 */
358
static void free_font_info(ASS_FontProviderMetaData *meta)
359
258k
{
360
258k
    if (meta->families) {
361
0
        for (int i = 0; i < meta->n_family; i++)
362
0
            free(meta->families[i]);
363
0
        free(meta->families);
364
0
    }
365
366
258k
    if (meta->fullnames) {
367
0
        for (int i = 0; i < meta->n_fullname; i++)
368
0
            free(meta->fullnames[i]);
369
0
        free(meta->fullnames);
370
0
    }
371
258k
}
372
373
/**
374
 * \brief Add a font to a font provider.
375
 * \param provider the font provider
376
 * \param meta basic metadata of the font
377
 * \param path path to the font file, or NULL
378
 * \param index face index inside the file (-1 to look up by PostScript name)
379
 * \param data private data for the font
380
 * \return success
381
 */
382
bool
383
ass_font_provider_add_font(ASS_FontProvider *provider,
384
                           ASS_FontProviderMetaData *meta, const char *path,
385
                           int index, void *data)
386
258k
{
387
258k
    int i;
388
258k
    ASS_FontSelector *selector = provider->parent;
389
258k
    ASS_FontInfo *info = NULL;
390
258k
    ASS_FontProviderMetaData implicit_meta = {0};
391
392
258k
    if (!meta->n_family) {
393
0
        FT_Face face;
394
0
        if (provider->funcs.get_font_index)
395
0
            index = provider->funcs.get_font_index(data);
396
0
        if (!path) {
397
0
            ASS_FontStream stream = {
398
0
                .func = provider->funcs.get_data,
399
0
                .priv = data,
400
0
            };
401
            // This name is only used in an error message, so use
402
            // our best name but don't panic if we don't have any.
403
            // Prefer PostScript name because it is unique.
404
0
            const char *name = meta->postscript_name ?
405
0
                meta->postscript_name : meta->extended_family;
406
0
            face = ass_face_stream(selector->library, selector->ftlibrary,
407
0
                                   name, &stream, index);
408
0
        } else {
409
0
            face = ass_face_open(selector->library, selector->ftlibrary,
410
0
                                 path, meta->postscript_name, index);
411
0
        }
412
0
        if (!face)
413
0
            goto error;
414
0
        if (!get_font_info(selector->ftlibrary, face, meta->extended_family,
415
0
                           &implicit_meta)) {
416
0
            FT_Done_Face(face);
417
0
            goto error;
418
0
        }
419
0
        if (implicit_meta.postscript_name) {
420
0
            implicit_meta.postscript_name =
421
0
                strdup(implicit_meta.postscript_name);
422
0
            if (!implicit_meta.postscript_name) {
423
0
                FT_Done_Face(face);
424
0
                goto error;
425
0
            }
426
0
        }
427
0
        FT_Done_Face(face);
428
0
        implicit_meta.extended_family = meta->extended_family;
429
0
        meta = &implicit_meta;
430
0
    }
431
432
#if 0
433
    int j;
434
    printf("new font:\n");
435
    printf("  families: ");
436
    for (j = 0; j < meta->n_family; j++)
437
        printf("'%s' ", meta->families[j]);
438
    printf("\n");
439
    printf("  fullnames: ");
440
    for (j = 0; j < meta->n_fullname; j++)
441
        printf("'%s' ", meta->fullnames[j]);
442
    printf("\n");
443
    printf("  style_flags: %lx\n", meta->style_flags);
444
    printf("  weight: %d\n", meta->weight);
445
    printf("  path: %s\n", path);
446
    printf("  index: %d\n", index);
447
#endif
448
449
    // check size
450
258k
    if (selector->n_font >= selector->alloc_font) {
451
70.3k
        selector->alloc_font = FFMAX(1, 2 * selector->alloc_font);
452
70.3k
        selector->font_infos = realloc(selector->font_infos,
453
70.3k
                selector->alloc_font * sizeof(ASS_FontInfo));
454
70.3k
    }
455
456
    // copy over metadata
457
258k
    info = selector->font_infos + selector->n_font;
458
258k
    memset(info, 0, sizeof(ASS_FontInfo));
459
460
    // set uid
461
258k
    info->uid = selector->uid++;
462
463
258k
    info->style_flags   = meta->style_flags;
464
258k
    info->weight        = meta->weight;
465
258k
    info->n_fullname    = meta->n_fullname;
466
258k
    info->n_family      = meta->n_family;
467
258k
    info->is_postscript = meta->is_postscript;
468
469
258k
    info->families = calloc(meta->n_family, sizeof(char *));
470
258k
    if (info->families == NULL)
471
0
        goto error;
472
473
258k
    if (meta->n_fullname) {
474
258k
        info->fullnames = calloc(meta->n_fullname, sizeof(char *));
475
258k
        if (info->fullnames == NULL)
476
0
            goto error;
477
258k
    }
478
479
621k
    for (i = 0; i < info->n_family; i++) {
480
363k
        info->families[i] = strdup(meta->families[i]);
481
363k
        if (info->families[i] == NULL)
482
0
            goto error;
483
363k
    }
484
485
516k
    for (i = 0; i < info->n_fullname; i++) {
486
258k
        info->fullnames[i] = strdup(meta->fullnames[i]);
487
258k
        if (info->fullnames[i] == NULL)
488
0
            goto error;
489
258k
    }
490
491
258k
    if (meta->postscript_name) {
492
258k
        info->postscript_name = strdup(meta->postscript_name);
493
258k
        if (info->postscript_name == NULL)
494
0
            goto error;
495
258k
    }
496
497
258k
    if (meta->extended_family) {
498
0
        info->extended_family = strdup(meta->extended_family);
499
0
        if (info->extended_family == NULL)
500
0
            goto error;
501
0
    }
502
503
258k
    if (path) {
504
258k
        info->path = strdup(path);
505
258k
        if (info->path == NULL)
506
0
            goto error;
507
258k
    }
508
509
258k
    info->index = index;
510
258k
    info->priv  = data;
511
258k
    info->provider = provider;
512
513
258k
    selector->n_font++;
514
515
258k
    free_font_info(&implicit_meta);
516
258k
    free(implicit_meta.postscript_name);
517
518
258k
    return true;
519
520
0
error:
521
0
    if (info)
522
0
        ass_font_provider_free_fontinfo(info);
523
524
0
    free_font_info(&implicit_meta);
525
0
    free(implicit_meta.postscript_name);
526
0
    provider->funcs.destroy_font(data);
527
528
0
    return false;
529
258k
}
530
531
/**
532
 * \brief Clean up font database. Deletes all fonts that have an invalid
533
 * font provider (NULL).
534
 * \param selector the font selector
535
 */
536
static void ass_fontselect_cleanup(ASS_FontSelector *selector)
537
23.4k
{
538
23.4k
    int i, w;
539
540
281k
    for (i = 0, w = 0; i < selector->n_font; i++) {
541
258k
        ASS_FontInfo *info = selector->font_infos + i;
542
543
        // update write pointer
544
258k
        if (info->provider != NULL) {
545
            // rewrite, if needed
546
0
            if (w != i)
547
0
                memcpy(selector->font_infos + w, selector->font_infos + i,
548
0
                        sizeof(ASS_FontInfo));
549
0
            w++;
550
0
        }
551
552
258k
    }
553
554
23.4k
    selector->n_font = w;
555
23.4k
}
556
557
void ass_font_provider_free(ASS_FontProvider *provider)
558
23.4k
{
559
23.4k
    int i;
560
23.4k
    ASS_FontSelector *selector = provider->parent;
561
562
    // free all fonts and mark their entries
563
281k
    for (i = 0; i < selector->n_font; i++) {
564
258k
        ASS_FontInfo *info = selector->font_infos + i;
565
566
258k
        if (info->provider == provider) {
567
258k
            ass_font_provider_free_fontinfo(info);
568
258k
            info->provider->funcs.destroy_font(info->priv);
569
258k
            info->provider = NULL;
570
258k
        }
571
572
258k
    }
573
574
    // delete marked entries
575
23.4k
    ass_fontselect_cleanup(selector);
576
577
    // free private data of the provider
578
23.4k
    if (provider->funcs.destroy_provider)
579
11.7k
        provider->funcs.destroy_provider(provider->priv);
580
581
23.4k
    free(provider);
582
23.4k
}
583
584
static bool check_postscript(ASS_FontInfo *fi)
585
0
{
586
0
    ASS_FontProvider *provider = fi->provider;
587
0
    assert(provider);
588
589
0
    if (provider->funcs.check_postscript)
590
0
        return provider->funcs.check_postscript(fi->priv);
591
0
    else
592
0
        return fi->is_postscript;
593
0
}
594
595
/**
596
 * \brief Return whether the given font is in the given family.
597
 */
598
static bool matches_family_name(ASS_FontInfo *f, const char *family,
599
                                bool match_extended_family)
600
1.43M
{
601
3.43M
    for (int i = 0; i < f->n_family; i++) {
602
2.01M
        if (ass_strcasecmp(f->families[i], family) == 0)
603
10.2k
            return true;
604
2.01M
    }
605
1.42M
    if (match_extended_family && f->extended_family) {
606
0
        if (ass_strcasecmp(f->extended_family, family) == 0)
607
0
            return true;
608
0
    }
609
1.42M
    return false;
610
1.42M
}
611
612
/**
613
 * \brief Return whether the given font has the given fullname or
614
 * PostScript name depending on whether it has PostScript outlines.
615
 */
616
static bool matches_full_or_postscript_name(ASS_FontInfo *f,
617
                                            const char *fullname)
618
1.42M
{
619
1.42M
    bool matches_fullname = false;
620
1.42M
    bool matches_postscript_name = false;
621
622
2.84M
    for (int i = 0; i < f->n_fullname; i++) {
623
1.42M
        if (ass_strcasecmp(f->fullnames[i], fullname) == 0) {
624
0
            matches_fullname = true;
625
0
            break;
626
0
        }
627
1.42M
    }
628
629
1.42M
    if (f->postscript_name != NULL &&
630
1.42M
        ass_strcasecmp(f->postscript_name, fullname) == 0)
631
0
        matches_postscript_name = true;
632
633
1.42M
    if (matches_fullname == matches_postscript_name)
634
1.42M
        return matches_fullname;
635
636
0
    if (check_postscript(f))
637
0
        return matches_postscript_name;
638
0
    else
639
0
        return matches_fullname;
640
0
}
641
642
/**
643
 * \brief Compare attributes of font (a) against a font request (req). Returns
644
 * a matching score - the lower the better.
645
 * Ignores font names/families!
646
 * \param a font
647
 * \param b font request
648
 * \return matching score
649
 */
650
static unsigned font_attributes_similarity(ASS_FontInfo *a, ASS_FontInfo *req)
651
10.2k
{
652
10.2k
    unsigned score = 0;
653
654
    // Assign score for italics mismatch
655
10.2k
    if ((req->style_flags & FT_STYLE_FLAG_ITALIC) &&
656
1.07k
        !(a->style_flags & FT_STYLE_FLAG_ITALIC))
657
1.07k
        score += 1;
658
9.17k
    else if (!(req->style_flags & FT_STYLE_FLAG_ITALIC) &&
659
9.17k
             (a->style_flags & FT_STYLE_FLAG_ITALIC))
660
16
        score += 4;
661
662
10.2k
    int a_weight = a->weight;
663
664
    // Offset effective weight for faux-bold (only if font isn't flagged as bold)
665
10.2k
    if ((req->weight > a->weight + 150) && !(a->style_flags & FT_STYLE_FLAG_BOLD))
666
1.28k
        a_weight += 120;
667
668
    // Assign score for weight mismatch
669
10.2k
    score += (73 * ABS(a_weight - req->weight)) / 256;
670
671
10.2k
    return score;
672
10.2k
}
673
674
#if 0
675
// dump font information
676
static void font_info_dump(ASS_FontInfo *font_infos, size_t len)
677
{
678
    int i, j;
679
680
    // dump font infos
681
    for (i = 0; i < len; i++) {
682
        printf("font %d\n", i);
683
        printf("  families: ");
684
        for (j = 0; j < font_infos[i].n_family; j++)
685
            printf("'%s' ", font_infos[i].families[j]);
686
        printf("  fullnames: ");
687
        for (j = 0; j < font_infos[i].n_fullname; j++)
688
            printf("'%s' ", font_infos[i].fullnames[j]);
689
        printf("\n");
690
        printf("  slant: %d\n", font_infos[i].slant);
691
        printf("  weight: %d\n", font_infos[i].weight);
692
        printf("  path: %s\n", font_infos[i].path);
693
        printf("  index: %d\n", font_infos[i].index);
694
        printf("  score: %d\n", font_infos[i].score);
695
696
    }
697
}
698
#endif
699
700
static bool check_glyph(ASS_FontInfo *fi, uint32_t code)
701
10.2k
{
702
10.2k
    ASS_FontProvider *provider = fi->provider;
703
10.2k
    assert(provider && provider->funcs.check_glyph);
704
705
10.2k
    return provider->funcs.check_glyph(fi->priv, code);
706
10.2k
}
707
708
static char *
709
find_font(ASS_FontSelector *priv,
710
          ASS_FontProviderMetaData meta, bool match_extended_family,
711
          unsigned bold, unsigned italic,
712
          int *index, char **postscript_name, int *uid, ASS_FontStream *stream,
713
          uint32_t code, bool *name_match)
714
65.3k
{
715
65.3k
    ASS_FontInfo req = {0};
716
65.3k
    ASS_FontInfo *selected = NULL;
717
718
    // do we actually have any fonts?
719
65.3k
    if (!priv->n_font)
720
0
        return NULL;
721
722
    // fill font request
723
65.3k
    req.style_flags = (italic ? FT_STYLE_FLAG_ITALIC : 0);
724
65.3k
    req.weight      = bold;
725
726
    // Match font family name against font list
727
65.3k
    unsigned score_min = UINT_MAX;
728
120k
    for (int i = 0; i < meta.n_fullname; i++) {
729
65.3k
        const char *fullname = meta.fullnames[i];
730
731
1.49M
        for (int x = 0; x < priv->n_font; x++) {
732
1.43M
            ASS_FontInfo *font = &priv->font_infos[x];
733
1.43M
            unsigned score = UINT_MAX;
734
735
1.43M
            if (matches_family_name(font, fullname, match_extended_family)) {
736
                // If there's a family match, compare font attributes
737
                // to determine best match in that particular family
738
10.2k
                score = font_attributes_similarity(font, &req);
739
10.2k
                *name_match = true;
740
1.42M
            } else if (matches_full_or_postscript_name(font, fullname)) {
741
                // If we don't have any match, compare fullnames against request
742
                // if there is a match now, assign lowest score possible. This means
743
                // the font should be chosen instantly, without further search.
744
0
                score = 0;
745
0
                *name_match = true;
746
0
            }
747
748
            // Consider updating idx if score is better than current minimum
749
1.43M
            if (score < score_min) {
750
                // Check if the font has the requested glyph.
751
                // We are doing this here, for every font face, because
752
                // coverage might differ between the variants of a font
753
                // family. In practice, it is common that the regular
754
                // style has the best coverage while bold/italic/etc
755
                // variants cover less (e.g. FreeSans family).
756
                // We want to be able to match even if the closest variant
757
                // does not have the requested glyph, but another member
758
                // of the family has the glyph.
759
10.2k
                if (!check_glyph(font, code))
760
3
                    continue;
761
762
10.2k
                score_min = score;
763
10.2k
                selected = font;
764
10.2k
            }
765
766
            // Lowest possible score instantly matches; this is typical
767
            // for fullname matches, but can also occur with family matches.
768
1.43M
            if (score == 0)
769
326
                break;
770
1.43M
        }
771
772
        // The list of names is sorted by priority. If we matched anything,
773
        // we can and should stop.
774
65.3k
        if (selected != NULL)
775
10.1k
            break;
776
65.3k
    }
777
778
    // found anything?
779
65.3k
    char *result = NULL;
780
65.3k
    if (selected) {
781
10.1k
        ASS_FontProvider *provider = selected->provider;
782
783
        // successfully matched, set up return values
784
10.1k
        *postscript_name = selected->postscript_name;
785
10.1k
        *uid   = selected->uid;
786
787
        // use lazy evaluation for index if applicable
788
10.1k
        if (provider->funcs.get_font_index) {
789
0
            *index = provider->funcs.get_font_index(selected->priv);
790
0
        } else
791
10.1k
            *index = selected->index;
792
793
        // set up memory stream if there is no path
794
10.1k
        if (selected->path == NULL) {
795
0
            stream->func = provider->funcs.get_data;
796
0
            stream->priv = selected->priv;
797
            // Prefer PostScript name because it is unique. This is only
798
            // used for display purposes so it doesn't matter that much,
799
            // though.
800
0
            if (selected->postscript_name)
801
0
                result = selected->postscript_name;
802
0
            else
803
0
                result = selected->families[0];
804
0
        } else
805
10.1k
            result = selected->path;
806
807
10.1k
    }
808
809
65.3k
    return result;
810
65.3k
}
811
812
static char *select_font(ASS_FontSelector *priv,
813
                         const char *family, bool match_extended_family,
814
                         unsigned bold, unsigned italic,
815
                         int *index, char **postscript_name, int *uid,
816
                         ASS_FontStream *stream, uint32_t code)
817
65.3k
{
818
65.3k
    ASS_FontProvider *default_provider = priv->default_provider;
819
65.3k
    ASS_FontProviderMetaData meta = {0};
820
65.3k
    char *result = NULL;
821
65.3k
    bool name_match = false;
822
823
65.3k
    if (family == NULL)
824
0
        return NULL;
825
826
65.3k
    ASS_FontProviderMetaData default_meta = {
827
65.3k
        .n_fullname = 1,
828
65.3k
        .fullnames  = (char **)&family,
829
65.3k
    };
830
831
    // Get a list of substitutes if applicable, and use it for matching.
832
65.3k
    if (default_provider && default_provider->funcs.get_substitutions) {
833
65.3k
        default_provider->funcs.get_substitutions(default_provider->priv,
834
65.3k
                                                  family, &meta);
835
65.3k
    }
836
837
65.3k
    if (!meta.n_fullname) {
838
74
        free(meta.fullnames);
839
74
        meta = default_meta;
840
74
    }
841
842
65.3k
    result = find_font(priv, meta, match_extended_family,
843
65.3k
                       bold, italic, index, postscript_name, uid,
844
65.3k
                       stream, code, &name_match);
845
846
    // If no matching font was found, it might not exist in the font list
847
    // yet. Call the match_fonts callback to fill in the missing fonts
848
    // on demand, and retry the search for a match.
849
65.3k
    if (result == NULL && name_match == false && default_provider &&
850
55.1k
            default_provider->funcs.match_fonts) {
851
        // TODO: consider changing the API to make more efficient
852
        // implementations possible.
853
0
        for (int i = 0; i < meta.n_fullname; i++) {
854
0
            default_provider->funcs.match_fonts(default_provider->priv,
855
0
                                                priv->library, default_provider,
856
0
                                                meta.fullnames[i]);
857
0
        }
858
0
        result = find_font(priv, meta, match_extended_family,
859
0
                           bold, italic, index, postscript_name, uid,
860
0
                           stream, code, &name_match);
861
0
    }
862
863
    // cleanup
864
65.3k
    if (meta.fullnames != default_meta.fullnames) {
865
130k
        for (int i = 0; i < meta.n_fullname; i++)
866
65.2k
            free(meta.fullnames[i]);
867
65.2k
        free(meta.fullnames);
868
65.2k
    }
869
870
65.3k
    return result;
871
65.3k
}
872
873
874
/**
875
 * \brief Find a font. Use default family or path if necessary.
876
 * \param family font family
877
 * \param treat_family_as_pattern treat family as fontconfig pattern
878
 * \param bold font weight value
879
 * \param italic font slant value
880
 * \param index out: font index inside a file
881
 * \param code: the character that should be present in the font, can be 0
882
 * \return font file path
883
*/
884
char *ass_font_select(ASS_FontSelector *priv,
885
                      const ASS_Font *font, int *index, char **postscript_name,
886
                      int *uid, ASS_FontStream *data, uint32_t code)
887
27.6k
{
888
27.6k
    char *res = 0;
889
27.6k
    const char *family = font->desc.family.str;  // always zero-terminated
890
27.6k
    unsigned bold = font->desc.bold;
891
27.6k
    unsigned italic = font->desc.italic;
892
27.6k
    ASS_FontProvider *default_provider = priv->default_provider;
893
894
27.6k
    if (family && *family)
895
27.5k
        res = select_font(priv, family, false, bold, italic, index,
896
27.5k
                postscript_name, uid, data, code);
897
898
27.6k
    if (!res && priv->family_default) {
899
27.6k
        res = select_font(priv, priv->family_default, false, bold,
900
27.6k
                italic, index, postscript_name, uid, data, code);
901
27.6k
        if (res)
902
0
            ass_msg(priv->library, MSGL_WARN, "fontselect: Using default "
903
0
                    "font family: (%s, %d, %d) -> %s, %d, %s",
904
0
                    family, bold, italic, res, *index,
905
0
                    *postscript_name ? *postscript_name : "(none)");
906
27.6k
    }
907
908
27.6k
    if (!res && default_provider && default_provider->funcs.get_fallback) {
909
27.6k
        const char *search_family = family;
910
27.6k
        if (!search_family || !*search_family)
911
103
            search_family = "Arial";
912
27.6k
        char *fallback_family = default_provider->funcs.get_fallback(
913
27.6k
                default_provider->priv, priv->library, search_family, code);
914
915
27.6k
        if (fallback_family) {
916
10.1k
            res = select_font(priv, fallback_family, true, bold, italic,
917
10.1k
                    index, postscript_name, uid, data, code);
918
10.1k
            free(fallback_family);
919
10.1k
        }
920
27.6k
    }
921
922
27.6k
    if (!res && priv->path_default) {
923
0
        res = priv->path_default;
924
0
        *index = priv->index_default;
925
0
        ass_msg(priv->library, MSGL_WARN, "fontselect: Using default font: "
926
0
                "(%s, %d, %d) -> %s, %d, %s", family, bold, italic,
927
0
                priv->path_default, *index,
928
0
                *postscript_name ? *postscript_name : "(none)");
929
0
    }
930
931
27.6k
    if (res)
932
10.1k
        ass_msg(priv->library, MSGL_INFO,
933
10.1k
                "fontselect: (%s, %d, %d) -> %s, %d, %s", family, bold,
934
10.1k
                italic, res, *index, *postscript_name ? *postscript_name : "(none)");
935
17.4k
    else
936
17.4k
        ass_msg(priv->library, MSGL_WARN,
937
17.4k
                "fontselect: failed to find any fallback with glyph 0x%X for font: "
938
17.4k
                "(%s, %d, %d)", code, family, bold, italic);
939
940
27.6k
    return res;
941
27.6k
}
942
943
944
/**
945
 * \brief Process memory font.
946
 * \param priv private data
947
 * \param idx index of the processed font in priv->library->fontdata
948
 *
949
 * Builds a FontInfo with FreeType and some table reading.
950
*/
951
static void process_fontdata(ASS_FontProvider *priv, int idx)
952
0
{
953
0
    ASS_FontSelector *selector = priv->parent;
954
0
    ASS_Library *library = selector->library;
955
956
0
    int rc;
957
0
    const char *name = library->fontdata[idx].name;
958
0
    const char *data = library->fontdata[idx].data;
959
0
    int data_size = library->fontdata[idx].size;
960
961
0
    FT_Face face;
962
0
    int face_index, num_faces = 1;
963
964
0
    for (face_index = 0; face_index < num_faces; ++face_index) {
965
0
        ASS_FontProviderMetaData info;
966
0
        FontDataFT *ft;
967
968
0
        rc = FT_New_Memory_Face(selector->ftlibrary, (unsigned char *) data,
969
0
                                data_size, face_index, &face);
970
0
        if (rc) {
971
0
            ass_msg(library, MSGL_WARN, "Error opening memory font '%s'",
972
0
                   name);
973
0
            continue;
974
0
        }
975
976
0
        num_faces = face->num_faces;
977
978
0
        ass_charmap_magic(library, face);
979
980
0
        memset(&info, 0, sizeof(ASS_FontProviderMetaData));
981
0
        if (!get_font_info(selector->ftlibrary, face, NULL, &info)) {
982
0
            ass_msg(library, MSGL_WARN,
983
0
                    "Error getting metadata for embedded font '%s'", name);
984
0
            FT_Done_Face(face);
985
0
            continue;
986
0
        }
987
988
0
        ft = calloc(1, sizeof(FontDataFT));
989
990
0
        if (ft == NULL) {
991
0
            free_font_info(&info);
992
0
            FT_Done_Face(face);
993
0
            continue;
994
0
        }
995
996
0
        ft->lib  = library;
997
0
        ft->face = face;
998
0
        ft->idx  = idx;
999
1000
0
        if (!ass_font_provider_add_font(priv, &info, NULL, face_index, ft)) {
1001
0
            ass_msg(library, MSGL_WARN, "Failed to add embedded font '%s'",
1002
0
                    name);
1003
0
            free(ft);
1004
0
        }
1005
1006
0
        free_font_info(&info);
1007
0
    }
1008
0
}
1009
1010
/**
1011
 * \brief Create font provider for embedded fonts. This parses the fonts known
1012
 * to the current ASS_Library and adds them to the selector.
1013
 * \param selector font selector
1014
 * \return font provider
1015
 */
1016
static ASS_FontProvider *
1017
ass_embedded_fonts_add_provider(ASS_FontSelector *selector, size_t *num_emfonts)
1018
11.7k
{
1019
11.7k
    ASS_FontProvider *priv = ass_font_provider_new(selector, &ft_funcs, NULL);
1020
11.7k
    if (priv == NULL)
1021
0
        return NULL;
1022
1023
11.7k
    ASS_Library *lib = selector->library;
1024
1025
11.7k
    if (lib->fonts_dir && lib->fonts_dir[0]) {
1026
0
        load_fonts_from_dir(lib, lib->fonts_dir);
1027
0
    }
1028
1029
11.7k
    for (size_t i = 0; i < lib->num_fontdata; i++)
1030
0
        process_fontdata(priv, i);
1031
11.7k
    *num_emfonts = lib->num_fontdata;
1032
1033
11.7k
    return priv;
1034
11.7k
}
1035
1036
struct font_constructors {
1037
    ASS_DefaultFontProvider id;
1038
    ASS_FontProvider *(*constructor)(ASS_Library *, ASS_FontSelector *,
1039
                                     const char *, FT_Library);
1040
    const char *name;
1041
};
1042
1043
struct font_constructors font_constructors[] = {
1044
#ifdef CONFIG_CORETEXT
1045
    { ASS_FONTPROVIDER_CORETEXT,        &ass_coretext_add_provider,     "coretext"},
1046
#endif
1047
#ifdef CONFIG_DIRECTWRITE
1048
    { ASS_FONTPROVIDER_DIRECTWRITE,     &ass_directwrite_add_provider,  "directwrite"
1049
#if ASS_WINAPI_DESKTOP
1050
        " (with GDI)"
1051
#else
1052
        " (without GDI)"
1053
#endif
1054
    },
1055
#endif
1056
#ifdef CONFIG_FONTCONFIG
1057
    { ASS_FONTPROVIDER_FONTCONFIG,      &ass_fontconfig_add_provider,   "fontconfig"},
1058
#endif
1059
    { ASS_FONTPROVIDER_NONE, NULL, NULL },
1060
};
1061
1062
/**
1063
 * \brief Init font selector.
1064
 * \param library libass library object
1065
 * \param ftlibrary freetype library object
1066
 * \param family default font family
1067
 * \param path default font path
1068
 * \return newly created font selector
1069
 */
1070
ASS_FontSelector *
1071
ass_fontselect_init(ASS_Library *library, FT_Library ftlibrary, size_t *num_emfonts,
1072
                    const char *family, const char *path, const char *config,
1073
                    ASS_DefaultFontProvider dfp)
1074
11.7k
{
1075
11.7k
    ASS_FontSelector *priv = calloc(1, sizeof(ASS_FontSelector));
1076
11.7k
    if (priv == NULL)
1077
0
        return NULL;
1078
1079
11.7k
    priv->library = library;
1080
11.7k
    priv->ftlibrary = ftlibrary;
1081
11.7k
    priv->uid = 1;
1082
11.7k
    priv->family_default = family ? strdup(family) : NULL;
1083
11.7k
    priv->path_default = path ? strdup(path) : NULL;
1084
11.7k
    priv->index_default = 0;
1085
1086
11.7k
    if (family && !priv->family_default)
1087
0
        goto fail;
1088
11.7k
    if (path && !priv->path_default)
1089
0
        goto fail;
1090
1091
11.7k
    priv->embedded_provider = ass_embedded_fonts_add_provider(priv, num_emfonts);
1092
1093
11.7k
    if (priv->embedded_provider == NULL) {
1094
0
        ass_msg(library, MSGL_WARN, "failed to create embedded font provider");
1095
0
        goto fail;
1096
0
    }
1097
1098
11.7k
    if (dfp >= ASS_FONTPROVIDER_AUTODETECT) {
1099
11.7k
        for (int i = 0; font_constructors[i].constructor; i++ )
1100
11.7k
            if (dfp == font_constructors[i].id ||
1101
11.7k
                dfp == ASS_FONTPROVIDER_AUTODETECT) {
1102
11.7k
                priv->default_provider =
1103
11.7k
                    font_constructors[i].constructor(library, priv,
1104
11.7k
                                                     config, ftlibrary);
1105
11.7k
                if (priv->default_provider) {
1106
11.7k
                    ass_msg(library, MSGL_INFO, "Using font provider %s",
1107
11.7k
                            font_constructors[i].name);
1108
11.7k
                    break;
1109
11.7k
                }
1110
11.7k
            }
1111
1112
11.7k
        if (!priv->default_provider)
1113
0
            ass_msg(library, MSGL_WARN, "can't find selected font provider");
1114
1115
11.7k
    }
1116
1117
11.7k
    return priv;
1118
1119
0
fail:
1120
0
    if (priv->default_provider)
1121
0
        ass_font_provider_free(priv->default_provider);
1122
0
    if (priv->embedded_provider)
1123
0
        ass_font_provider_free(priv->embedded_provider);
1124
1125
0
    free(priv->family_default);
1126
0
    free(priv->path_default);
1127
1128
0
    free(priv);
1129
1130
0
    return NULL;
1131
11.7k
}
1132
1133
void ass_get_available_font_providers(ASS_Library *priv,
1134
                                      ASS_DefaultFontProvider **providers,
1135
                                      size_t *size)
1136
0
{
1137
0
    size_t offset = 2;
1138
1139
0
    *size = offset;
1140
0
    for (int i = 0; font_constructors[i].constructor; i++)
1141
0
        (*size)++;
1142
1143
0
    *providers = calloc(*size, sizeof(ASS_DefaultFontProvider));
1144
1145
0
    if (*providers == NULL) {
1146
0
        *size = (size_t)-1;
1147
0
        return;
1148
0
    }
1149
1150
0
    (*providers)[0] = ASS_FONTPROVIDER_NONE;
1151
0
    (*providers)[1] = ASS_FONTPROVIDER_AUTODETECT;
1152
1153
0
    for (int i = offset; i < *size; i++)
1154
0
        (*providers)[i] = font_constructors[i-offset].id;
1155
0
}
1156
1157
/**
1158
 * \brief Free font selector and release associated data
1159
 * \param the font selector
1160
 */
1161
void ass_fontselect_free(ASS_FontSelector *priv)
1162
11.7k
{
1163
11.7k
    if (priv->default_provider)
1164
11.7k
        ass_font_provider_free(priv->default_provider);
1165
11.7k
    if (priv->embedded_provider)
1166
11.7k
        ass_font_provider_free(priv->embedded_provider);
1167
1168
11.7k
    free(priv->font_infos);
1169
11.7k
    free(priv->path_default);
1170
11.7k
    free(priv->family_default);
1171
1172
11.7k
    free(priv);
1173
11.7k
}
1174
1175
void ass_map_font(const ASS_FontMapping *map, int len, const char *name,
1176
                  ASS_FontProviderMetaData *meta)
1177
0
{
1178
0
    for (int i = 0; i < len; i++) {
1179
0
        if (ass_strcasecmp(map[i].from, name) == 0) {
1180
0
            meta->fullnames = calloc(1, sizeof(char *));
1181
0
            if (meta->fullnames) {
1182
0
                meta->fullnames[0] = strdup(map[i].to);
1183
0
                if (meta->fullnames[0])
1184
0
                    meta->n_fullname = 1;
1185
0
            }
1186
0
            return;
1187
0
        }
1188
0
    }
1189
0
}
1190
1191
size_t ass_update_embedded_fonts(ASS_FontSelector *selector, size_t num_loaded)
1192
0
{
1193
0
    if (!selector->embedded_provider)
1194
0
        return num_loaded;
1195
1196
0
    size_t num_fontdata = selector->library->num_fontdata;
1197
0
    for (size_t i = num_loaded; i < num_fontdata; i++)
1198
0
        process_fontdata(selector->embedded_provider, i);
1199
0
    return num_fontdata;
1200
0
}