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
54 // 4yo fontconfig does not have these.
55 // They are only needed for debug output, anyway.
57 #define FC_FULLNAME "fullname"
60 #define FC_EMBOLDEN "embolden"
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
,
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
;
86 FcFontSet
*fset
= NULL
;
93 if (treat_family_as_pattern
)
94 pat
= FcNameParse((const FcChar8
*) family
);
96 pat
= FcPatternCreate();
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.
117 char *s
= strdup(family
);
118 char *p
= s
+ strlen(s
);
120 if (*p
== ' ' || *p
== '-') {
122 FcPatternAddString(pat
, FC_FAMILY
, (const FcChar8
*) 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
);
138 fset
= FcFontSort(priv
->config
, pat
, FcTrue
, NULL
, &result
);
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
)
148 if (r_outline
!= FcTrue
)
152 result
= FcPatternGetCharSet(curp
, FC_CHARSET
, 0, &r_charset
);
153 if (result
!= FcResultMatch
)
155 if (FcCharSetHasChar(r_charset
, code
))
159 if (curf
>= fset
->nfont
)
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);
172 rpat
= FcFontRenderPrepare(priv
->config
, pat
, fset
->fonts
[curf
]);
176 result
= FcPatternGetInteger(rpat
, FC_INDEX
, 0, &r_index
);
177 if (result
!= FcResultMatch
)
181 result
= FcPatternGetString(rpat
, FC_FILE
, 0, &r_file
);
182 if (result
!= FcResultMatch
)
184 retval
= strdup((const char *) r_file
);
186 result
= FcPatternGetString(rpat
, FC_FAMILY
, 0, &r_family
);
187 if (result
!= FcResultMatch
)
190 result
= FcPatternGetString(rpat
, FC_FULLNAME
, 0, &r_fullname
);
191 if (result
!= FcResultMatch
)
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: "
200 (const char *) (r_fullname
? r_fullname
: r_family
), family
);
202 result
= FcPatternGetString(rpat
, FC_STYLE
, 0, &r_style
);
203 if (result
!= FcResultMatch
)
206 result
= FcPatternGetInteger(rpat
, FC_SLANT
, 0, &r_slant
);
207 if (result
!= FcResultMatch
)
210 result
= FcPatternGetInteger(rpat
, FC_WEIGHT
, 0, &r_weight
);
211 if (result
!= FcResultMatch
)
214 result
= FcPatternGetBool(rpat
, FC_EMBOLDEN
, 0, &r_embolden
);
215 if (result
!= FcResultMatch
)
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" : "");
226 FcPatternDestroy(pat
);
228 FcPatternDestroy(rpat
);
230 FcFontSetDestroy(fset
);
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
,
252 *index
= priv
->index_default
;
253 return priv
->path_default
;
255 if (family
&& *family
)
257 _select_font(library
, priv
, family
, treat_family_as_pattern
,
258 bold
, italic
, index
, code
);
259 if (!res
&& priv
->family_default
) {
261 _select_font(library
, priv
, priv
->family_default
, 0, bold
,
262 italic
, index
, code
);
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
,
276 res
= _select_font(library
, priv
, "Arial", 0, bold
, italic
,
279 ass_msg(library
, MSGL_WARN
, "fontconfig_select: Using 'Arial' "
280 "font family: (%s, %d, %d) -> %s, %d", family
, bold
,
281 italic
, res
, *index
);
284 ass_msg(library
, MSGL_V
,
285 "fontconfig_select: (%s, %d, %d) -> %s, %d", family
, bold
,
286 italic
, res
, *index
);
290 #if (FC_VERSION < 20402)
291 static char *validate_fname(char *name
)
297 int sz
= strlen(name
);
299 q
= fname
= malloc(sz
+ 1);
302 code
= ass_utf8_get_char(&p
);
312 (code
== '>') || (code
== '|') || (code
== 0)) {
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
)
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)
345 const char *fonts_dir
= library
->fonts_dir
;
351 rc
= stat(fonts_dir
, &st
);
355 res
= mkdir(fonts_dir
, 0700);
357 res
= mkdir(fonts_dir
);
360 ass_msg(library
, MSGL_WARN
, "Failed to create directory '%s'",
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
);
372 fp
= fopen(buf
, "wb");
376 fwrite(data
, data_size
, 1, fp
);
379 #else // (FC_VERSION >= 20402)
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
);
390 ass_msg(library
, MSGL_WARN
, "Error opening memory font: %s",
394 num_faces
= face
->num_faces
;
397 FcFreeTypeQueryFace(face
, (unsigned char *) name
, 0,
398 FcConfigGetBlanks(priv
->config
));
400 ass_msg(library
, MSGL_WARN
, "%s failed", "FcFreeTypeQueryFace");
405 fset
= FcConfigGetFonts(priv
->config
, FcSetSystem
); // somehow it failes when asked for FcSetApplication
407 ass_msg(library
, MSGL_WARN
, "%s failed", "FcConfigGetFonts");
412 res
= FcFontSetAdd(fset
, pattern
);
414 ass_msg(library
, MSGL_WARN
, "%s failed", "FcFontSetAdd");
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
,
441 FCInstance
*priv
= calloc(1, sizeof(FCInstance
));
442 const char *dir
= library
->fonts_dir
;
446 ass_msg(library
, MSGL_WARN
,
447 "Fontconfig disabled, only default font will be used.");
451 priv
->config
= FcConfigCreate();
452 rc
= FcConfigParseAndLoad(priv
->config
, (unsigned char *) config
, FcTrue
);
454 ass_msg(library
, MSGL_WARN
, "No usable fontconfig configuration "
455 "file found, using fallback.");
456 FcConfigDestroy(priv
->config
);
457 priv
->config
= FcInitLoadConfig();
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
);
471 for (i
= 0; i
< library
->num_fontdata
; ++i
)
472 process_fontdata(priv
, library
, ftlibrary
, i
);
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) {
484 fcs
= FcFontSetCreate();
485 fss
= FcStrSetCreate();
486 rc
= FcStrSetAdd(fss
, (const FcChar8
*) dir
);
488 ass_msg(library
, MSGL_WARN
, "%s failed", "FcStrSetAdd");
492 rc
= FcDirScan(fcs
, fss
, NULL
,
493 FcConfigGetBlanks(priv
->config
),
494 (const FcChar8
*) dir
, FcFalse
);
496 ass_msg(library
, MSGL_WARN
, "%s failed", "FcDirScan");
500 rc
= FcDirSave(fcs
, fss
, (const FcChar8
*) dir
);
502 ass_msg(library
, MSGL_WARN
, "%s failed", "FcDirSave");
510 rc
= FcConfigAppFontAddDir(priv
->config
, (const FcChar8
*) dir
);
512 ass_msg(library
, MSGL_WARN
, "%s failed", "FcConfigAppFontAddDir");
516 priv
->family_default
= family
? strdup(family
) : NULL
;
518 priv
->path_default
= path
? strdup(path
) : NULL
;
519 priv
->index_default
= 0;
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
,
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
,
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;
557 int fontconfig_update(FCInstance
*priv
)
565 void fontconfig_done(FCInstance
*priv
)
567 #ifdef CONFIG_FONTCONFIG
568 if (priv
&& priv
->config
)
569 FcConfigDestroy(priv
->config
);
571 if (priv
&& priv
->path_default
)
572 free(priv
->path_default
);
573 if (priv
&& priv
->family_default
)
574 free(priv
->family_default
);