Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/console_cmds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -46,6 +48,7 @@
#include "company_cmd.h"
#include "misc_cmd.h"

#include <charconv>
#include <sstream>

#include "safeguards.h"
Expand Down Expand Up @@ -2179,6 +2182,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);

Check notice

Code scanning / CodeQL

Unused local variable

Variable _ is not used.
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) {
Expand Down Expand Up @@ -2751,6 +2800,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);
Expand Down
61 changes: 61 additions & 0 deletions src/fontcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,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; }
Expand Down
55 changes: 55 additions & 0 deletions src/fontdetection.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
115 changes: 85 additions & 30 deletions src/os/macosx/font_osx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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. */
Expand All @@ -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
Expand Down Expand Up @@ -371,3 +386,43 @@ void LoadCoreTextFont(FontSize fs)

new CoreTextFontCache(fs, std::move(font_ref), settings->size);
}

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;
Loading