1
0
Fork 0
pull/12790/merge
Peter Nelson 2024-12-30 02:37:30 -08:00 committed by GitHub
commit 5ea5bc6727
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 403 additions and 38 deletions

View File

@ -24,6 +24,8 @@
#include "fios.h"
#include "fileio_func.h"
#include "fontcache.h"
#include "fontdetection.h"
#include "language.h"
#include "screenshot.h"
#include "genworld.h"
#include "strings_func.h"
@ -46,6 +48,7 @@
#include "company_cmd.h"
#include "misc_cmd.h"
#include <charconv>
#include <sstream>
#include "safeguards.h"
@ -2225,6 +2228,52 @@ DEF_CONSOLE_CMD(ConContent)
}
#endif /* defined(WITH_ZLIB) */
/* List all the fonts available via console */
DEF_CONSOLE_CMD(ConListFonts)
{
if (argc == 0) {
IConsolePrint(CC_HELP, "List all fonts.");
return true;
}
FontSearcher *fs = FontSearcher::GetFontSearcher();
if (fs == nullptr) {
IConsolePrint(CC_ERROR, "No font searcher exists.");
return true;
}
if (argc == 1) {
auto families = fs->ListFamilies(_current_language->isocode, _current_language->winlangid);
int i = 0;
for (const std::string_view &family : families) {
IConsolePrint(CC_DEFAULT, "{}) {}", i, family);
++i;
}
} else if (argc == 2) {
std::string family = argv[1];
/* If argv is a number treat it as an index into the list of fonts, which we need to get again... */
int index;
auto [_, err] = std::from_chars(family.data(), family.data() + family.size(), index, 10);
if (err == std::errc()) {
auto families = fs->ListFamilies(_current_language->isocode, _current_language->winlangid);
std::sort(std::begin(families), std::end(families));
if (IsInsideMM(index, 0, families.size())) family = families[index];
}
auto styles = fs->ListStyles(_current_language->isocode, _current_language->winlangid, family);
int i = 0;
for (const FontFamily &font : styles) {
IConsolePrint(CC_DEFAULT, "{}) {}, {}", i, font.family, font.style);
i++;
}
}
return true;
}
DEF_CONSOLE_CMD(ConFont)
{
if (argc == 0) {
@ -2807,6 +2856,7 @@ void IConsoleStdLibRegister()
IConsole::CmdRegister("saveconfig", ConSaveConfig);
IConsole::CmdRegister("ls", ConListFiles);
IConsole::CmdRegister("list_saves", ConListFiles);
IConsole::CmdRegister("list_fonts", ConListFonts);
IConsole::CmdRegister("list_scenarios", ConListScenarios);
IConsole::CmdRegister("list_heightmaps", ConListHeightmaps);
IConsole::CmdRegister("cd", ConChangeDirectory);

View File

@ -250,6 +250,67 @@ void UninitFontCache()
#endif /* WITH_FREETYPE */
}
bool FontFamilySorter(const FontFamily &a, const FontFamily &b)
{
int r = StrNaturalCompare(a.family, b.family);
if (r == 0) r = (a.weight - b.weight);
if (r == 0) r = (a.slant - b.slant);
if (r == 0) r = StrNaturalCompare(a.style, b.style);
return r < 0;
}
/**
* Register the FontSearcher instance. There can be only one font searcher, which depends on platform.
*/
FontSearcher::FontSearcher()
{
FontSearcher::instance = this;
}
/**
* Deregister this FontSearcher.
*/
FontSearcher::~FontSearcher()
{
FontSearcher::instance = nullptr;
}
std::vector<std::string_view> FontSearcher::ListFamilies(const std::string &language_isocode, int winlangid)
{
std::vector<std::string_view> families;
if (this->cached_language_isocode != language_isocode || this->cached_winlangid != winlangid) {
this->UpdateCachedFonts(language_isocode, winlangid);
this->cached_language_isocode = language_isocode;
this->cached_winlangid = winlangid;
}
for (const FontFamily &ff : this->cached_fonts) {
if (std::find(std::begin(families), std::end(families), ff.family) != std::end(families)) continue;
families.push_back(ff.family);
}
return families;
}
std::vector<std::reference_wrapper<const FontFamily>> FontSearcher::ListStyles(const std::string &language_isocode, int winlangid, std::string_view family)
{
std::vector<std::reference_wrapper<const FontFamily>> styles;
if (this->cached_language_isocode != language_isocode || this->cached_winlangid != winlangid) {
this->UpdateCachedFonts(language_isocode, winlangid);
this->cached_language_isocode = language_isocode;
this->cached_winlangid = winlangid;
}
for (const FontFamily &ff : this->cached_fonts) {
if (ff.family != family) continue;
styles.emplace_back(std::ref(ff));
}
return styles;
}
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA)
bool SetFallbackFont(FontCacheSettings *, const std::string &, int, MissingGlyphSearcher *) { return false; }

View File

@ -39,4 +39,59 @@ FT_Error GetFontByFaceName(const char *font_name, FT_Face *face);
*/
bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, int winlangid, class MissingGlyphSearcher *callback);
struct FontFamily {
std::string family;
std::string style;
int32_t slant;
int32_t weight;
FontFamily(std::string_view family, std::string_view style, int32_t slant, int32_t weight) : family(family), style(style), slant(slant), weight(weight) {}
};
bool FontFamilySorter(const FontFamily &a, const FontFamily &b);
class FontSearcher {
public:
FontSearcher();
virtual ~FontSearcher();
/**
* Get the active FontSearcher instance.
* @return FontSearcher instance, or nullptr if not present.
*/
static inline FontSearcher *GetFontSearcher() { return FontSearcher::instance; }
/**
* Update cached font information.
* @param language_isocode the language, e.g. en_GB.
* @param winlangid the language ID windows style.
*/
virtual void UpdateCachedFonts(const std::string &language_isocode, int winlangid) = 0;
/**
* List available fonts.
* @param language_isocode the language, e.g. en_GB.
* @param winlangid the language ID windows style.
* @return vector containing font family names.
*/
std::vector<std::string_view> ListFamilies(const std::string &language_isocode, int winlangid);
/**
* List available styles for a font family.
* @param language_isocode the language, e.g. en_GB.
* @param winlangid the language ID windows style.
* @param font_family The font family to list.
* @return vector containing style information for the family.
*/
std::vector<std::reference_wrapper<const FontFamily>> ListStyles(const std::string &language_isocode, int winlangid, std::string_view family);
protected:
std::vector<FontFamily> cached_fonts;
std::string cached_language_isocode;
int cached_winlangid;
private:
static inline FontSearcher *instance = nullptr;
};
#endif

View File

@ -24,7 +24,7 @@
#include "safeguards.h"
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, int, MissingGlyphSearcher *callback)
static void EnumerateCoreFextFonts(const std::string &language_isocode, int ntries, std::function<bool(int, CTFontDescriptorRef, CTFontSymbolicTraits)> enum_func)
{
/* Determine fallback font using CoreText. This uses the language isocode
* to find a suitable font. CoreText is available from 10.5 onwards. */
@ -55,9 +55,12 @@ bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_is
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++) {
/* Nothing to see here. */
if (descs == nullptr) return;
CFIndex count = CFArrayGetCount(descs.get());
for (int tries = 0; tries < ntries; tries++) {
for (CFIndex i = 0; i < count; i++) {
CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i);
/* Get font traits. */
@ -67,34 +70,46 @@ bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_is
/* 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 name[128];
CFAutoRelease<CFStringRef> font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute));
CFStringGetCString(font_name.get(), name, lengthof(name), 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. */
if (name[0] == '.' || strncmp(name, "LastResort", 10) == 0) continue;
/* Save result. */
callback->SetFontNames(settings, name);
if (!callback->FindMissingGlyphs()) {
Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name);
result = true;
break;
}
bool continue_enumerating = enum_func(tries, font, symbolic_traits);
if (!continue_enumerating) return;
}
}
}
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, int, MissingGlyphSearcher *callback)
{
bool result = false;
EnumerateCoreFextFonts(language_isocode, 2, [&settings, &language_isocode, &callback, &result](int tries, CTFontDescriptorRef font, CTFontSymbolicTraits symbolic_traits) {
/* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */
if (symbolic_traits & kCTFontBoldTrait) return true;
/* Select monospaced fonts if asked for. */
if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) return true;
/* Get font name. */
char name[128];
CFAutoRelease<CFStringRef> font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute));
CFStringGetCString(font_name.get(), name, lengthof(name), 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) return true;
/* There are some special fonts starting with an '.' and the last
* resort font that aren't usable. Skip them. */
if (name[0] == '.' || strncmp(name, "LastResort", 10) == 0) return true;
/* Save result. */
callback->SetFontNames(settings, name);
if (!callback->FindMissingGlyphs()) {
Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name);
result = true;
return false;
}
return true;
});
if (!result) {
/* For some OS versions, the font 'Arial Unicode MS' does not report all languages it
@ -371,3 +386,43 @@ void LoadCoreTextFont(FontSize fs)
new CoreTextFontCache(fs, std::move(font_ref), GetFontCacheFontSize(fs));
}
class CoreTextFontSearcher : public FontSearcher {
public:
void UpdateCachedFonts(const std::string &language_isocode, int winlangid) override;
};
void CoreTextFontSearcher::UpdateCachedFonts(const std::string &language_isocode, int)
{
this->cached_fonts.clear();
EnumerateCoreFextFonts(language_isocode, 1, [this](int, CTFontDescriptorRef font, CTFontSymbolicTraits) {
/* Get font name. */
char family[128];
CFAutoRelease<CFStringRef> family_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute));
CFStringGetCString(family_name.get(), family, std::size(family), kCFStringEncodingUTF8);
char style[128];
CFAutoRelease<CFStringRef> style_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute));
CFStringGetCString(style_name.get(), style, std::size(style), kCFStringEncodingUTF8);
/* Don't add duplicate fonts. */
std::string_view sv_family = family;
std::string_view sv_style = style;
if (std::any_of(std::begin(this->cached_fonts), std::end(this->cached_fonts), [&sv_family, &sv_style](const FontFamily &ff) { return ff.family == sv_family && ff.style == sv_style; })) return true;
CFAutoRelease<CFDictionaryRef> traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute));
float weight = 0.0f;
CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontWeightTrait), kCFNumberFloatType, &weight);
float slant = 0.0f;
CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSlantTrait), kCFNumberFloatType, &slant);
this->cached_fonts.emplace_back(sv_family, sv_style, static_cast<int>(slant * 100), static_cast<int>(weight * 100));
return true;
});
std::sort(std::begin(this->cached_fonts), std::end(this->cached_fonts), FontFamilySorter);
}
CoreTextFontSearcher _coretextfs_instance;

View File

@ -39,6 +39,19 @@ static std::tuple<std::string, std::string> SplitFontFamilyAndStyle(std::string_
return { std::string(font_name.substr(0, separator)), std::string(font_name.substr(begin)) };
}
/**
* Get language string for FontConfig pattern matching.
* @param language_isocode Language's ISO code.
* @return Language code for FontConfig.
*/
static std::string GetFontConfigLanguage(const std::string &language_isocode)
{
/* Fontconfig doesn't handle full language isocodes, only the part
* before the _ of e.g. en_GB is used, so "remove" everything after
* the _. */
return fmt::format(":lang={}", language_isocode.substr(0, language_isocode.find('_')));
}
FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
{
FT_Error err = FT_Err_Cannot_Open_Resource;
@ -107,10 +120,7 @@ bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_is
auto fc_instance = FcConfigReference(nullptr);
assert(fc_instance != nullptr);
/* Fontconfig doesn't handle full language isocodes, only the part
* before the _ of e.g. en_GB is used, so "remove" everything after
* the _. */
std::string lang = fmt::format(":lang={}", language_isocode.substr(0, language_isocode.find('_')));
std::string lang = GetFontConfigLanguage(language_isocode);
/* First create a pattern to match the wanted language. */
FcPattern *pat = FcNameParse((const FcChar8 *)lang.c_str());
@ -180,3 +190,69 @@ bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_is
FcConfigDestroy(fc_instance);
return ret;
}
/**
* FontConfig implementation of FontSearcher.
*/
class FontConfigFontSearcher : public FontSearcher {
public:
void UpdateCachedFonts(const std::string &language_isocode, int winlangid) override;
};
void FontConfigFontSearcher::UpdateCachedFonts(const std::string &language_isocode, int)
{
this->cached_fonts.clear();
if (!FcInit()) return;
FcConfig *fc_instance = FcConfigReference(nullptr);
assert(fc_instance != nullptr);
std::string lang = GetFontConfigLanguage(language_isocode);
/* First create a pattern to match the wanted language. */
FcPattern *pat = FcNameParse(reinterpret_cast<const FcChar8 *>(lang.c_str()));
/* We want to know this attributes. */
FcObjectSet *os = FcObjectSetCreate();
FcObjectSetAdd(os, FC_FAMILY);
FcObjectSetAdd(os, FC_STYLE);
FcObjectSetAdd(os, FC_SLANT);
FcObjectSetAdd(os, FC_WEIGHT);
/* Get the list of filenames matching the wanted language. */
FcFontSet *fs = FcFontList(nullptr, pat, os);
/* We don't need these anymore. */
FcObjectSetDestroy(os);
FcPatternDestroy(pat);
if (fs != nullptr) {
this->cached_fonts.reserve(fs->nfont);
for (const FcPattern *font : std::span(fs->fonts, fs->nfont)) {
FcChar8 *family;
FcChar8 *style;
int32_t slant;
int32_t weight;
if (FcPatternGetString(font, FC_FAMILY, 0, &family) != FcResultMatch) continue;
if (FcPatternGetString(font, FC_STYLE, 0, &style) != FcResultMatch) continue;
if (FcPatternGetInteger(font, FC_SLANT, 0, &slant) != FcResultMatch) continue;
if (FcPatternGetInteger(font, FC_WEIGHT, 0, &weight) != FcResultMatch) continue;
/* Don't add duplicate fonts. */
std::string_view sv_family = reinterpret_cast<const char *>(family);
std::string_view sv_style = reinterpret_cast<const char *>(style);
if (std::any_of(std::begin(this->cached_fonts), std::end(this->cached_fonts), [&sv_family, &sv_style](const FontFamily &ff) { return ff.family == sv_family && ff.style == sv_style; })) continue;
this->cached_fonts.emplace_back(sv_family, sv_style, slant, weight);
}
/* Clean up the list of filenames. */
FcFontSetDestroy(fs);
}
FcConfigDestroy(fc_instance);
std::sort(std::begin(this->cached_fonts), std::end(this->cached_fonts), FontFamilySorter);
}
static FontConfigFontSearcher _fcfs_instance;

View File

@ -34,15 +34,14 @@
struct EFCParam {
FontCacheSettings *settings;
LOCALESIGNATURE locale;
LOCALESIGNATURE locale;
MissingGlyphSearcher *callback;
std::vector<std::wstring> fonts;
bool Add(const std::wstring_view &font)
{
for (const auto &entry : this->fonts) {
if (font.compare(entry) == 0) return false;
}
if (font.starts_with('@')) return false;
if (std::find(std::begin(this->fonts), std::end(this->fonts), font) != std::end(this->fonts)) return false;
this->fonts.emplace_back(font);
@ -99,7 +98,6 @@ bool SetFallbackFont(FontCacheSettings *settings, const std::string &, int winla
return ret == 0;
}
/**
* Create a new Win32FontCache.
* @param fs The font size that is going to be cached.
@ -380,3 +378,73 @@ void LoadWin32Font(FontSize fs)
LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font_name);
}
/**
* Win32 implementation of FontSearcher.
*/
class Win32FontSearcher : public FontSearcher {
public:
void UpdateCachedFonts(const std::string &language_isocode, int winlangid) override;
};
/**
* State passed between EnumFontFamiliesEx and our list fonts callbacks.
*/
struct EFCListFontsParam : EFCParam {
std::vector<FontFamily> &fonts;
explicit EFCListFontsParam(std::vector<FontFamily> &fonts) : fonts(fonts) {}
};
static int CALLBACK ListStylesFontCallback(ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *, DWORD, LPARAM lParam)
{
EFCListFontsParam &info = *reinterpret_cast<EFCListFontsParam *>(lParam);
LOGFONT &lf = lpelfe->elfLogFont;
info.fonts.emplace_back(FS2OTTD(lf.lfFaceName), FS2OTTD(lpelfe->elfStyle), lf.lfItalic, lf.lfWeight);
return 1;
}
static int CALLBACK ListFamiliesFontCallback(ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam)
{
EFCListFontsParam &info = *reinterpret_cast<EFCListFontsParam *>(lParam);
/* Only use TrueType fonts */
if (!(type & TRUETYPE_FONTTYPE)) return 1;
/* Skip duplicates */
if (!info.Add(lpelfe->elfFullName)) return 1;
/* Don't use SYMBOL fonts */
if (lpelfe->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1;
/* The font has to have at least one of the supported locales to be usable. */
if ((metric->ntmFontSig.fsCsb[0] & info.locale.lsCsbSupported[0]) == 0 && (metric->ntmFontSig.fsCsb[1] & info.locale.lsCsbSupported[1]) == 0) return 1;
LOGFONT &lf = lpelfe->elfLogFont;
HDC dc = GetDC(nullptr);
EnumFontFamiliesEx(dc, &lf, (FONTENUMPROC)&ListStylesFontCallback, reinterpret_cast<LPARAM>(&info), 0);
ReleaseDC(nullptr, dc);
return 1;
}
void Win32FontSearcher::UpdateCachedFonts(const std::string &, int winlangid)
{
EFCListFontsParam info(this->cached_fonts);
this->cached_fonts.clear();
if (GetLocaleInfo(MAKELCID(winlangid, SORT_DEFAULT), LOCALE_FONTSIGNATURE, reinterpret_cast<LPTSTR>(&info.locale), sizeof(info.locale) / sizeof(wchar_t)) == 0) {
/* Invalid langid or some other mysterious error, can't determine fallback font. */
Debug(fontcache, 1, "Can't get locale info for fallback font (langid=0x{:x})", winlangid);
return;
}
LOGFONT lf{};
lf.lfCharSet = DEFAULT_CHARSET;
HDC dc = GetDC(nullptr);
EnumFontFamiliesEx(dc, &lf, (FONTENUMPROC)&ListFamiliesFontCallback, reinterpret_cast<LPARAM>(&info), 0);
ReleaseDC(nullptr, dc);
}
static Win32FontSearcher _win32fs_instance;