2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file font_unix.cpp Functions related to font handling on Unix/Fontconfig. */
10 #include "../../stdafx.h"
11 #include "../../debug.h"
12 #include "../../fontdetection.h"
13 #include "../../string_func.h"
14 #include "../../strings_func.h"
16 #include <fontconfig/fontconfig.h>
18 #include "safeguards.h"
21 #include FT_FREETYPE_H
23 extern FT_Library _library
;
26 * Split the font name into the font family and style. These fields are separated by a comma,
27 * but the style does not necessarily need to exist.
28 * @param font_name The font name.
29 * @return The font family and style.
31 static std::tuple
<std::string
, std::string
> SplitFontFamilyAndStyle(std::string_view font_name
)
33 auto separator
= font_name
.find(',');
34 if (separator
== std::string_view::npos
) return { std::string(font_name
), std::string() };
36 auto begin
= font_name
.find_first_not_of("\t ", separator
+ 1);
37 if (begin
== std::string_view::npos
) return { std::string(font_name
.substr(0, separator
)), std::string() };
39 return { std::string(font_name
.substr(0, separator
)), std::string(font_name
.substr(begin
)) };
42 FT_Error
GetFontByFaceName(const char *font_name
, FT_Face
*face
)
44 FT_Error err
= FT_Err_Cannot_Open_Resource
;
47 ShowInfo("Unable to load font configuration");
51 auto fc_instance
= FcConfigReference(nullptr);
52 assert(fc_instance
!= nullptr);
54 /* Split & strip the font's style */
55 auto [font_family
, font_style
] = SplitFontFamilyAndStyle(font_name
);
57 /* Resolve the name and populate the information structure */
58 FcPattern
*pat
= FcPatternCreate();
59 if (!font_family
.empty()) FcPatternAddString(pat
, FC_FAMILY
, reinterpret_cast<const FcChar8
*>(font_family
.c_str()));
60 if (!font_style
.empty()) FcPatternAddString(pat
, FC_STYLE
, reinterpret_cast<const FcChar8
*>(font_style
.c_str()));
61 FcConfigSubstitute(nullptr, pat
, FcMatchPattern
);
62 FcDefaultSubstitute(pat
);
63 FcFontSet
*fs
= FcFontSetCreate();
65 FcPattern
*match
= FcFontMatch(nullptr, pat
, &result
);
67 if (fs
!= nullptr && match
!= nullptr) {
72 FcFontSetAdd(fs
, match
);
74 for (int i
= 0; err
!= FT_Err_Ok
&& i
< fs
->nfont
; i
++) {
75 /* Try the new filename */
76 if (FcPatternGetString(fs
->fonts
[i
], FC_FILE
, 0, &file
) == FcResultMatch
&&
77 FcPatternGetString(fs
->fonts
[i
], FC_FAMILY
, 0, &family
) == FcResultMatch
&&
78 FcPatternGetString(fs
->fonts
[i
], FC_STYLE
, 0, &style
) == FcResultMatch
&&
79 FcPatternGetInteger(fs
->fonts
[i
], FC_INDEX
, 0, &index
) == FcResultMatch
) {
81 /* The correct style? */
82 if (!font_style
.empty() && !StrEqualsIgnoreCase(font_style
, (char *)style
)) continue;
84 /* Font config takes the best shot, which, if the family name is spelled
85 * wrongly a 'random' font, so check whether the family name is the
86 * same as the supplied name */
87 if (StrEqualsIgnoreCase(font_family
, (char *)family
)) {
88 err
= FT_New_Face(_library
, (char *)file
, index
, face
);
94 FcPatternDestroy(pat
);
96 FcConfigDestroy(fc_instance
);
101 bool SetFallbackFont(FontCacheSettings
*settings
, const std::string
&language_isocode
, int, MissingGlyphSearcher
*callback
)
105 if (!FcInit()) return ret
;
107 auto fc_instance
= FcConfigReference(nullptr);
108 assert(fc_instance
!= nullptr);
110 /* Fontconfig doesn't handle full language isocodes, only the part
111 * before the _ of e.g. en_GB is used, so "remove" everything after
113 std::string lang
= fmt::format(":lang={}", language_isocode
.substr(0, language_isocode
.find('_')));
115 /* First create a pattern to match the wanted language. */
116 FcPattern
*pat
= FcNameParse((const FcChar8
*)lang
.c_str());
117 /* We only want to know these attributes. */
118 FcObjectSet
*os
= FcObjectSetBuild(FC_FILE
, FC_INDEX
, FC_SPACING
, FC_SLANT
, FC_WEIGHT
, nullptr);
119 /* Get the list of filenames matching the wanted language. */
120 FcFontSet
*fs
= FcFontList(nullptr, pat
, os
);
122 /* We don't need these anymore. */
123 FcObjectSetDestroy(os
);
124 FcPatternDestroy(pat
);
127 int best_weight
= -1;
128 const char *best_font
= nullptr;
131 for (int i
= 0; i
< fs
->nfont
; i
++) {
132 FcPattern
*font
= fs
->fonts
[i
];
134 FcChar8
*file
= nullptr;
135 FcResult res
= FcPatternGetString(font
, FC_FILE
, 0, &file
);
136 if (res
!= FcResultMatch
|| file
== nullptr) {
140 /* Get a font with the right spacing .*/
142 FcPatternGetInteger(font
, FC_SPACING
, 0, &value
);
143 if (callback
->Monospace() != (value
== FC_MONO
) && value
!= FC_DUAL
) continue;
145 /* Do not use those that explicitly say they're slanted. */
146 FcPatternGetInteger(font
, FC_SLANT
, 0, &value
);
147 if (value
!= 0) continue;
149 /* We want the fatter font as they look better at small sizes. */
150 FcPatternGetInteger(font
, FC_WEIGHT
, 0, &value
);
151 if (value
<= best_weight
) continue;
153 /* Possible match based on attributes, get index. */
155 res
= FcPatternGetInteger(font
, FC_INDEX
, 0, &index
);
156 if (res
!= FcResultMatch
) continue;
158 callback
->SetFontNames(settings
, (const char *)file
, &index
);
160 bool missing
= callback
->FindMissingGlyphs();
161 Debug(fontcache
, 1, "Font \"{}\" misses{} glyphs", (char *)file
, missing
? "" : " no");
165 best_font
= (const char *)file
;
170 if (best_font
!= nullptr) {
172 callback
->SetFontNames(settings
, best_font
, &best_index
);
173 InitFontCache(callback
->Monospace());
176 /* Clean up the list of filenames. */
177 FcFontSetDestroy(fs
);
180 FcConfigDestroy(fc_instance
);