OpenTTD Source 20250711-master-gaaf5d39b15
font_unix.cpp
Go to the documentation of this file.
1/*
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/>.
6 */
7
10#include "../../stdafx.h"
11
12#include "../../misc/autorelease.hpp"
13#include "../../debug.h"
14#include "../../fontdetection.h"
15#include "../../string_func.h"
16#include "../../strings_func.h"
17
18#include <fontconfig/fontconfig.h>
19
20#include "../../safeguards.h"
21
22#include <ft2build.h>
23#include FT_FREETYPE_H
24
25extern FT_Library _ft_library;
26
32static const FcChar8 *ToFcString(const std::string &str)
33{
34 return reinterpret_cast<const FcChar8 *>(str.c_str());
35}
36
42static const char *FromFcString(const FcChar8 *str)
43{
44 return reinterpret_cast<const char *>(str);
45}
46
53static std::tuple<std::string, std::string> SplitFontFamilyAndStyle(std::string_view font_name)
54{
55 auto separator = font_name.find(',');
56 if (separator == std::string_view::npos) return { std::string(font_name), std::string() };
57
58 auto begin = font_name.find_first_not_of("\t ", separator + 1);
59 if (begin == std::string_view::npos) return { std::string(font_name.substr(0, separator)), std::string() };
60
61 return { std::string(font_name.substr(0, separator)), std::string(font_name.substr(begin)) };
62}
63
64FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face)
65{
66 FT_Error err = FT_Err_Cannot_Open_Resource;
67
68 if (!FcInit()) {
69 ShowInfo("Unable to load font configuration");
70 return err;
71 }
72
73 auto fc_instance = AutoRelease<FcConfig, FcConfigDestroy>(FcConfigReference(nullptr));
74 assert(fc_instance != nullptr);
75
76 /* Split & strip the font's style */
77 auto [font_family, font_style] = SplitFontFamilyAndStyle(font_name);
78
79 /* Resolve the name and populate the information structure */
80 auto pat = AutoRelease<FcPattern, FcPatternDestroy>(FcPatternCreate());
81 if (!font_family.empty()) FcPatternAddString(pat.get(), FC_FAMILY, ToFcString(font_family));
82 if (!font_style.empty()) FcPatternAddString(pat.get(), FC_STYLE, ToFcString(font_style));
83 FcConfigSubstitute(nullptr, pat.get(), FcMatchPattern);
84 FcDefaultSubstitute(pat.get());
85 auto fs = AutoRelease<FcFontSet, FcFontSetDestroy>(FcFontSetCreate());
86 FcResult result;
87 FcPattern *match = FcFontMatch(nullptr, pat.get(), &result);
88
89 if (fs == nullptr || match == nullptr) return err;
90
91 FcFontSetAdd(fs.get(), match);
92
93 for (FcPattern *font : std::span(fs->fonts, fs->nfont)) {
94 FcChar8 *family;
95 FcChar8 *style;
96 FcChar8 *file;
97 int32_t index;
98
99 /* Try the new filename */
100 if (FcPatternGetString(font, FC_FILE, 0, &file) != FcResultMatch) continue;
101 if (FcPatternGetString(font, FC_FAMILY, 0, &family) != FcResultMatch) continue;
102 if (FcPatternGetString(font, FC_STYLE, 0, &style) != FcResultMatch) continue;
103 if (FcPatternGetInteger(font, FC_INDEX, 0, &index) != FcResultMatch) continue;
104
105 /* The correct style? */
106 if (!font_style.empty() && !StrEqualsIgnoreCase(font_style, FromFcString(style))) continue;
107
108 /* Font config takes the best shot, which, if the family name is spelled
109 * wrongly a 'random' font, so check whether the family name is the
110 * same as the supplied name */
111 if (StrEqualsIgnoreCase(font_family, FromFcString(family))) {
112 err = FT_New_Face(_ft_library, FromFcString(file), index, face);
113 if (err == FT_Err_Ok) return err;
114 }
115 }
116
117 return err;
118}
119
120bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback)
121{
122 bool ret = false;
123
124 if (!FcInit()) return ret;
125
126 auto fc_instance = AutoRelease<FcConfig, FcConfigDestroy>(FcConfigReference(nullptr));
127 assert(fc_instance != nullptr);
128
129 /* Fontconfig doesn't handle full language isocodes, only the part
130 * before the _ of e.g. en_GB is used, so "remove" everything after
131 * the _. */
132 std::string lang = fmt::format(":lang={}", language_isocode.substr(0, language_isocode.find('_')));
133
134 /* First create a pattern to match the wanted language. */
135 auto pat = AutoRelease<FcPattern, FcPatternDestroy>(FcNameParse(ToFcString(lang)));
136 /* We only want to know these attributes. */
137 auto os = AutoRelease<FcObjectSet, FcObjectSetDestroy>(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, nullptr));
138 /* Get the list of filenames matching the wanted language. */
139 auto fs = AutoRelease<FcFontSet, FcFontSetDestroy>(FcFontList(nullptr, pat.get(), os.get()));
140
141 if (fs == nullptr) return ret;
142
143 int best_weight = -1;
144 const char *best_font = nullptr;
145 int best_index = 0;
146
147 for (FcPattern *font : std::span(fs->fonts, fs->nfont)) {
148 FcChar8 *file = nullptr;
149 FcResult res = FcPatternGetString(font, FC_FILE, 0, &file);
150 if (res != FcResultMatch || file == nullptr) continue;
151
152 /* Get a font with the right spacing .*/
153 int value = 0;
154 FcPatternGetInteger(font, FC_SPACING, 0, &value);
155 if (callback->Monospace() != (value == FC_MONO) && value != FC_DUAL) continue;
156
157 /* Do not use those that explicitly say they're slanted. */
158 FcPatternGetInteger(font, FC_SLANT, 0, &value);
159 if (value != 0) continue;
160
161 /* We want the fatter font as they look better at small sizes. */
162 FcPatternGetInteger(font, FC_WEIGHT, 0, &value);
163 if (value <= best_weight) continue;
164
165 /* Possible match based on attributes, get index. */
166 int32_t index;
167 res = FcPatternGetInteger(font, FC_INDEX, 0, &index);
168 if (res != FcResultMatch) continue;
169
170 callback->SetFontNames(settings, FromFcString(file), &index);
171
172 bool missing = callback->FindMissingGlyphs();
173 Debug(fontcache, 1, "Font \"{}\" misses{} glyphs", FromFcString(file), missing ? "" : " no");
174
175 if (!missing) {
176 best_weight = value;
177 best_font = FromFcString(file);
178 best_index = index;
179 }
180 }
181
182 if (best_font == nullptr) return false;
183
184 callback->SetFontNames(settings, best_font, &best_index);
185 InitFontCache(callback->Monospace());
186 return true;
187}
std::unique_ptr< T, DeleterFromFunc< Tfunc > > AutoRelease
Specialisation of std::unique_ptr for objects which must be deleted by calling a function.
A searcher for missing glyphs.
bool FindMissingGlyphs()
Check whether there are glyphs missing in the current language.
Definition strings.cpp:2279
virtual void SetFontNames(struct FontCacheSettings *settings, std::string_view font_name, const void *os_data=nullptr)=0
Set the right font names.
virtual bool Monospace()=0
Whether to search for a monospace font or not.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
fluid_settings_t * settings
FluidSynth settings handle.
FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face)
Load a freetype font face with the given font name.
Definition font_unix.cpp:64
static const char * FromFcString(const FcChar8 *str)
Get a C-style string from a FontConfig-style string.
Definition font_unix.cpp:42
static const FcChar8 * ToFcString(const std::string &str)
Get a FontConfig-style string from a std::string.
Definition font_unix.cpp:32
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback)
We would like to have a fallback font as the current one doesn't contain all characters we need.
static std::tuple< std::string, std::string > SplitFontFamilyAndStyle(std::string_view font_name)
Split the font name into the font family and style.
Definition font_unix.cpp:53
void InitFontCache(bool monospace)
(Re)initialize the font cache related things, i.e.
bool StrEqualsIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
Definition string.cpp:321
Settings for the four different fonts.
Definition fontcache.h:200