Handle illegal \a tags like VSFilter
[libass.git] / libass / ass_fontconfig.c
blob006be9767a69619bc42385bd36950f99241e0da5
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 // 4yo fontconfig does not have these.
55 // They are only needed for debug output, anyway.
56 #ifndef FC_FULLNAME
57 #define FC_FULLNAME "fullname"
58 #endif
59 #ifndef FC_EMBOLDEN
60 #define FC_EMBOLDEN "embolden"
61 #endif
63 /**
64 * \brief Low-level font selection.
65 * \param priv private data
66 * \param family font family
67 * \param treat_family_as_pattern treat family as fontconfig pattern
68 * \param bold font weight value
69 * \param italic font slant value
70 * \param index out: font index inside a file
71 * \param code: the character that should be present in the font, can be 0
72 * \return font file path
74 static char *_select_font(ASS_Library *library, FCInstance *priv,
75 const char *family, int treat_family_as_pattern,
76 unsigned bold, unsigned italic, int *index,
77 uint32_t code)
79 FcBool rc;
80 FcResult result;
81 FcPattern *pat = NULL, *rpat = NULL;
82 int r_index, r_slant, r_weight;
83 FcChar8 *r_family, *r_style, *r_file, *r_fullname;
84 FcBool r_outline, r_embolden;
85 FcCharSet *r_charset;
86 FcFontSet *fset = NULL;
87 int curf;
88 char *retval = NULL;
89 int family_cnt = 0;
91 *index = 0;
93 if (treat_family_as_pattern)
94 pat = FcNameParse((const FcChar8 *) family);
95 else
96 pat = FcPatternCreate();
98 if (!pat)
99 goto error;
101 if (!treat_family_as_pattern) {
102 FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) family);
104 // In SSA/ASS fonts are sometimes referenced by their "full name",
105 // which is usually a concatenation of family name and font
106 // style (ex. Ottawa Bold). Full name is available from
107 // FontConfig pattern element FC_FULLNAME, but it is never
108 // used for font matching.
109 // Therefore, I'm removing words from the end of the name one
110 // by one, and adding shortened names to the pattern. It seems
111 // that the first value (full name in this case) has
112 // precedence in matching.
113 // An alternative approach could be to reimplement FcFontSort
114 // using FC_FULLNAME instead of FC_FAMILY.
115 family_cnt = 1;
117 char *s = strdup(family);
118 char *p = s + strlen(s);
119 while (--p > s)
120 if (*p == ' ' || *p == '-') {
121 *p = '\0';
122 FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) s);
123 ++family_cnt;
125 free(s);
128 FcPatternAddBool(pat, FC_OUTLINE, FcTrue);
129 FcPatternAddInteger(pat, FC_SLANT, italic);
130 FcPatternAddInteger(pat, FC_WEIGHT, bold);
132 FcDefaultSubstitute(pat);
134 rc = FcConfigSubstitute(priv->config, pat, FcMatchPattern);
135 if (!rc)
136 goto error;
138 fset = FcFontSort(priv->config, pat, FcTrue, NULL, &result);
139 if (!fset)
140 goto error;
142 for (curf = 0; curf < fset->nfont; ++curf) {
143 FcPattern *curp = fset->fonts[curf];
145 result = FcPatternGetBool(curp, FC_OUTLINE, 0, &r_outline);
146 if (result != FcResultMatch)
147 continue;
148 if (r_outline != FcTrue)
149 continue;
150 if (!code)
151 break;
152 result = FcPatternGetCharSet(curp, FC_CHARSET, 0, &r_charset);
153 if (result != FcResultMatch)
154 continue;
155 if (FcCharSetHasChar(r_charset, code))
156 break;
159 if (curf >= fset->nfont)
160 goto error;
162 #if (FC_VERSION >= 20297)
163 if (!treat_family_as_pattern) {
164 // Remove all extra family names from original pattern.
165 // After this, FcFontRenderPrepare will select the most relevant family
166 // name in case there are more than one of them.
167 for (; family_cnt > 1; --family_cnt)
168 FcPatternRemove(pat, FC_FAMILY, family_cnt - 1);
170 #endif
172 rpat = FcFontRenderPrepare(priv->config, pat, fset->fonts[curf]);
173 if (!rpat)
174 goto error;
176 result = FcPatternGetInteger(rpat, FC_INDEX, 0, &r_index);
177 if (result != FcResultMatch)
178 goto error;
179 *index = r_index;
181 result = FcPatternGetString(rpat, FC_FILE, 0, &r_file);
182 if (result != FcResultMatch)
183 goto error;
184 retval = strdup((const char *) r_file);
186 result = FcPatternGetString(rpat, FC_FAMILY, 0, &r_family);
187 if (result != FcResultMatch)
188 r_family = NULL;
190 result = FcPatternGetString(rpat, FC_FULLNAME, 0, &r_fullname);
191 if (result != FcResultMatch)
192 r_fullname = NULL;
194 if (!treat_family_as_pattern &&
195 !(r_family && strcasecmp((const char *) r_family, family) == 0) &&
196 !(r_fullname && strcasecmp((const char *) r_fullname, family) == 0))
197 ass_msg(library, MSGL_WARN,
198 "fontconfig: Selected font is not the requested one: "
199 "'%s' != '%s'",
200 (const char *) (r_fullname ? r_fullname : r_family), family);
202 result = FcPatternGetString(rpat, FC_STYLE, 0, &r_style);
203 if (result != FcResultMatch)
204 r_style = NULL;
206 result = FcPatternGetInteger(rpat, FC_SLANT, 0, &r_slant);
207 if (result != FcResultMatch)
208 r_slant = 0;
210 result = FcPatternGetInteger(rpat, FC_WEIGHT, 0, &r_weight);
211 if (result != FcResultMatch)
212 r_weight = 0;
214 result = FcPatternGetBool(rpat, FC_EMBOLDEN, 0, &r_embolden);
215 if (result != FcResultMatch)
216 r_embolden = 0;
218 ass_msg(library, MSGL_V,
219 "Font info: family '%s', style '%s', fullname '%s',"
220 " slant %d, weight %d%s", (const char *) r_family,
221 (const char *) r_style, (const char *) r_fullname, r_slant,
222 r_weight, r_embolden ? ", embolden" : "");
224 error:
225 if (pat)
226 FcPatternDestroy(pat);
227 if (rpat)
228 FcPatternDestroy(rpat);
229 if (fset)
230 FcFontSetDestroy(fset);
231 return retval;
235 * \brief Find a font. Use default family or path if necessary.
236 * \param priv_ private data
237 * \param family font family
238 * \param treat_family_as_pattern treat family as fontconfig pattern
239 * \param bold font weight value
240 * \param italic font slant value
241 * \param index out: font index inside a file
242 * \param code: the character that should be present in the font, can be 0
243 * \return font file path
245 char *fontconfig_select(ASS_Library *library, FCInstance *priv,
246 const char *family, int treat_family_as_pattern,
247 unsigned bold, unsigned italic, int *index,
248 uint32_t code)
250 char *res = 0;
251 if (!priv->config) {
252 *index = priv->index_default;
253 return priv->path_default;
255 if (family && *family)
256 res =
257 _select_font(library, priv, family, treat_family_as_pattern,
258 bold, italic, index, code);
259 if (!res && priv->family_default) {
260 res =
261 _select_font(library, priv, priv->family_default, 0, bold,
262 italic, index, code);
263 if (res)
264 ass_msg(library, MSGL_WARN, "fontconfig_select: Using default "
265 "font family: (%s, %d, %d) -> %s, %d",
266 family, bold, italic, res, *index);
268 if (!res && priv->path_default) {
269 res = priv->path_default;
270 *index = priv->index_default;
271 ass_msg(library, MSGL_WARN, "fontconfig_select: Using default font: "
272 "(%s, %d, %d) -> %s, %d", family, bold, italic,
273 res, *index);
275 if (!res) {
276 res = _select_font(library, priv, "Arial", 0, bold, italic,
277 index, code);
278 if (res)
279 ass_msg(library, MSGL_WARN, "fontconfig_select: Using 'Arial' "
280 "font family: (%s, %d, %d) -> %s, %d", family, bold,
281 italic, res, *index);
283 if (res)
284 ass_msg(library, MSGL_V,
285 "fontconfig_select: (%s, %d, %d) -> %s, %d", family, bold,
286 italic, res, *index);
287 return res;
290 #if (FC_VERSION < 20402)
291 static char *validate_fname(char *name)
293 char *fname;
294 char *p;
295 char *q;
296 unsigned code;
297 int sz = strlen(name);
299 q = fname = malloc(sz + 1);
300 p = name;
301 while (*p) {
302 code = ass_utf8_get_char(&p);
303 if (code == 0)
304 break;
305 if ((code > 0x7F) ||
306 (code == '\\') ||
307 (code == '/') ||
308 (code == ':') ||
309 (code == '*') ||
310 (code == '?') ||
311 (code == '<') ||
312 (code == '>') || (code == '|') || (code == 0)) {
313 *q++ = '_';
314 } else {
315 *q++ = code;
317 if (p - name > sz)
318 break;
320 *q = 0;
321 return fname;
323 #endif
326 * \brief Process memory font.
327 * \param priv private data
328 * \param library library object
329 * \param ftlibrary freetype library object
330 * \param idx index of the processed font in library->fontdata
331 * With FontConfig >= 2.4.2, builds a font pattern in memory via FT_New_Memory_Face/FcFreeTypeQueryFace.
332 * With older FontConfig versions, save the font to ~/.mplayer/fonts.
334 static void process_fontdata(FCInstance *priv, ASS_Library *library,
335 FT_Library ftlibrary, int idx)
337 int rc;
338 const char *name = library->fontdata[idx].name;
339 const char *data = library->fontdata[idx].data;
340 int data_size = library->fontdata[idx].size;
342 #if (FC_VERSION < 20402)
343 struct stat st;
344 char *fname;
345 const char *fonts_dir = library->fonts_dir;
346 char buf[1000];
347 FILE *fp = NULL;
349 if (!fonts_dir)
350 return;
351 rc = stat(fonts_dir, &st);
352 if (rc) {
353 int res;
354 #ifndef __MINGW32__
355 res = mkdir(fonts_dir, 0700);
356 #else
357 res = mkdir(fonts_dir);
358 #endif
359 if (res) {
360 ass_msg(library, MSGL_WARN, "Failed to create directory '%s'",
361 fonts_dir);
363 } else if (!S_ISDIR(st.st_mode)) {
364 ass_msg(library, MSGL_WARN, "Not a directory: '%s'", fonts_dir);
367 fname = validate_fname((char *) name);
369 snprintf(buf, 1000, "%s/%s", fonts_dir, fname);
370 free(fname);
372 fp = fopen(buf, "wb");
373 if (!fp)
374 return;
376 fwrite(data, data_size, 1, fp);
377 fclose(fp);
379 #else // (FC_VERSION >= 20402)
380 FT_Face face;
381 FcPattern *pattern;
382 FcFontSet *fset;
383 FcBool res;
384 int face_index, num_faces = 1;
386 for (face_index = 0; face_index < num_faces; ++face_index) {
387 rc = FT_New_Memory_Face(ftlibrary, (unsigned char *) data,
388 data_size, face_index, &face);
389 if (rc) {
390 ass_msg(library, MSGL_WARN, "Error opening memory font: %s",
391 name);
392 return;
394 num_faces = face->num_faces;
396 pattern =
397 FcFreeTypeQueryFace(face, (unsigned char *) name, 0,
398 FcConfigGetBlanks(priv->config));
399 if (!pattern) {
400 ass_msg(library, MSGL_WARN, "%s failed", "FcFreeTypeQueryFace");
401 FT_Done_Face(face);
402 return;
405 fset = FcConfigGetFonts(priv->config, FcSetSystem); // somehow it failes when asked for FcSetApplication
406 if (!fset) {
407 ass_msg(library, MSGL_WARN, "%s failed", "FcConfigGetFonts");
408 FT_Done_Face(face);
409 return;
412 res = FcFontSetAdd(fset, pattern);
413 if (!res) {
414 ass_msg(library, MSGL_WARN, "%s failed", "FcFontSetAdd");
415 FT_Done_Face(face);
416 return;
419 FT_Done_Face(face);
421 #endif
425 * \brief Init fontconfig.
426 * \param library libass library object
427 * \param ftlibrary freetype library object
428 * \param family default font family
429 * \param path default font path
430 * \param fc whether fontconfig should be used
431 * \param config path to a fontconfig configuration file, or NULL
432 * \param update whether the fontconfig cache should be built/updated
433 * \return pointer to fontconfig private data
435 FCInstance *fontconfig_init(ASS_Library *library,
436 FT_Library ftlibrary, const char *family,
437 const char *path, int fc, const char *config,
438 int update)
440 int rc;
441 FCInstance *priv = calloc(1, sizeof(FCInstance));
442 const char *dir = library->fonts_dir;
443 int i;
445 if (!fc) {
446 ass_msg(library, MSGL_WARN,
447 "Fontconfig disabled, only default font will be used.");
448 goto exit;
451 priv->config = FcConfigCreate();
452 rc = FcConfigParseAndLoad(priv->config, (unsigned char *) config, FcTrue);
453 if (!rc) {
454 ass_msg(library, MSGL_WARN, "No usable fontconfig configuration "
455 "file found, using fallback.");
456 FcConfigDestroy(priv->config);
457 priv->config = FcInitLoadConfig();
458 rc++;
460 if (rc && update) {
461 FcConfigBuildFonts(priv->config);
464 if (!rc || !priv->config) {
465 ass_msg(library, MSGL_FATAL,
466 "No valid fontconfig configuration found!");
467 FcConfigDestroy(priv->config);
468 goto exit;
471 for (i = 0; i < library->num_fontdata; ++i)
472 process_fontdata(priv, library, ftlibrary, i);
474 if (dir) {
475 if (FcDirCacheValid((const FcChar8 *) dir) == FcFalse) {
476 ass_msg(library, MSGL_INFO, "Updating font cache");
477 if (FcGetVersion() >= 20390 && FcGetVersion() < 20400)
478 ass_msg(library, MSGL_WARN, "Beta versions of fontconfig"
479 "are not supported. Update before reporting any bugs");
480 // FontConfig >= 2.4.0 updates cache automatically in FcConfigAppFontAddDir()
481 if (FcGetVersion() < 20390) {
482 FcFontSet *fcs;
483 FcStrSet *fss;
484 fcs = FcFontSetCreate();
485 fss = FcStrSetCreate();
486 rc = FcStrSetAdd(fss, (const FcChar8 *) dir);
487 if (!rc) {
488 ass_msg(library, MSGL_WARN, "%s failed", "FcStrSetAdd");
489 goto ErrorFontCache;
492 rc = FcDirScan(fcs, fss, NULL,
493 FcConfigGetBlanks(priv->config),
494 (const FcChar8 *) dir, FcFalse);
495 if (!rc) {
496 ass_msg(library, MSGL_WARN, "%s failed", "FcDirScan");
497 goto ErrorFontCache;
500 rc = FcDirSave(fcs, fss, (const FcChar8 *) dir);
501 if (!rc) {
502 ass_msg(library, MSGL_WARN, "%s failed", "FcDirSave");
503 goto ErrorFontCache;
505 ErrorFontCache:
510 rc = FcConfigAppFontAddDir(priv->config, (const FcChar8 *) dir);
511 if (!rc) {
512 ass_msg(library, MSGL_WARN, "%s failed", "FcConfigAppFontAddDir");
516 priv->family_default = family ? strdup(family) : NULL;
517 exit:
518 priv->path_default = path ? strdup(path) : NULL;
519 priv->index_default = 0;
521 return priv;
524 int fontconfig_update(FCInstance *priv)
526 return FcConfigBuildFonts(priv->config);
529 #else /* CONFIG_FONTCONFIG */
531 char *fontconfig_select(ASS_Library *library, FCInstance *priv,
532 const char *family, int treat_family_as_pattern,
533 unsigned bold, unsigned italic, int *index,
534 uint32_t code)
536 *index = priv->index_default;
537 return priv->path_default;
540 FCInstance *fontconfig_init(ASS_Library *library,
541 FT_Library ftlibrary, const char *family,
542 const char *path, int fc, const char *config,
543 int update)
545 FCInstance *priv;
547 ass_msg(library, MSGL_WARN,
548 "Fontconfig disabled, only default font will be used.");
550 priv = calloc(1, sizeof(FCInstance));
552 priv->path_default = strdup(path);
553 priv->index_default = 0;
554 return priv;
557 int fontconfig_update(FCInstance *priv)
559 // Do nothing
560 return 1;
563 #endif
565 void fontconfig_done(FCInstance *priv)
567 #ifdef CONFIG_FONTCONFIG
568 if (priv && priv->config)
569 FcConfigDestroy(priv->config);
570 #endif
571 if (priv && priv->path_default)
572 free(priv->path_default);
573 if (priv && priv->family_default)
574 free(priv->family_default);
575 if (priv)
576 free(priv);