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
= FcNameParse((FcChar8
*)font_family
.data());
59 if (!font_style
.empty()) FcPatternAddString(pat
, FC_STYLE
, (FcChar8
*)font_style
.data());
60 FcConfigSubstitute(nullptr, pat
, FcMatchPattern
);
61 FcDefaultSubstitute(pat
);
62 FcFontSet
*fs
= FcFontSetCreate();
64 FcPattern
*match
= FcFontMatch(nullptr, pat
, &result
);
66 if (fs
!= nullptr && match
!= nullptr) {
71 FcFontSetAdd(fs
, match
);
73 for (int i
= 0; err
!= FT_Err_Ok
&& i
< fs
->nfont
; i
++) {
74 /* Try the new filename */
75 if (FcPatternGetString(fs
->fonts
[i
], FC_FILE
, 0, &file
) == FcResultMatch
&&
76 FcPatternGetString(fs
->fonts
[i
], FC_FAMILY
, 0, &family
) == FcResultMatch
&&
77 FcPatternGetString(fs
->fonts
[i
], FC_STYLE
, 0, &style
) == FcResultMatch
&&
78 FcPatternGetInteger(fs
->fonts
[i
], FC_INDEX
, 0, &index
) == FcResultMatch
) {
80 /* The correct style? */
81 if (!font_style
.empty() && !StrEqualsIgnoreCase(font_style
, (char *)style
)) continue;
83 /* Font config takes the best shot, which, if the family name is spelled
84 * wrongly a 'random' font, so check whether the family name is the
85 * same as the supplied name */
86 if (StrEqualsIgnoreCase(font_family
, (char *)family
)) {
87 err
= FT_New_Face(_library
, (char *)file
, index
, face
);
93 FcPatternDestroy(pat
);
95 FcConfigDestroy(fc_instance
);
100 bool SetFallbackFont(FontCacheSettings
*settings
, const std::string
&language_isocode
, int, MissingGlyphSearcher
*callback
)
104 if (!FcInit()) return ret
;
106 auto fc_instance
= FcConfigReference(nullptr);
107 assert(fc_instance
!= nullptr);
109 /* Fontconfig doesn't handle full language isocodes, only the part
110 * before the _ of e.g. en_GB is used, so "remove" everything after
112 std::string lang
= fmt::format(":lang={}", language_isocode
.substr(0, language_isocode
.find('_')));
114 /* First create a pattern to match the wanted language. */
115 FcPattern
*pat
= FcNameParse((const FcChar8
*)lang
.c_str());
116 /* We only want to know these attributes. */
117 FcObjectSet
*os
= FcObjectSetBuild(FC_FILE
, FC_INDEX
, FC_SPACING
, FC_SLANT
, FC_WEIGHT
, nullptr);
118 /* Get the list of filenames matching the wanted language. */
119 FcFontSet
*fs
= FcFontList(nullptr, pat
, os
);
121 /* We don't need these anymore. */
122 FcObjectSetDestroy(os
);
123 FcPatternDestroy(pat
);
126 int best_weight
= -1;
127 const char *best_font
= nullptr;
130 for (int i
= 0; i
< fs
->nfont
; i
++) {
131 FcPattern
*font
= fs
->fonts
[i
];
133 FcChar8
*file
= nullptr;
134 FcResult res
= FcPatternGetString(font
, FC_FILE
, 0, &file
);
135 if (res
!= FcResultMatch
|| file
== nullptr) {
139 /* Get a font with the right spacing .*/
141 FcPatternGetInteger(font
, FC_SPACING
, 0, &value
);
142 if (callback
->Monospace() != (value
== FC_MONO
) && value
!= FC_DUAL
) continue;
144 /* Do not use those that explicitly say they're slanted. */
145 FcPatternGetInteger(font
, FC_SLANT
, 0, &value
);
146 if (value
!= 0) continue;
148 /* We want the fatter font as they look better at small sizes. */
149 FcPatternGetInteger(font
, FC_WEIGHT
, 0, &value
);
150 if (value
<= best_weight
) continue;
152 /* Possible match based on attributes, get index. */
154 res
= FcPatternGetInteger(font
, FC_INDEX
, 0, &index
);
155 if (res
!= FcResultMatch
) continue;
157 callback
->SetFontNames(settings
, (const char *)file
, &index
);
159 bool missing
= callback
->FindMissingGlyphs();
160 Debug(fontcache
, 1, "Font \"{}\" misses{} glyphs", (char *)file
, missing
? "" : " no");
164 best_font
= (const char *)file
;
169 if (best_font
!= nullptr) {
171 callback
->SetFontNames(settings
, best_font
, &best_index
);
172 InitFontCache(callback
->Monospace());
175 /* Clean up the list of filenames. */
176 FcFontSetDestroy(fs
);
179 FcConfigDestroy(fc_instance
);