Cosmetics: remove underscore prefix from _select_font function
[libass.git] / libass / ass_fontconfig.c
blob0150b2e955c6a3a4ef85e44b52b656d5db8117d9
1 /*
2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
4 * This file is part of libass.
6 * libass is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * libass 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
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with libass; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "config.h"
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <assert.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <inttypes.h>
30 #include <ft2build.h>
31 #include FT_FREETYPE_H
33 #include "ass_utils.h"
34 #include "ass.h"
35 #include "ass_library.h"
36 #include "ass_fontconfig.h"
38 #ifdef CONFIG_FONTCONFIG
39 #include <fontconfig/fontconfig.h>
40 #include <fontconfig/fcfreetype.h>
41 #endif
43 struct fc_instance {
44 #ifdef CONFIG_FONTCONFIG
45 FcConfig *config;
46 #endif
47 char *family_default;
48 char *path_default;
49 int index_default;
52 #ifdef CONFIG_FONTCONFIG
54 /**
55 * \brief Case-insensitive match ASS/SSA font family against full name. (also
56 * known as "name for humans")
58 * \param lib library instance
59 * \param priv fontconfig instance
60 * \param family font family
61 * \return font set
63 static FcFontSet *match_fullname(ASS_Library *lib, FCInstance *priv,
64 const char *family)
66 FcFontSet *sets[2];
67 FcFontSet *result = FcFontSetCreate();
68 int nsets = 0;
69 int i, fi;
71 if ((sets[nsets] = FcConfigGetFonts(priv->config, FcSetSystem)))
72 nsets++;
73 if ((sets[nsets] = FcConfigGetFonts(priv->config, FcSetApplication)))
74 nsets++;
76 // Run over font sets and patterns and try to match against full name
77 for (i = 0; i < nsets; i++) {
78 FcFontSet *set = sets[i];
79 for (fi = 0; fi < set->nfont; fi++) {
80 FcPattern *pat = set->fonts[fi];
81 char *fullname;
82 int pi = 0;
83 while (FcPatternGetString(pat, FC_FULLNAME, pi++,
84 (FcChar8 **) &fullname) == FcResultMatch)
85 if (strcasecmp(fullname, family) == 0) {
86 FcFontSetAdd(result, FcPatternDuplicate(pat));
87 break;
92 return result;
95 /**
96 * \brief Low-level font selection.
97 * \param priv private data
98 * \param family font family
99 * \param treat_family_as_pattern treat family as fontconfig pattern
100 * \param bold font weight value
101 * \param italic font slant value
102 * \param index out: font index inside a file
103 * \param code: the character that should be present in the font, can be 0
104 * \return font file path
106 static char *select_font(ASS_Library *library, FCInstance *priv,
107 const char *family, int treat_family_as_pattern,
108 unsigned bold, unsigned italic, int *index,
109 uint32_t code)
111 FcBool rc;
112 FcResult result;
113 FcPattern *pat = NULL, *rpat = NULL;
114 int r_index, r_slant, r_weight;
115 FcChar8 *r_family, *r_style, *r_file, *r_fullname;
116 FcBool r_outline, r_embolden;
117 FcCharSet *r_charset;
118 FcFontSet *ffullname = NULL, *fsorted = NULL, *fset = NULL;
119 int curf;
120 char *retval = NULL;
121 int family_cnt = 0;
123 *index = 0;
125 if (treat_family_as_pattern)
126 pat = FcNameParse((const FcChar8 *) family);
127 else
128 pat = FcPatternCreate();
130 if (!pat)
131 goto error;
133 if (!treat_family_as_pattern) {
134 FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) family);
136 // In SSA/ASS fonts are sometimes referenced by their "full name",
137 // which is usually a concatenation of family name and font
138 // style (ex. Ottawa Bold). Full name is available from
139 // FontConfig pattern element FC_FULLNAME, but it is never
140 // used for font matching.
141 // Therefore, I'm removing words from the end of the name one
142 // by one, and adding shortened names to the pattern. It seems
143 // that the first value (full name in this case) has
144 // precedence in matching.
145 // An alternative approach could be to reimplement FcFontSort
146 // using FC_FULLNAME instead of FC_FAMILY.
147 family_cnt = 1;
149 char *s = strdup(family);
150 char *p = s + strlen(s);
151 while (--p > s)
152 if (*p == ' ' || *p == '-') {
153 *p = '\0';
154 FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) s);
155 ++family_cnt;
157 free(s);
160 FcPatternAddBool(pat, FC_OUTLINE, FcTrue);
161 FcPatternAddInteger(pat, FC_SLANT, italic);
162 FcPatternAddInteger(pat, FC_WEIGHT, bold);
164 FcDefaultSubstitute(pat);
166 rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern);
167 if (!rc)
168 goto error;
170 fsorted = FcFontSort(priv->config, pat, FcTrue, NULL, &result);
171 ffullname = match_fullname(library, priv, family);
172 if (!fsorted || !ffullname)
173 goto error;
175 fset = FcFontSetCreate();
176 for (curf = 0; curf < ffullname->nfont; ++curf) {
177 FcPattern *curp = ffullname->fonts[curf];
178 FcPatternReference(curp);
179 FcFontSetAdd(fset, curp);
181 for (curf = 0; curf < fsorted->nfont; ++curf) {
182 FcPattern *curp = fsorted->fonts[curf];
183 FcPatternReference(curp);
184 FcFontSetAdd(fset, curp);
187 for (curf = 0; curf < fset->nfont; ++curf) {
188 FcPattern *curp = fset->fonts[curf];
190 result = FcPatternGetBool(curp, FC_OUTLINE, 0, &r_outline);
191 if (result != FcResultMatch)
192 continue;
193 if (r_outline != FcTrue)
194 continue;
195 if (!code)
196 break;
197 result = FcPatternGetCharSet(curp, FC_CHARSET, 0, &r_charset);
198 if (result != FcResultMatch)
199 continue;
200 if (FcCharSetHasChar(r_charset, code))
201 break;
204 if (curf >= fset->nfont)
205 goto error;
207 if (!treat_family_as_pattern) {
208 // Remove all extra family names from original pattern.
209 // After this, FcFontRenderPrepare will select the most relevant family
210 // name in case there are more than one of them.
211 for (; family_cnt > 1; --family_cnt)
212 FcPatternRemove(pat, FC_FAMILY, family_cnt - 1);
215 rpat = FcFontRenderPrepare(priv->config, pat, fset->fonts[curf]);
216 if (!rpat)
217 goto error;
219 result = FcPatternGetInteger(rpat, FC_INDEX, 0, &r_index);
220 if (result != FcResultMatch)
221 goto error;
222 *index = r_index;
224 result = FcPatternGetString(rpat, FC_FILE, 0, &r_file);
225 if (result != FcResultMatch)
226 goto error;
227 retval = strdup((const char *) r_file);
229 result = FcPatternGetString(rpat, FC_FAMILY, 0, &r_family);
230 if (result != FcResultMatch)
231 r_family = NULL;
233 result = FcPatternGetString(rpat, FC_FULLNAME, 0, &r_fullname);
234 if (result != FcResultMatch)
235 r_fullname = NULL;
237 if (!treat_family_as_pattern &&
238 !(r_family && strcasecmp((const char *) r_family, family) == 0) &&
239 !(r_fullname && strcasecmp((const char *) r_fullname, family) == 0))
240 ass_msg(library, MSGL_WARN,
241 "fontconfig: Selected font is not the requested one: "
242 "'%s' != '%s'",
243 (const char *) (r_fullname ? r_fullname : r_family), family);
245 result = FcPatternGetString(rpat, FC_STYLE, 0, &r_style);
246 if (result != FcResultMatch)
247 r_style = NULL;
249 result = FcPatternGetInteger(rpat, FC_SLANT, 0, &r_slant);
250 if (result != FcResultMatch)
251 r_slant = 0;
253 result = FcPatternGetInteger(rpat, FC_WEIGHT, 0, &r_weight);
254 if (result != FcResultMatch)
255 r_weight = 0;
257 result = FcPatternGetBool(rpat, FC_EMBOLDEN, 0, &r_embolden);
258 if (result != FcResultMatch)
259 r_embolden = 0;
261 ass_msg(library, MSGL_V,
262 "Font info: family '%s', style '%s', fullname '%s',"
263 " slant %d, weight %d%s", (const char *) r_family,
264 (const char *) r_style, (const char *) r_fullname, r_slant,
265 r_weight, r_embolden ? ", embolden" : "");
267 error:
268 if (pat)
269 FcPatternDestroy(pat);
270 if (rpat)
271 FcPatternDestroy(rpat);
272 if (fsorted)
273 FcFontSetDestroy(fsorted);
274 if (ffullname)
275 FcFontSetDestroy(ffullname);
276 if (fset)
277 FcFontSetDestroy(fset);
278 return retval;
282 * \brief Find a font. Use default family or path if necessary.
283 * \param priv_ private data
284 * \param family font family
285 * \param treat_family_as_pattern treat family as fontconfig pattern
286 * \param bold font weight value
287 * \param italic font slant value
288 * \param index out: font index inside a file
289 * \param code: the character that should be present in the font, can be 0
290 * \return font file path
292 char *fontconfig_select(ASS_Library *library, FCInstance *priv,
293 const char *family, int treat_family_as_pattern,
294 unsigned bold, unsigned italic, int *index,
295 uint32_t code)
297 char *res = 0;
298 if (!priv->config) {
299 *index = priv->index_default;
300 res = priv->path_default ? strdup(priv->path_default) : 0;
301 return res;
303 if (family && *family)
304 res =
305 select_font(library, priv, family, treat_family_as_pattern,
306 bold, italic, index, code);
307 if (!res && priv->family_default) {
308 res =
309 select_font(library, priv, priv->family_default, 0, bold,
310 italic, index, code);
311 if (res)
312 ass_msg(library, MSGL_WARN, "fontconfig_select: Using default "
313 "font family: (%s, %d, %d) -> %s, %d",
314 family, bold, italic, res, *index);
316 if (!res && priv->path_default) {
317 res = strdup(priv->path_default);
318 *index = priv->index_default;
319 ass_msg(library, MSGL_WARN, "fontconfig_select: Using default font: "
320 "(%s, %d, %d) -> %s, %d", family, bold, italic,
321 res, *index);
323 if (!res) {
324 res = select_font(library, priv, "Arial", 0, bold, italic,
325 index, code);
326 if (res)
327 ass_msg(library, MSGL_WARN, "fontconfig_select: Using 'Arial' "
328 "font family: (%s, %d, %d) -> %s, %d", family, bold,
329 italic, res, *index);
331 if (res)
332 ass_msg(library, MSGL_V,
333 "fontconfig_select: (%s, %d, %d) -> %s, %d", family, bold,
334 italic, res, *index);
335 return res;
339 * \brief Process memory font.
340 * \param priv private data
341 * \param library library object
342 * \param ftlibrary freetype library object
343 * \param idx index of the processed font in library->fontdata
345 * Builds a font pattern in memory via FT_New_Memory_Face/FcFreeTypeQueryFace.
347 static void process_fontdata(FCInstance *priv, ASS_Library *library,
348 FT_Library ftlibrary, int idx)
350 int rc;
351 const char *name = library->fontdata[idx].name;
352 const char *data = library->fontdata[idx].data;
353 int data_size = library->fontdata[idx].size;
355 FT_Face face;
356 FcPattern *pattern;
357 FcFontSet *fset;
358 FcBool res;
359 int face_index, num_faces = 1;
361 for (face_index = 0; face_index < num_faces; ++face_index) {
362 rc = FT_New_Memory_Face(ftlibrary, (unsigned char *) data,
363 data_size, face_index, &face);
364 if (rc) {
365 ass_msg(library, MSGL_WARN, "Error opening memory font: %s",
366 name);
367 return;
369 num_faces = face->num_faces;
371 pattern =
372 FcFreeTypeQueryFace(face, (unsigned char *) name, 0,
373 FcConfigGetBlanks(priv->config));
374 if (!pattern) {
375 ass_msg(library, MSGL_WARN, "%s failed", "FcFreeTypeQueryFace");
376 FT_Done_Face(face);
377 return;
380 fset = FcConfigGetFonts(priv->config, FcSetSystem); // somehow it failes when asked for FcSetApplication
381 if (!fset) {
382 ass_msg(library, MSGL_WARN, "%s failed", "FcConfigGetFonts");
383 FT_Done_Face(face);
384 return;
387 res = FcFontSetAdd(fset, pattern);
388 if (!res) {
389 ass_msg(library, MSGL_WARN, "%s failed", "FcFontSetAdd");
390 FT_Done_Face(face);
391 return;
394 FT_Done_Face(face);
399 * \brief Init fontconfig.
400 * \param library libass library object
401 * \param ftlibrary freetype library object
402 * \param family default font family
403 * \param path default font path
404 * \param fc whether fontconfig should be used
405 * \param config path to a fontconfig configuration file, or NULL
406 * \param update whether the fontconfig cache should be built/updated
407 * \return pointer to fontconfig private data
409 FCInstance *fontconfig_init(ASS_Library *library,
410 FT_Library ftlibrary, const char *family,
411 const char *path, int fc, const char *config,
412 int update)
414 int rc;
415 FCInstance *priv = calloc(1, sizeof(FCInstance));
416 const char *dir = library->fonts_dir;
417 int i;
419 if (!fc) {
420 ass_msg(library, MSGL_WARN,
421 "Fontconfig disabled, only default font will be used.");
422 goto exit;
425 priv->config = FcConfigCreate();
426 rc = FcConfigParseAndLoad(priv->config, (unsigned char *) config, FcTrue);
427 if (!rc) {
428 ass_msg(library, MSGL_WARN, "No usable fontconfig configuration "
429 "file found, using fallback.");
430 FcConfigDestroy(priv->config);
431 priv->config = FcInitLoadConfig();
432 rc++;
434 if (rc && update) {
435 FcConfigBuildFonts(priv->config);
438 if (!rc || !priv->config) {
439 ass_msg(library, MSGL_FATAL,
440 "No valid fontconfig configuration found!");
441 FcConfigDestroy(priv->config);
442 goto exit;
445 for (i = 0; i < library->num_fontdata; ++i)
446 process_fontdata(priv, library, ftlibrary, i);
448 if (dir) {
449 ass_msg(library, MSGL_V, "Updating font cache");
451 rc = FcConfigAppFontAddDir(priv->config, (const FcChar8 *) dir);
452 if (!rc) {
453 ass_msg(library, MSGL_WARN, "%s failed", "FcConfigAppFontAddDir");
457 priv->family_default = family ? strdup(family) : NULL;
458 exit:
459 priv->path_default = path ? strdup(path) : NULL;
460 priv->index_default = 0;
462 return priv;
465 int fontconfig_update(FCInstance *priv)
467 return FcConfigBuildFonts(priv->config);
470 #else /* CONFIG_FONTCONFIG */
472 char *fontconfig_select(ASS_Library *library, FCInstance *priv,
473 const char *family, int treat_family_as_pattern,
474 unsigned bold, unsigned italic, int *index,
475 uint32_t code)
477 *index = priv->index_default;
478 char* res = priv->path_default ? strdup(priv->path_default) : 0;
479 return res;
482 FCInstance *fontconfig_init(ASS_Library *library,
483 FT_Library ftlibrary, const char *family,
484 const char *path, int fc, const char *config,
485 int update)
487 FCInstance *priv;
489 ass_msg(library, MSGL_WARN,
490 "Fontconfig disabled, only default font will be used.");
492 priv = calloc(1, sizeof(FCInstance));
494 priv->path_default = path ? strdup(path) : 0;
495 priv->index_default = 0;
496 return priv;
499 int fontconfig_update(FCInstance *priv)
501 // Do nothing
502 return 1;
505 #endif
507 void fontconfig_done(FCInstance *priv)
509 #ifdef CONFIG_FONTCONFIG
510 if (priv && priv->config)
511 FcConfigDestroy(priv->config);
512 #endif
513 if (priv && priv->path_default)
514 free(priv->path_default);
515 if (priv && priv->family_default)
516 free(priv->family_default);
517 if (priv)
518 free(priv);