/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 | } |