1
0
Fork 0

Codechange: Move fallback font detection to FontCacheFactory.

pull/13303/head
Peter Nelson 2025-06-10 11:46:18 +01:00
parent ddb128f10d
commit ee2f0f46ee
No known key found for this signature in database
GPG Key ID: 8EF8F0A467DF75ED
12 changed files with 187 additions and 176 deletions

View File

@ -187,7 +187,6 @@ add_files(
fios_gui.cpp
fontcache.cpp
fontcache.h
fontdetection.h
framerate_gui.cpp
framerate_type.h
gamelog.cpp

View File

@ -9,7 +9,6 @@
#include "stdafx.h"
#include "fontcache.h"
#include "fontdetection.h"
#include "blitter/factory.hpp"
#include "gfx_layout.h"
#include "openttd.h"
@ -39,6 +38,25 @@ FontCacheSettings _fcsettings;
}
}
/**
* We would like to have a fallback font as the current one
* doesn't contain all characters we need.
* This function must set all fonts of settings.
* @param settings the settings to overwrite the fontname of.
* @param language_isocode the language, e.g. en_GB.
* @param callback The function to call to check for missing glyphs.
* @return true if a font has been set, false otherwise.
*/
/* static */ bool FontProviderManager::SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback)
{
for (auto &provider : FontProviderManager::GetProviders()) {
if (provider->SetFallbackFont(settings, language_isocode, callback)) {
return true;
}
}
return false;
}
/**
* Create a new font cache.
* @param fs The size of the font.
@ -239,8 +257,3 @@ void UninitFontCache()
if (fc->HasParent()) delete fc;
}
}
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA)
bool SetFallbackFont(FontCacheSettings *, const std::string &, MissingGlyphSearcher *) { return false; }
#endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) */

View File

@ -229,11 +229,13 @@ public:
}
virtual void LoadFont(FontSize fs, FontType fonttype) = 0;
virtual bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) = 0;
};
class FontProviderManager : ProviderManager<FontCacheFactory> {
public:
static void LoadFont(FontSize fs, FontType fonttype);
static bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback);
};
/* Implemented in spritefontcache.cpp */

View File

@ -8,14 +8,14 @@
/** @file freetypefontcache.cpp FreeType font cache implementation. */
#include "../stdafx.h"
#include "../debug.h"
#include "../fontcache.h"
#include "../fontdetection.h"
#include "../blitter/factory.hpp"
#include "../core/math_func.hpp"
#include "../zoom_func.h"
#include "../fileio_func.h"
#include "../error_func.h"
#include "../../os/unix/font_unix.h"
#include "truetypefontcache.h"
#include "../table/control_codes.h"
@ -258,8 +258,10 @@ public:
}
}
#ifdef WITH_FONTCONFIG
/* Try loading based on font face name (OS-wide fonts). */
if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face);
#endif /* WITH_FONTCONFIG */
if (error == FT_Err_Ok) {
error = LoadFont(fs, face, font, GetFontCacheFontSize(fs));
@ -271,6 +273,15 @@ public:
}
}
bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) override
{
#ifdef WITH_FONTCONFIG
if (FontConfigSetFallbackFont(settings, language_isocode, callback)) return true;
#endif /* WITH_FONTCONFIG */
return false;
}
private:
static FT_Error LoadFont(FontSize fs, FT_Face face, std::string_view font_name, uint size)
{
@ -311,10 +322,4 @@ private:
static FreeTypeFontCacheFactory s_freetype_fontcache_factory;
#if !defined(WITH_FONTCONFIG)
FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face) { return FT_Err_Cannot_Open_Resource; }
#endif /* !defined(WITH_FONTCONFIG) */
#endif /* WITH_FREETYPE */

View File

@ -101,6 +101,11 @@ public:
new SpriteFontCache(fs);
}
bool SetFallbackFont(struct FontCacheSettings *, const std::string &, class MissingGlyphSearcher *) override
{
return false;
}
};
static SpriteFontCacheFactory s_sprite_fontcache_factory;

View File

@ -1,41 +0,0 @@
/*
* This file is part of OpenTTD.
* 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.
* 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.
* 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/>.
*/
/** @file fontdetection.h Functions related to detecting/finding the right font. */
#ifndef FONTDETECTION_H
#define FONTDETECTION_H
#include "fontcache.h"
#ifdef WITH_FREETYPE
#include <ft2build.h>
#include FT_FREETYPE_H
/**
* Load a freetype font face with the given font name.
* @param font_name The name of the font to load.
* @param face The face that has been found.
* @return The error we encountered.
*/
FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face);
#endif /* WITH_FREETYPE */
/**
* We would like to have a fallback font as the current one
* doesn't contain all characters we need.
* This function must set all fonts of settings.
* @param settings the settings to overwrite the fontname of.
* @param language_isocode the language, e.g. en_GB.
* @param callback The function to call to check for missing glyphs.
* @return true if a font has been set, false otherwise.
*/
bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback);
#endif

View File

@ -14,7 +14,6 @@
#include "../../blitter/factory.hpp"
#include "../../error_func.h"
#include "../../fileio_func.h"
#include "../../fontdetection.h"
#include "../../string_func.h"
#include "../../strings_func.h"
#include "../../zoom_func.h"
@ -24,91 +23,6 @@
#include "../../safeguards.h"
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback)
{
/* Determine fallback font using CoreText. This uses the language isocode
* to find a suitable font. CoreText is available from 10.5 onwards. */
std::string lang;
if (language_isocode == "zh_TW") {
/* Traditional Chinese */
lang = "zh-Hant";
} else if (language_isocode == "zh_CN") {
/* Simplified Chinese */
lang = "zh-Hans";
} else {
/* Just copy the first part of the isocode. */
lang = language_isocode.substr(0, language_isocode.find('_'));
}
/* Create a font descriptor matching the wanted language and latin (english) glyphs.
* Can't use CFAutoRelease here for everything due to the way the dictionary has to be created. */
CFStringRef lang_codes[2];
lang_codes[0] = CFStringCreateWithCString(kCFAllocatorDefault, lang.c_str(), kCFStringEncodingUTF8);
lang_codes[1] = CFSTR("en");
CFArrayRef lang_arr = CFArrayCreate(kCFAllocatorDefault, (const void **)lang_codes, lengthof(lang_codes), &kCFTypeArrayCallBacks);
CFAutoRelease<CFDictionaryRef> lang_attribs(CFDictionaryCreate(kCFAllocatorDefault, const_cast<const void **>(reinterpret_cast<const void *const *>(&kCTFontLanguagesAttribute)), (const void **)&lang_arr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
CFAutoRelease<CTFontDescriptorRef> lang_desc(CTFontDescriptorCreateWithAttributes(lang_attribs.get()));
CFRelease(lang_arr);
CFRelease(lang_codes[0]);
/* Get array of all font descriptors for the wanted language. */
CFAutoRelease<CFSetRef> mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast<const void **>(reinterpret_cast<const void *const *>(&kCTFontLanguagesAttribute)), 1, &kCFTypeSetCallBacks));
CFAutoRelease<CFArrayRef> descs(CTFontDescriptorCreateMatchingFontDescriptors(lang_desc.get(), mandatory_attribs.get()));
bool result = false;
for (int tries = 0; tries < 2; tries++) {
for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()); i++) {
CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i);
/* Get font traits. */
CFAutoRelease<CFDictionaryRef> traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute));
CTFontSymbolicTraits symbolic_traits;
CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait), kCFNumberIntType, &symbolic_traits);
/* Skip symbol fonts and vertical fonts. */
if ((symbolic_traits & kCTFontClassMaskTrait) == (CTFontStylisticClass)kCTFontSymbolicClass || (symbolic_traits & kCTFontVerticalTrait)) continue;
/* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */
if (symbolic_traits & kCTFontBoldTrait) continue;
/* Select monospaced fonts if asked for. */
if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue;
/* Get font name. */
char buffer[128];
CFAutoRelease<CFStringRef> font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute));
CFStringGetCString(font_name.get(), buffer, std::size(buffer), kCFStringEncodingUTF8);
/* Serif fonts usually look worse on-screen with only small
* font sizes. As such, we try for a sans-serif font first.
* If we can't find one in the first try, try all fonts. */
if (tries == 0 && (symbolic_traits & kCTFontClassMaskTrait) != (CTFontStylisticClass)kCTFontSansSerifClass) continue;
/* There are some special fonts starting with an '.' and the last
* resort font that aren't usable. Skip them. */
std::string_view name{buffer};
if (name.starts_with(".") || name.starts_with("LastResort")) continue;
/* Save result. */
callback->SetFontNames(settings, name);
if (!callback->FindMissingGlyphs()) {
Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name);
result = true;
break;
}
}
}
if (!result) {
/* For some OS versions, the font 'Arial Unicode MS' does not report all languages it
* supports. If we didn't find any other font, just try it, maybe we get lucky. */
callback->SetFontNames(settings, "Arial Unicode MS");
result = !callback->FindMissingGlyphs();
}
callback->FindMissingGlyphs();
return result;
}
CoreTextFontCache::CoreTextFontCache(FontSize fs, CFAutoRelease<CTFontDescriptorRef> &&font, int pixels) : TrueTypeFontCache(fs, pixels), font_desc(std::move(font))
{
this->SetFontSize(pixels);
@ -345,6 +259,90 @@ public:
new CoreTextFontCache(fs, std::move(font_ref), GetFontCacheFontSize(fs));
}
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) override
{
/* Determine fallback font using CoreText. This uses the language isocode
* to find a suitable font. CoreText is available from 10.5 onwards. */
std::string lang;
if (language_isocode == "zh_TW") {
/* Traditional Chinese */
lang = "zh-Hant";
} else if (language_isocode == "zh_CN") {
/* Simplified Chinese */
lang = "zh-Hans";
} else {
/* Just copy the first part of the isocode. */
lang = language_isocode.substr(0, language_isocode.find('_'));
}
/* Create a font descriptor matching the wanted language and latin (english) glyphs.
* Can't use CFAutoRelease here for everything due to the way the dictionary has to be created. */
CFStringRef lang_codes[2];
lang_codes[0] = CFStringCreateWithCString(kCFAllocatorDefault, lang.c_str(), kCFStringEncodingUTF8);
lang_codes[1] = CFSTR("en");
CFArrayRef lang_arr = CFArrayCreate(kCFAllocatorDefault, (const void **)lang_codes, lengthof(lang_codes), &kCFTypeArrayCallBacks);
CFAutoRelease<CFDictionaryRef> lang_attribs(CFDictionaryCreate(kCFAllocatorDefault, const_cast<const void **>(reinterpret_cast<const void *const *>(&kCTFontLanguagesAttribute)), (const void **)&lang_arr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
CFAutoRelease<CTFontDescriptorRef> lang_desc(CTFontDescriptorCreateWithAttributes(lang_attribs.get()));
CFRelease(lang_arr);
CFRelease(lang_codes[0]);
/* Get array of all font descriptors for the wanted language. */
CFAutoRelease<CFSetRef> mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast<const void **>(reinterpret_cast<const void *const *>(&kCTFontLanguagesAttribute)), 1, &kCFTypeSetCallBacks));
CFAutoRelease<CFArrayRef> descs(CTFontDescriptorCreateMatchingFontDescriptors(lang_desc.get(), mandatory_attribs.get()));
bool result = false;
for (int tries = 0; tries < 2; tries++) {
for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()); i++) {
CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i);
/* Get font traits. */
CFAutoRelease<CFDictionaryRef> traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute));
CTFontSymbolicTraits symbolic_traits;
CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait), kCFNumberIntType, &symbolic_traits);
/* Skip symbol fonts and vertical fonts. */
if ((symbolic_traits & kCTFontClassMaskTrait) == (CTFontStylisticClass)kCTFontSymbolicClass || (symbolic_traits & kCTFontVerticalTrait)) continue;
/* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */
if (symbolic_traits & kCTFontBoldTrait) continue;
/* Select monospaced fonts if asked for. */
if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue;
/* Get font name. */
char buffer[128];
CFAutoRelease<CFStringRef> font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute));
CFStringGetCString(font_name.get(), buffer, std::size(buffer), kCFStringEncodingUTF8);
/* Serif fonts usually look worse on-screen with only small
* font sizes. As such, we try for a sans-serif font first.
* If we can't find one in the first try, try all fonts. */
if (tries == 0 && (symbolic_traits & kCTFontClassMaskTrait) != (CTFontStylisticClass)kCTFontSansSerifClass) continue;
/* There are some special fonts starting with an '.' and the last
* resort font that aren't usable. Skip them. */
std::string_view name{buffer};
if (name.starts_with(".") || name.starts_with("LastResort")) continue;
/* Save result. */
callback->SetFontNames(settings, name);
if (!callback->FindMissingGlyphs()) {
Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name);
result = true;
break;
}
}
}
if (!result) {
/* For some OS versions, the font 'Arial Unicode MS' does not report all languages it
* supports. If we didn't find any other font, just try it, maybe we get lucky. */
callback->SetFontNames(settings, "Arial Unicode MS");
result = !callback->FindMissingGlyphs();
}
callback->FindMissingGlyphs();
return result;
}
private:
static CTFontDescriptorRef LoadFontFromFile(const std::string &font_name)
{

View File

@ -12,6 +12,7 @@ add_files(
add_files(
font_unix.cpp
font_unix.h
CONDITION Fontconfig_FOUND
)

View File

@ -11,17 +11,17 @@
#include "../../misc/autorelease.hpp"
#include "../../debug.h"
#include "../../fontdetection.h"
#include "../../fontcache.h"
#include "../../string_func.h"
#include "../../strings_func.h"
#include "font_unix.h"
#include <fontconfig/fontconfig.h>
#include "../../safeguards.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include "../../safeguards.h"
extern FT_Library _ft_library;
/**
@ -61,6 +61,12 @@ static std::tuple<std::string, std::string> SplitFontFamilyAndStyle(std::string_
return { std::string(font_name.substr(0, separator)), std::string(font_name.substr(begin)) };
}
/**
* Load a freetype font face with the given font name.
* @param font_name The name of the font to load.
* @param face The face that has been found.
* @return The error we encountered.
*/
FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face)
{
FT_Error err = FT_Err_Cannot_Open_Resource;
@ -117,7 +123,7 @@ FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face)
return err;
}
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback)
bool FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback)
{
bool ret = false;

View File

@ -0,0 +1,26 @@
/*
* This file is part of OpenTTD.
* 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.
* 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.
* 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/>.
*/
/** @file font_unix.h Functions related to detecting/finding the right font. */
#ifndef FONT_UNIX_H
#define FONT_UNIX_H
#ifdef WITH_FONTCONFIG
#include "../../fontcache.h"
#include <ft2build.h>
#include FT_FREETYPE_H
FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face);
bool FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback);
#endif /* WITH_FONTCONFIG */
#endif /* FONT_UNIX_H */

View File

@ -15,7 +15,6 @@
#include "../../fileio_func.h"
#include "../../fontcache.h"
#include "../../fontcache/truetypefontcache.h"
#include "../../fontdetection.h"
#include "../../library_loader.h"
#include "../../string_func.h"
#include "../../strings_func.h"
@ -85,32 +84,6 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
return 0; // stop enumerating
}
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback)
{
Debug(fontcache, 1, "Trying fallback fonts");
EFCParam langInfo;
std::wstring lang = OTTD2FS(language_isocode.substr(0, language_isocode.find('_')));
if (GetLocaleInfoEx(lang.c_str(), LOCALE_FONTSIGNATURE, reinterpret_cast<LPWSTR>(&langInfo.locale), sizeof(langInfo.locale) / sizeof(wchar_t)) == 0) {
/* Invalid isocode or some other mysterious error, can't determine fallback font. */
Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode);
return false;
}
langInfo.settings = settings;
langInfo.callback = callback;
LOGFONT font;
/* Enumerate all fonts. */
font.lfCharSet = DEFAULT_CHARSET;
font.lfFaceName[0] = '\0';
font.lfPitchAndFamily = 0;
HDC dc = GetDC(nullptr);
int ret = EnumFontFamiliesEx(dc, &font, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&langInfo, 0);
ReleaseDC(nullptr, dc);
return ret == 0;
}
/**
* Create a new Win32FontCache.
* @param fs The font size that is going to be cached.
@ -335,6 +308,31 @@ public:
LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font);
}
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) override
{
Debug(fontcache, 1, "Trying fallback fonts");
EFCParam langInfo;
std::wstring lang = OTTD2FS(language_isocode.substr(0, language_isocode.find('_')));
if (GetLocaleInfoEx(lang.c_str(), LOCALE_FONTSIGNATURE, reinterpret_cast<LPWSTR>(&langInfo.locale), sizeof(langInfo.locale) / sizeof(wchar_t)) == 0) {
/* Invalid isocode or some other mysterious error, can't determine fallback font. */
Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode);
return false;
}
langInfo.settings = settings;
langInfo.callback = callback;
LOGFONT font;
/* Enumerate all fonts. */
font.lfCharSet = DEFAULT_CHARSET;
font.lfFaceName[0] = '\0';
font.lfPitchAndFamily = 0;
HDC dc = GetDC(nullptr);
int ret = EnumFontFamiliesEx(dc, &font, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&langInfo, 0);
ReleaseDC(nullptr, dc);
return ret == 0;
}
private:
static void LoadWin32Font(FontSize fs, const LOGFONT &logfont, uint size, std::string_view font_name)
{

View File

@ -17,7 +17,6 @@
#include "newgrf_text.h"
#include "fileio_func.h"
#include "signs_base.h"
#include "fontdetection.h"
#include "error.h"
#include "error_func.h"
#include "strings_func.h"
@ -2365,7 +2364,7 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
_fcsettings.mono.os_handle = nullptr;
_fcsettings.medium.os_handle = nullptr;
bad_font = !SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher);
bad_font = !FontProviderManager::SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher);
_fcsettings = std::move(backup);