Skip to content

Commit 19f65dc

Browse files
committed
Change: Test font for elligibility before adding as fallback.
1 parent 45a06e3 commit 19f65dc

File tree

12 files changed

+130
-58
lines changed

12 files changed

+130
-58
lines changed

src/fontcache.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ FontCacheSettings _fcsettings;
5252
* @param callback The function to call to check for missing glyphs.
5353
* @return true if a font has been set, false otherwise.
5454
*/
55-
/* static */ bool FontProviderManager::FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback)
55+
/* static */ bool FontProviderManager::FindFallbackFont(const std::string &language_isocode, const MissingGlyphs &missing_glyphs, MissingGlyphSearcher *callback)
5656
{
5757
return std::ranges::any_of(FontProviderManager::GetProviders(),
58-
[&](auto *provider) { return provider->FindFallbackFont(language_isocode, fontsizes, callback); });
58+
[&](auto *provider) { return provider->FindFallbackFont(language_isocode, missing_glyphs, callback); });
5959
}
6060

6161
int FontCache::GetDefaultFontHeight(FontSize fs)
@@ -244,6 +244,31 @@ static std::string GetFontCacheFontName(FontSize fs)
244244
}
245245
}
246246

247+
/**
248+
* Test a fallback font, with optional OS-specific handle, for specific glyphs.
249+
* @param name Name of font to test.
250+
* @param handle OS-specific handle or data of font.
251+
* @param missing_glyphs Glyphs to search for.
252+
*/
253+
/* static */ bool FontCache::TryFallback(const std::string &name, std::span<const std::byte> os_handle, const std::set<char32_t> &missing_glyphs)
254+
{
255+
/* Load the font without registering it. The font size does not matter. */
256+
auto fc = FontProviderManager::LoadFont(FS_NORMAL, FontType::TrueType, false, name, os_handle);
257+
if (fc == nullptr) return false;
258+
259+
size_t matching_chars = 0;
260+
for (const char32_t &c : missing_glyphs) {
261+
if (fc->MapCharToGlyph(c, true) != 0) ++matching_chars;
262+
}
263+
264+
if (matching_chars < missing_glyphs.size()) {
265+
Debug(fontcache, 1, "Font \"{}\" misses {} glyphs", name, missing_glyphs.size() - matching_chars);
266+
return false;
267+
}
268+
269+
return true;
270+
}
271+
247272
/**
248273
* (Re)initialize the font cache related things, i.e. load the non-sprite fonts.
249274
* @param fontsizes Font sizes to be initialised.

src/fontcache.h

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ class FontCache {
6161
FontCache::AddFallback(fontsizes, name, os_handle);
6262
}
6363

64+
static bool TryFallback(const std::string &name, std::span<const std::byte> os_handle, const std::set<char32_t> &missing_glyphs);
65+
66+
/**
67+
* Test a fallback font, with OS-specific handle, for specific glyphs.
68+
* @param name Name of font to test.
69+
* @param handle OS-specific handle or data of font.
70+
* @param missing_glyphs Glyphs to search for.
71+
*/
72+
template <typename T>
73+
static bool TryFallbackWithHandle(const std::string &name, T &handle, const std::set<char32_t> &missing_glyphs)
74+
{
75+
auto os_handle = std::as_bytes(std::span(&handle, 1));
76+
return FontCache::TryFallback(name, os_handle, missing_glyphs);
77+
}
78+
6479
/**
6580
* Get the FontSize of the font.
6681
* @return The FontSize.
@@ -230,6 +245,11 @@ enum class FontType : uint8_t {
230245
TrueType, ///< Scalable TrueType fonts.
231246
};
232247

248+
struct MissingGlyphs {
249+
std::set<char32_t> glyphs{}; ///< Glyphs that are missing.
250+
FontSizes fontsizes{}; ///< Font sizes which are missing glyphs,
251+
};
252+
233253
/** Factory for FontCaches. */
234254
class FontCacheFactory : public BaseProvider<FontCacheFactory> {
235255
public:
@@ -244,13 +264,13 @@ class FontCacheFactory : public BaseProvider<FontCacheFactory> {
244264
}
245265

246266
virtual std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span<const std::byte> os_handle) const = 0;
247-
virtual bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, class MissingGlyphSearcher *callback) const = 0;
267+
virtual bool FindFallbackFont(const std::string &language_isocode, const MissingGlyphs &glyphs, class MissingGlyphSearcher *callback) const = 0;
248268
};
249269

250270
class FontProviderManager : ProviderManager<FontCacheFactory> {
251271
public:
252272
static std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span<const std::byte> os_handle);
253-
static bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback);
273+
static bool FindFallbackFont(const std::string &language_isocode, const MissingGlyphs &glyphs, MissingGlyphSearcher *callback);
254274
};
255275

256276
/* Implemented in spritefontcache.cpp */

src/fontcache/freetypefontcache.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,10 @@ class FreeTypeFontCacheFactory : public FontCacheFactory {
269269
return LoadFont(fs, face, font_name, GetFontCacheFontSize(fs));
270270
}
271271

272-
bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, class MissingGlyphSearcher *callback) const override
272+
bool FindFallbackFont(const std::string &language_isocode, const MissingGlyphs &glyphs, class MissingGlyphSearcher *callback) const override
273273
{
274274
#ifdef WITH_FONTCONFIG
275-
if (FontConfigFindFallbackFont(language_isocode, fontsizes, callback)) return true;
275+
if (FontConfigFindFallbackFont(language_isocode, glyphs, callback)) return true;
276276
#endif /* WITH_FONTCONFIG */
277277

278278
return false;

src/fontcache/spritefontcache.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class SpriteFontCacheFactory : public FontCacheFactory {
165165
return std::make_unique<SpriteFontCache>(fs);
166166
}
167167

168-
bool FindFallbackFont(const std::string &, FontSizes, class MissingGlyphSearcher *) const override
168+
bool FindFallbackFont(const std::string &, const MissingGlyphs &, class MissingGlyphSearcher *) const override
169169
{
170170
return false;
171171
}

src/os/macosx/font_osx.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ class CoreTextFontCacheFactory : public FontCacheFactory {
249249
return std::make_unique<CoreTextFontCache>(fs, std::move(font_ref), GetFontCacheFontSize(fs));
250250
}
251251

252-
bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override
252+
bool FindFallbackFont(const std::string &language_isocode, const MissingGlyphs &glyphs, MissingGlyphSearcher *callback) const override
253253
{
254254
/* Determine fallback font using CoreText. This uses the language isocode
255255
* to find a suitable font. CoreText is available from 10.5 onwards. */
@@ -313,10 +313,10 @@ class CoreTextFontCacheFactory : public FontCacheFactory {
313313
if (name.starts_with(".") || name.starts_with("LastResort")) continue;
314314

315315
/* Save result. */
316-
FontCache::AddFallback(fontsizes, name);
317-
if (!callback->FindMissingGlyphs()) {
316+
result = FontCache::TryFallback(name, {}, glyphs.glyphs);
317+
if (result) {
318+
FontCache::AddFallback(fontsizes, name);
318319
Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name);
319-
result = true;
320320
break;
321321
}
322322
}
@@ -325,11 +325,12 @@ class CoreTextFontCacheFactory : public FontCacheFactory {
325325
if (!result) {
326326
/* For some OS versions, the font 'Arial Unicode MS' does not report all languages it
327327
* supports. If we didn't find any other font, just try it, maybe we get lucky. */
328-
FontCache::AddFallback(fontsizes, "Arial Unicode MS");
329-
result = !callback->FindMissingGlyphs();
328+
result = FontCache::TryFallbackWithHandle(FromFcString(file), index, glyphs.glyphs);
329+
if (result) {
330+
FontCache::AddFallback(fontsizes, "Arial Unicode MS");
331+
}
330332
}
331333

332-
callback->FindMissingGlyphs();
333334
return result;
334335
}
335336

src/os/unix/font_unix.cpp

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ static int GetPreferredWeightDistance(int weight)
139139
return 0;
140140
}
141141

142-
bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback)
142+
bool FontConfigFindFallbackFont(const std::string &language_isocode, const MissingGlyphs &glyphs, MissingGlyphSearcher *)
143143
{
144144
bool ret = false;
145145

@@ -156,7 +156,7 @@ bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes f
156156
/* First create a pattern to match the wanted language. */
157157
auto pat = AutoRelease<FcPattern, FcPatternDestroy>(FcNameParse(ToFcString(lang)));
158158
/* We only want to know these attributes. */
159-
auto os = AutoRelease<FcObjectSet, FcObjectSetDestroy>(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, nullptr));
159+
auto os = AutoRelease<FcObjectSet, FcObjectSetDestroy>(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, FC_CHARSET, nullptr));
160160
/* Get the list of filenames matching the wanted language. */
161161
auto fs = AutoRelease<FcFontSet, FcFontSetDestroy>(FcFontList(nullptr, pat.get(), os.get()));
162162

@@ -174,7 +174,7 @@ bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes f
174174
/* Get a font with the right spacing .*/
175175
int value = 0;
176176
FcPatternGetInteger(font, FC_SPACING, 0, &value);
177-
if (fontsizes.Test(FS_MONO) != (value == FC_MONO) && value != FC_DUAL) continue;
177+
if (glyphs.fontsizes.Test(FS_MONO) != (value == FC_MONO) && value != FC_DUAL) continue;
178178

179179
/* Do not use those that explicitly say they're slanted. */
180180
FcPatternGetInteger(font, FC_SLANT, 0, &value);
@@ -185,26 +185,32 @@ bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes f
185185
int weight = GetPreferredWeightDistance(value);
186186
if (best_weight != -1 && weight > best_weight) continue;
187187

188+
size_t matching_chars = 0;
189+
FcCharSet *charset;
190+
FcPatternGetCharSet(font, FC_CHARSET, 0, &charset);
191+
for (const char32_t &c : glyphs.glyphs) {
192+
if (FcCharSetHasChar(charset, c)) ++matching_chars;
193+
}
194+
195+
if (matching_chars < glyphs.glyphs.size()) {
196+
Debug(fontcache, 1, "Font \"{}\" misses {} glyphs", FromFcString(file), glyphs.glyphs.size() - matching_chars);
197+
continue;
198+
}
199+
188200
/* Possible match based on attributes, get index. */
189201
int32_t index;
190202
res = FcPatternGetInteger(font, FC_INDEX, 0, &index);
191203
if (res != FcResultMatch) continue;
192204

193-
FontCache::AddFallbackWithHandle(fontsizes, FromFcString(file), index);
194-
195-
bool missing = callback->FindMissingGlyphs();
196-
Debug(fontcache, 1, "Font \"{}\" misses{} glyphs", FromFcString(file), missing ? "" : " no");
205+
// if (!FontCache::TryFallbackWithHandle(FromFcString(file), index, glyphs.glyphs)) continue;
197206

198-
if (!missing) {
199-
best_weight = weight;
200-
best_font = FromFcString(file);
201-
best_index = index;
202-
}
207+
best_weight = weight;
208+
best_font = FromFcString(file);
209+
best_index = index;
203210
}
204211

205212
if (best_font == nullptr) return false;
206213

207-
FontCache::AddFallbackWithHandle(fontsizes, best_font, best_index);
208-
FontCache::LoadFontCaches(fontsizes);
214+
FontCache::AddFallbackWithHandle(glyphs.fontsizes, best_font, best_index);
209215
return true;
210216
}

src/os/unix/font_unix.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face);
2121

22-
bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback);
22+
bool FontConfigFindFallbackFont(const std::string &language_isocode, const MissingGlyphs &glyphs, MissingGlyphSearcher *callback);
2323

2424
#endif /* WITH_FONTCONFIG */
2525

src/os/windows/font_win32.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
7777
char font_name[MAX_PATH];
7878
convert_from_fs(logfont->elfFullName, font_name);
7979

80+
if (!FontCache::TryFallbackWithHandle(font_name, logfont->elfLogFont, glyphs.glyphs)) return 1;
81+
8082
FontCache::AddFallbackWithHandle(info->fontsizes, font_name, logfont->elfLogFont);
81-
if (info->callback->FindMissingGlyphs()) return 1;
8283
Debug(fontcache, 1, "Fallback font: {}", font_name);
8384
return 0; // stop enumerating
8485
}
@@ -302,7 +303,7 @@ class Win32FontCacheFactory : FontCacheFactory {
302303
return LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font_name);
303304
}
304305

305-
bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override
306+
bool FindFallbackFont(const std::string &language_isocode, const MissingGlyphs &glyphs, MissingGlyphSearcher *callback) const override
306307
{
307308
Debug(fontcache, 1, "Trying fallback fonts");
308309
EFCParam langInfo;

src/strings.cpp

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,36 +2299,37 @@ std::string_view GetCurrentLanguageIsoCode()
22992299
return _langpack.langpack->isocode;
23002300
}
23012301

2302-
/**
2303-
* Check whether there are glyphs missing in the current language.
2304-
* @return If glyphs are missing, return \c true, else return \c false.
2305-
*/
2306-
bool MissingGlyphSearcher::FindMissingGlyphs()
2302+
MissingGlyphs BaseStringMissingGlyphSearcher::GetRequiredGlyphs(FontSizes fontsizes)
23072303
{
2308-
FontCache::LoadFontCaches(this->fontsizes);
2304+
MissingGlyphs res{};
23092305

23102306
this->Reset();
23112307
for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
2312-
FontSize size = this->DefaultSize();
2313-
FontCache *fc = FontCache::Get(size);
2308+
FontSize fs = this->DefaultSize();
2309+
FontCache *fc = FontCache::Get(fs);
23142310
for (char32_t c : Utf8View(*text)) {
23152311
if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
2316-
size = (FontSize)(c - SCC_FIRST_FONT);
2317-
fc = FontCache::Get(size);
2318-
} else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) {
2319-
/* The character is printable, but not in the normal font. This is the case we were testing for. */
2320-
Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", static_cast<uint32_t>(c), FontSizeToName(size));
2321-
return true;
2312+
fs = (FontSize)(c - SCC_FIRST_FONT);
2313+
fc = FontCache::Get(fs);
2314+
continue;
23222315
}
2316+
2317+
if (!fontsizes.Test(fs)) continue;
2318+
if (!IsPrintable(c) || IsTextDirectionChar(c)) continue;
2319+
if (IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END)) continue;
2320+
if (fc->MapCharToGlyph(c, false) != 0) continue;
2321+
2322+
res.fontsizes.Set(fs);
2323+
res.glyphs.insert(c);
23232324
}
23242325
}
2325-
return false;
2326+
return res;
23262327
}
23272328

23282329
/** Helper for searching through the language pack. */
2329-
class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
2330+
class LanguagePackGlyphSearcher : public BaseStringMissingGlyphSearcher {
23302331
public:
2331-
LanguagePackGlyphSearcher() : MissingGlyphSearcher(FONTSIZES_REQUIRED) {}
2332+
LanguagePackGlyphSearcher() : BaseStringMissingGlyphSearcher(FONTSIZES_REQUIRED) {}
23322333

23332334
private:
23342335
uint i; ///< Iterator for the primary language tables.
@@ -2377,15 +2378,20 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
23772378
{
23782379
static LanguagePackGlyphSearcher pack_searcher;
23792380
if (searcher == nullptr) searcher = &pack_searcher;
2380-
bool bad_font = searcher->FindMissingGlyphs();
2381+
2382+
FontCache::LoadFontCaches(searcher->fontsizes);
2383+
2384+
auto missing_glyphs = searcher->GetRequiredGlyphs(searcher->fontsizes);
2385+
bool bad_font = missing_glyphs.fontsizes.Any();
2386+
23812387
#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
23822388
if (bad_font) {
23832389
/* We found an unprintable character... lets try whether we can find
23842390
* a fallback font that can print the characters in the current language. */
23852391
bool any_font_configured = !_fcsettings.medium.font.empty();
23862392
FontCacheSettings backup = _fcsettings;
23872393

2388-
bad_font = !FontProviderManager::FindFallbackFont(_langpack.langpack->isocode, searcher->fontsizes, searcher);
2394+
bad_font = !FontProviderManager::FindFallbackFont(_langpack.langpack->isocode, missing_glyphs, searcher);
23892395

23902396
_fcsettings = std::move(backup);
23912397

@@ -2404,7 +2410,7 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
24042410
/* Our fallback font does miss characters too, so keep the
24052411
* user chosen font as that is more likely to be any good than
24062412
* the wild guess we made */
2407-
FontCache::LoadFontCaches(searcher->fontsizes);
2413+
FontCache::LoadFontCaches(missing_glyphs.fontsizes);
24082414
}
24092415
}
24102416
#endif
@@ -2421,12 +2427,12 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
24212427
ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING);
24222428

24232429
/* Reset the font width */
2424-
LoadStringWidthTable(searcher->fontsizes);
2430+
LoadStringWidthTable(missing_glyphs.fontsizes);
24252431
return;
24262432
}
24272433

24282434
/* Update the font with cache */
2429-
LoadStringWidthTable(searcher->fontsizes);
2435+
LoadStringWidthTable(missing_glyphs.fontsizes);
24302436

24312437
#if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
24322438
/*

src/strings_func.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#ifndef STRINGS_FUNC_H
1111
#define STRINGS_FUNC_H
1212

13+
#include "fontcache.h"
1314
#include "strings_type.h"
1415
#include "gfx_type.h"
1516
#include "vehicle_type.h"
@@ -162,6 +163,20 @@ class MissingGlyphSearcher {
162163
/** Make sure everything gets destructed right. */
163164
virtual ~MissingGlyphSearcher() = default;
164165

166+
/**
167+
* Get set of glyphs required for the current language.
168+
* @param fontsizes Font sizes to test.
169+
* @return Set of required glyphs.
170+
**/
171+
virtual MissingGlyphs GetRequiredGlyphs(FontSizes fontsizes) = 0;
172+
};
173+
174+
class BaseStringMissingGlyphSearcher : public MissingGlyphSearcher {
175+
public:
176+
BaseStringMissingGlyphSearcher(FontSizes fontsizes) : MissingGlyphSearcher(fontsizes) {}
177+
178+
MissingGlyphs GetRequiredGlyphs(FontSizes fontsizes) override;
179+
165180
/**
166181
* Get the next string to search through.
167182
* @return The next string or nullopt if there is none.
@@ -178,10 +193,8 @@ class MissingGlyphSearcher {
178193
* Reset the search, i.e. begin from the beginning again.
179194
*/
180195
virtual void Reset() = 0;
181-
182-
bool FindMissingGlyphs();
183196
};
184197

185-
void CheckForMissingGlyphs(MissingGlyphSearcher *search = nullptr);
198+
void CheckForMissingGlyphs(MissingGlyphSearcher *searcher = nullptr);
186199

187200
#endif /* STRINGS_FUNC_H */

0 commit comments

Comments
 (0)