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.
27 #include <sys/types.h>
31 #include FT_FREETYPE_H
33 #include "ass_utils.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>
44 #ifdef CONFIG_FONTCONFIG
52 #ifdef CONFIG_FONTCONFIG
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
63 static FcFontSet
*match_fullname(ASS_Library
*lib
, FCInstance
*priv
,
67 FcFontSet
*result
= FcFontSetCreate();
71 if ((sets
[nsets
] = FcConfigGetFonts(priv
->config
, FcSetSystem
)))
73 if ((sets
[nsets
] = FcConfigGetFonts(priv
->config
, FcSetApplication
)))
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
];
83 while (FcPatternGetString(pat
, FC_FULLNAME
, pi
++,
84 (FcChar8
**) &fullname
) == FcResultMatch
)
85 if (strcasecmp(fullname
, family
) == 0) {
86 FcFontSetAdd(result
, FcPatternDuplicate(pat
));
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
,
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
;
125 if (treat_family_as_pattern
)
126 pat
= FcNameParse((const FcChar8
*) family
);
128 pat
= FcPatternCreate();
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.
149 char *s
= strdup(family
);
150 char *p
= s
+ strlen(s
);
152 if (*p
== ' ' || *p
== '-') {
154 FcPatternAddString(pat
, FC_FAMILY
, (const FcChar8
*) 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
);
170 fsorted
= FcFontSort(priv
->config
, pat
, FcTrue
, NULL
, &result
);
171 ffullname
= match_fullname(library
, priv
, family
);
172 if (!fsorted
|| !ffullname
)
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
)
193 if (r_outline
!= FcTrue
)
197 result
= FcPatternGetCharSet(curp
, FC_CHARSET
, 0, &r_charset
);
198 if (result
!= FcResultMatch
)
200 if (FcCharSetHasChar(r_charset
, code
))
204 if (curf
>= fset
->nfont
)
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
]);
219 result
= FcPatternGetInteger(rpat
, FC_INDEX
, 0, &r_index
);
220 if (result
!= FcResultMatch
)
224 result
= FcPatternGetString(rpat
, FC_FILE
, 0, &r_file
);
225 if (result
!= FcResultMatch
)
227 retval
= strdup((const char *) r_file
);
229 result
= FcPatternGetString(rpat
, FC_FAMILY
, 0, &r_family
);
230 if (result
!= FcResultMatch
)
233 result
= FcPatternGetString(rpat
, FC_FULLNAME
, 0, &r_fullname
);
234 if (result
!= FcResultMatch
)
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: "
243 (const char *) (r_fullname
? r_fullname
: r_family
), family
);
245 result
= FcPatternGetString(rpat
, FC_STYLE
, 0, &r_style
);
246 if (result
!= FcResultMatch
)
249 result
= FcPatternGetInteger(rpat
, FC_SLANT
, 0, &r_slant
);
250 if (result
!= FcResultMatch
)
253 result
= FcPatternGetInteger(rpat
, FC_WEIGHT
, 0, &r_weight
);
254 if (result
!= FcResultMatch
)
257 result
= FcPatternGetBool(rpat
, FC_EMBOLDEN
, 0, &r_embolden
);
258 if (result
!= FcResultMatch
)
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" : "");
269 FcPatternDestroy(pat
);
271 FcPatternDestroy(rpat
);
273 FcFontSetDestroy(fsorted
);
275 FcFontSetDestroy(ffullname
);
277 FcFontSetDestroy(fset
);
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
,
299 *index
= priv
->index_default
;
300 res
= priv
->path_default
? strdup(priv
->path_default
) : 0;
303 if (family
&& *family
)
305 select_font(library
, priv
, family
, treat_family_as_pattern
,
306 bold
, italic
, index
, code
);
307 if (!res
&& priv
->family_default
) {
309 select_font(library
, priv
, priv
->family_default
, 0, bold
,
310 italic
, index
, code
);
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
,
324 res
= select_font(library
, priv
, "Arial", 0, bold
, italic
,
327 ass_msg(library
, MSGL_WARN
, "fontconfig_select: Using 'Arial' "
328 "font family: (%s, %d, %d) -> %s, %d", family
, bold
,
329 italic
, res
, *index
);
332 ass_msg(library
, MSGL_V
,
333 "fontconfig_select: (%s, %d, %d) -> %s, %d", family
, bold
,
334 italic
, res
, *index
);
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
)
351 const char *name
= library
->fontdata
[idx
].name
;
352 const char *data
= library
->fontdata
[idx
].data
;
353 int data_size
= library
->fontdata
[idx
].size
;
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
);
365 ass_msg(library
, MSGL_WARN
, "Error opening memory font: %s",
369 num_faces
= face
->num_faces
;
372 FcFreeTypeQueryFace(face
, (unsigned char *) name
, 0,
373 FcConfigGetBlanks(priv
->config
));
375 ass_msg(library
, MSGL_WARN
, "%s failed", "FcFreeTypeQueryFace");
380 fset
= FcConfigGetFonts(priv
->config
, FcSetSystem
); // somehow it failes when asked for FcSetApplication
382 ass_msg(library
, MSGL_WARN
, "%s failed", "FcConfigGetFonts");
387 res
= FcFontSetAdd(fset
, pattern
);
389 ass_msg(library
, MSGL_WARN
, "%s failed", "FcFontSetAdd");
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
,
415 FCInstance
*priv
= calloc(1, sizeof(FCInstance
));
416 const char *dir
= library
->fonts_dir
;
420 ass_msg(library
, MSGL_WARN
,
421 "Fontconfig disabled, only default font will be used.");
425 priv
->config
= FcConfigCreate();
426 rc
= FcConfigParseAndLoad(priv
->config
, (unsigned char *) config
, FcTrue
);
428 ass_msg(library
, MSGL_WARN
, "No usable fontconfig configuration "
429 "file found, using fallback.");
430 FcConfigDestroy(priv
->config
);
431 priv
->config
= FcInitLoadConfig();
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
);
445 for (i
= 0; i
< library
->num_fontdata
; ++i
)
446 process_fontdata(priv
, library
, ftlibrary
, i
);
449 ass_msg(library
, MSGL_V
, "Updating font cache");
451 rc
= FcConfigAppFontAddDir(priv
->config
, (const FcChar8
*) dir
);
453 ass_msg(library
, MSGL_WARN
, "%s failed", "FcConfigAppFontAddDir");
457 priv
->family_default
= family
? strdup(family
) : NULL
;
459 priv
->path_default
= path
? strdup(path
) : NULL
;
460 priv
->index_default
= 0;
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
,
477 *index
= priv
->index_default
;
478 char* res
= priv
->path_default
? strdup(priv
->path_default
) : 0;
482 FCInstance
*fontconfig_init(ASS_Library
*library
,
483 FT_Library ftlibrary
, const char *family
,
484 const char *path
, int fc
, const char *config
,
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;
499 int fontconfig_update(FCInstance
*priv
)
507 void fontconfig_done(FCInstance
*priv
)
509 #ifdef CONFIG_FONTCONFIG
510 if (priv
&& priv
->config
)
511 FcConfigDestroy(priv
->config
);
513 if (priv
&& priv
->path_default
)
514 free(priv
->path_default
);
515 if (priv
&& priv
->family_default
)
516 free(priv
->family_default
);