mirror of
https://github.com/OpenTTD/OpenTTD
synced 2025-12-10 06:52:05 +01:00
Change: Support side-by-side fallback FontCaches instead of hierarchical. (#13303)
The text layouter system can now support using different fonts for different glyphs, including mixing scalable and sprite glyphs.
This commit is contained in:
@@ -2319,6 +2319,18 @@ static bool ConContent(std::span<std::string_view> argv)
|
||||
}
|
||||
#endif /* defined(WITH_ZLIB) */
|
||||
|
||||
/**
|
||||
* Get string representation of a font load reason.
|
||||
* @param load_reason The font load reason.
|
||||
* @return String representation.
|
||||
*/
|
||||
static std::string_view FontLoadReasonToName(FontLoadReason load_reason)
|
||||
{
|
||||
static const std::string_view LOAD_REASON_TO_NAME[] = { "default", "configured", "language" };
|
||||
static_assert(std::size(LOAD_REASON_TO_NAME) == to_underlying(FontLoadReason::End));
|
||||
return LOAD_REASON_TO_NAME[to_underlying(load_reason)];
|
||||
}
|
||||
|
||||
static bool ConFont(std::span<std::string_view> argv)
|
||||
{
|
||||
if (argv.empty()) {
|
||||
@@ -2367,17 +2379,18 @@ static bool ConFont(std::span<std::string_view> argv)
|
||||
SetFont(argfs, font, size);
|
||||
}
|
||||
|
||||
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
|
||||
FontCache *fc = FontCache::Get(fs);
|
||||
FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
|
||||
/* Make sure all non sprite fonts are loaded. */
|
||||
if (!setting->font.empty() && !fc->HasParent()) {
|
||||
FontCache::LoadFontCaches(fs);
|
||||
fc = FontCache::Get(fs);
|
||||
}
|
||||
IConsolePrint(CC_DEFAULT, "{} font:", FontSizeToName(fs));
|
||||
IConsolePrint(CC_DEFAULT, "Currently active: \"{}\", size {}", fc->GetFontName(), fc->GetFontSize());
|
||||
IConsolePrint(CC_DEFAULT, "Requested: \"{}\", size {}", setting->font, setting->size);
|
||||
IConsolePrint(CC_INFO, "Configured fonts:");
|
||||
for (uint i = 0; FontSize fs : FONTSIZES_ALL) {
|
||||
const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
|
||||
IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\", size {}", i, FontSizeToName(fs), setting->font, setting->size);
|
||||
++i;
|
||||
}
|
||||
|
||||
IConsolePrint(CC_INFO, "Currently active fonts:");
|
||||
for (uint i = 0; const auto &fc : FontCache::Get()) {
|
||||
if (fc == nullptr) continue;
|
||||
IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\" size {} [{}]", i, FontSizeToName(fc->GetSize()), fc->GetFontName(), fc->GetFontSize(), FontLoadReasonToName(fc->GetFontLoadReason()));
|
||||
++i;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
/** @file fontcache.cpp Cache for characters from fonts. */
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "core/string_consumer.hpp"
|
||||
#include "fontcache.h"
|
||||
#include "blitter/factory.hpp"
|
||||
#include "gfx_layout.h"
|
||||
@@ -17,13 +19,14 @@
|
||||
#include "viewport_func.h"
|
||||
#include "window_func.h"
|
||||
#include "fileio_func.h"
|
||||
#include "zoom_func.h"
|
||||
|
||||
#include "safeguards.h"
|
||||
|
||||
/** Default unscaled heights for the different sizes of fonts. */
|
||||
/* static */ const int FontCache::DEFAULT_FONT_HEIGHT[FS_END] = {10, 6, 18, 10};
|
||||
/** Default unscaled ascenders for the different sizes of fonts. */
|
||||
/* static */ const int FontCache::DEFAULT_FONT_ASCENDER[FS_END] = {8, 5, 15, 8};
|
||||
/* static */ const int FontCache::DEFAULT_FONT_ASCENDER[FS_END] = {8, 6, 15, 8};
|
||||
|
||||
FontCacheSettings _fcsettings;
|
||||
|
||||
@@ -33,10 +36,10 @@ FontCacheSettings _fcsettings;
|
||||
* @param fonttype Font type requested.
|
||||
* @return FontCache of the font if loaded, or nullptr.
|
||||
*/
|
||||
/* static */ std::unique_ptr<FontCache> FontProviderManager::LoadFont(FontSize fs, FontType fonttype)
|
||||
/* static */ std::unique_ptr<FontCache> FontProviderManager::LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span<const std::byte> os_handle)
|
||||
{
|
||||
for (auto &provider : FontProviderManager::GetProviders()) {
|
||||
auto fc = provider->LoadFont(fs, fonttype);
|
||||
auto fc = provider->LoadFont(fs, fonttype, search, font_name, os_handle);
|
||||
if (fc != nullptr) return fc;
|
||||
}
|
||||
|
||||
@@ -52,10 +55,10 @@ FontCacheSettings _fcsettings;
|
||||
* @param callback The function to call to check for missing glyphs.
|
||||
* @return true if a font has been set, false otherwise.
|
||||
*/
|
||||
/* static */ bool FontProviderManager::FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback)
|
||||
/* static */ bool FontProviderManager::FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback)
|
||||
{
|
||||
return std::ranges::any_of(FontProviderManager::GetProviders(),
|
||||
[&](auto *provider) { return provider->FindFallbackFont(settings, language_isocode, callback); });
|
||||
[&](auto *provider) { return provider->FindFallbackFont(language_isocode, fontsizes, callback); });
|
||||
}
|
||||
|
||||
int FontCache::GetDefaultFontHeight(FontSize fs)
|
||||
@@ -63,21 +66,33 @@ int FontCache::GetDefaultFontHeight(FontSize fs)
|
||||
return FontCache::DEFAULT_FONT_HEIGHT[fs];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the font name of a given font size.
|
||||
* @param fs The font size to look up.
|
||||
* @return The font name.
|
||||
*/
|
||||
std::string FontCache::GetName(FontSize fs)
|
||||
/* static */ void FontCache::UpdateCharacterHeight(FontSize fs)
|
||||
{
|
||||
FontCache *fc = FontCache::Get(fs);
|
||||
if (fc != nullptr) {
|
||||
return fc->GetFontName();
|
||||
} else {
|
||||
return "[NULL]";
|
||||
FontMetrics &metrics = FontCache::metrics[fs];
|
||||
|
||||
int ascender = 0;
|
||||
int descender = 0;
|
||||
|
||||
for (const auto &fc : FontCache::caches) {
|
||||
if (fc == nullptr || fc->fs != fs) continue;
|
||||
ascender = std::max(ascender, fc->ascender);
|
||||
descender = std::min(descender, fc->descender);
|
||||
}
|
||||
|
||||
if (ascender == 0 && descender == 0) {
|
||||
/* It's possible that no font is loaded yet, in which case use default values. */
|
||||
ascender = ScaleGUITrad(FontCache::DEFAULT_FONT_ASCENDER[fs]);
|
||||
descender = ScaleGUITrad(FontCache::DEFAULT_FONT_ASCENDER[fs] - FontCache::DEFAULT_FONT_HEIGHT[fs]);
|
||||
}
|
||||
|
||||
metrics.height = ascender - descender;
|
||||
metrics.baseline = ascender;
|
||||
}
|
||||
|
||||
int FontCache::GetGlyphYOffset()
|
||||
{
|
||||
return FontCache::GetFontBaseline(this->fs) - this->ascender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get height of a character for a given font size.
|
||||
@@ -86,20 +101,22 @@ std::string FontCache::GetName(FontSize fs)
|
||||
*/
|
||||
int GetCharacterHeight(FontSize size)
|
||||
{
|
||||
return FontCache::Get(size)->GetHeight();
|
||||
uint height = FontCache::GetCharacterHeight(size);
|
||||
if (height == 0) height = ScaleGUITrad(FontCache::GetDefaultFontHeight(FS_MONO));
|
||||
return height;
|
||||
}
|
||||
|
||||
|
||||
/* static */ std::array<std::unique_ptr<FontCache>, FS_END> FontCache::caches{};
|
||||
/* static */ FontCache::FontCaches FontCache::caches;
|
||||
/* static */ std::array<FontCache::FontMetrics, FS_END> FontCache::metrics{};
|
||||
/* static */ std::array<FontIndex, FS_END> FontCache::default_font_index{};
|
||||
|
||||
/**
|
||||
* Initialise font caches with the base sprite font cache for all sizes.
|
||||
*/
|
||||
/* static */ void FontCache::InitializeFontCaches()
|
||||
{
|
||||
for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) {
|
||||
if (FontCache::Get(fs) != nullptr) continue;
|
||||
FontCache::Register(FontProviderManager::LoadFont(fs, FontType::Sprite));
|
||||
for (FontSize fs : FONTSIZES_ALL) {
|
||||
UpdateCharacterHeight(fs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,19 +147,13 @@ void SetFont(FontSize fontsize, const std::string &font, uint size)
|
||||
if (!changed) return;
|
||||
|
||||
if (fontsize != FS_MONO) {
|
||||
/* Try to reload only the modified font. */
|
||||
FontCacheSettings backup = _fcsettings;
|
||||
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
|
||||
if (fs == fontsize) continue;
|
||||
FontCache *fc = FontCache::Get(fs);
|
||||
GetFontCacheSubSetting(fs)->font = fc->HasParent() ? fc->GetFontName() : "";
|
||||
}
|
||||
/* Check if fallback fonts are needed. */
|
||||
CheckForMissingGlyphs();
|
||||
_fcsettings = std::move(backup);
|
||||
} else {
|
||||
FontCache::LoadFontCaches(fontsize);
|
||||
}
|
||||
|
||||
FontCache::UpdateCharacterHeight(fontsize);
|
||||
LoadStringWidthTable(fontsize);
|
||||
UpdateAllVirtCoords();
|
||||
ReInitAllWindows(true);
|
||||
@@ -156,7 +167,7 @@ void SetFont(FontSize fontsize, const std::string &font, uint size)
|
||||
*/
|
||||
static bool IsDefaultFont(const FontCacheSubSetting &setting)
|
||||
{
|
||||
return setting.font.empty() && setting.os_handle == nullptr;
|
||||
return setting.font.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,7 +204,7 @@ static std::string GetDefaultTruetypeFont(FontSize fs)
|
||||
* @param fs Font size.
|
||||
* @return Full path of default font file.
|
||||
*/
|
||||
static std::string GetDefaultTruetypeFontFile([[maybe_unused]] FontSize fs)
|
||||
std::string GetDefaultTruetypeFontFile([[maybe_unused]] FontSize fs)
|
||||
{
|
||||
#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
|
||||
/* Find font file. */
|
||||
@@ -216,18 +227,54 @@ std::string GetFontCacheFontName(FontSize fs)
|
||||
return GetDefaultTruetypeFontFile(fs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a FontCache for its font size.
|
||||
* @param fc FontCache to register.
|
||||
*/
|
||||
/* static */ void FontCache::Register(std::unique_ptr<FontCache> &&fc)
|
||||
/* static */ void FontCache::Register(std::unique_ptr<FontCache> &&fc, FontLoadReason load_reason)
|
||||
{
|
||||
if (fc == nullptr) return;
|
||||
|
||||
FontSize fs = fc->fs;
|
||||
|
||||
fc->parent = std::move(FontCache::caches[fs]);
|
||||
FontCache::caches[fs] = std::move(fc);
|
||||
/* Find an empty font cache slot. */
|
||||
auto it = std::find(std::begin(FontCache::caches), std::end(FontCache::caches), nullptr);
|
||||
if (it == std::end(FontCache::caches)) it = FontCache::caches.insert(it, nullptr);
|
||||
|
||||
/* Set up our font index and make us the default font cache for this font size. */
|
||||
fc->font_index = static_cast<FontIndex>(std::distance(std::begin(FontCache::caches), it));
|
||||
fc->load_reason = load_reason;
|
||||
FontCache::default_font_index[fs] = fc->font_index;
|
||||
|
||||
/* Register this font cache in the slot. */
|
||||
*it = std::move(fc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a fallback font, with optional OS-specific handle.
|
||||
* @param fontsizes Fontsizes to add fallback to.
|
||||
* @param name Name of font to add.
|
||||
* @param handle OS-specific handle or data of font.
|
||||
*/
|
||||
/* static */ void FontCache::AddFallback(FontSizes fontsizes, FontLoadReason load_reason, std::string_view name, std::span<const std::byte> os_data)
|
||||
{
|
||||
for (FontSize fs : fontsizes) {
|
||||
GetFontCacheSubSetting(fs)->fallback_fonts.emplace_back(load_reason, std::string{name}, std::vector<std::byte>{os_data.begin(), os_data.end()});
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void FontCache::LoadDefaultFonts(FontSize fs)
|
||||
{
|
||||
/* Load the sprite font, even if it's not preferred. */
|
||||
FontCache::Register(FontProviderManager::LoadFont(fs, FontType::Sprite, false, {}, {}), FontLoadReason::Default);
|
||||
if (!_fcsettings.prefer_sprite) {
|
||||
/* Load the default truetype font if sprite font is not preferred. */
|
||||
FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, false, GetDefaultTruetypeFontFile(fs), {}), FontLoadReason::Default);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void FontCache::LoadFallbackFonts(FontSize fs)
|
||||
{
|
||||
const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
|
||||
for (auto it = setting->fallback_fonts.rbegin(); it != setting->fallback_fonts.rend(); ++it) {
|
||||
FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, false, it->name, it->os_handle), it->load_reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,17 +283,53 @@ std::string GetFontCacheFontName(FontSize fs)
|
||||
*/
|
||||
/* static */ void FontCache::LoadFontCaches(FontSizes fontsizes)
|
||||
{
|
||||
FontCache::InitializeFontCaches();
|
||||
static constexpr std::string_view FALLBACK_FONT = "fallback";
|
||||
static constexpr std::string_view DEFAULT_FONT = "default";
|
||||
static constexpr std::initializer_list<std::string_view> extra_prefer_default = {DEFAULT_FONT, FALLBACK_FONT};
|
||||
static constexpr std::initializer_list<std::string_view> extra_prefer_fallback = {FALLBACK_FONT, DEFAULT_FONT};
|
||||
|
||||
for (FontSize fs : fontsizes) {
|
||||
Layouter::ResetFontCache(fs);
|
||||
FontCache::default_font_index[fs] = INVALID_FONT_INDEX;
|
||||
}
|
||||
|
||||
/* Unload everything except the sprite font cache. */
|
||||
while (FontCache::Get(fs)->HasParent()) {
|
||||
FontCache::caches[fs] = std::move(FontCache::caches[fs]->parent);
|
||||
/* Remove all existing FontCaches. */
|
||||
if (fontsizes == FONTSIZES_ALL) {
|
||||
FontCache::caches.clear();
|
||||
} else {
|
||||
for (auto it = std::begin(FontCache::caches); it != std::end(FontCache::caches); ++it) {
|
||||
if (*it == nullptr) continue;
|
||||
if (!fontsizes.Test((*it)->fs)) continue;
|
||||
it->reset();
|
||||
}
|
||||
}
|
||||
|
||||
for (FontSize fs : fontsizes) {
|
||||
/* Parse configured fonts, separated by ';' into a list. */
|
||||
std::vector<std::string_view> fontnames;
|
||||
StringConsumer consumer(GetFontCacheSubSetting(fs)->font);
|
||||
do {
|
||||
auto fontname = StrTrimView(consumer.ReadUntilChar(';', StringConsumer::SKIP_ONE_SEPARATOR), " \t");
|
||||
if (!fontname.empty()) fontnames.push_back(fontname);
|
||||
} while (consumer.AnyBytesLeft());
|
||||
|
||||
/* Add the default and fallback fonts as lowest priority if not manually specified. */
|
||||
for (const auto &extra_font : _fcsettings.prefer_default ? extra_prefer_default : extra_prefer_fallback) {
|
||||
if (std::ranges::find(fontnames, extra_font) == std::end(fontnames)) fontnames.push_back(extra_font);
|
||||
}
|
||||
|
||||
FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType));
|
||||
/* Load configured fonts in reverse order so that the first entry has priority. */
|
||||
for (auto it = fontnames.rbegin(); it != fontnames.rend(); ++it) {
|
||||
if (*it == DEFAULT_FONT) {
|
||||
FontCache::LoadDefaultFonts(fs);
|
||||
} else if (*it == FALLBACK_FONT) {
|
||||
FontCache::LoadFallbackFonts(fs);
|
||||
} else {
|
||||
FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, true, std::string{*it}, {}), FontLoadReason::Configured);
|
||||
}
|
||||
}
|
||||
|
||||
FontCache::UpdateCharacterHeight(fs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,8 +339,14 @@ std::string GetFontCacheFontName(FontSize fs)
|
||||
*/
|
||||
/* static */ void FontCache::ClearFontCaches(FontSizes fontsizes)
|
||||
{
|
||||
for (const auto &fc : FontCache::caches) {
|
||||
if (fc == nullptr) continue;
|
||||
if (!fontsizes.Test(fc->GetSize())) continue;
|
||||
fc->ClearFontCache();
|
||||
}
|
||||
|
||||
for (FontSize fs : fontsizes) {
|
||||
FontCache::Get(fs)->ClearFontCache();
|
||||
FontCache::UpdateCharacterHeight(fs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +355,5 @@ std::string GetFontCacheFontName(FontSize fs)
|
||||
*/
|
||||
/* static */ void FontCache::UninitializeFontCaches()
|
||||
{
|
||||
for (auto &fc : FontCache::caches) {
|
||||
fc.reset();
|
||||
}
|
||||
FontCache::caches.clear();
|
||||
}
|
||||
|
||||
139
src/fontcache.h
139
src/fontcache.h
@@ -16,20 +16,42 @@
|
||||
|
||||
/** Glyphs are characters from a font. */
|
||||
typedef uint32_t GlyphID;
|
||||
static const GlyphID SPRITE_GLYPH = 1U << 30;
|
||||
using FontIndex = uint8_t;
|
||||
|
||||
static const FontIndex INVALID_FONT_INDEX = std::numeric_limits<FontIndex>::max();
|
||||
|
||||
enum class FontLoadReason : uint8_t {
|
||||
Default,
|
||||
Configured,
|
||||
LanguageFallback,
|
||||
End,
|
||||
};
|
||||
|
||||
/** Font cache for basic fonts. */
|
||||
class FontCache {
|
||||
protected:
|
||||
static std::array<std::unique_ptr<FontCache>, FS_END> caches; ///< All the font caches.
|
||||
std::unique_ptr<FontCache> parent; ///< The parent of this font cache.
|
||||
using FontCaches = std::vector<std::unique_ptr<FontCache>>;
|
||||
static FontCaches caches;
|
||||
|
||||
struct FontMetrics {
|
||||
int height = 0;
|
||||
int baseline = 0;
|
||||
};
|
||||
|
||||
static std::array<FontMetrics, FS_END> metrics;
|
||||
static std::array<FontIndex, FS_END> default_font_index;
|
||||
|
||||
const FontSize fs; ///< The size of the font.
|
||||
FontIndex font_index; ///< The index of the font.
|
||||
FontLoadReason load_reason; ///< Reason why the font is loaded.
|
||||
int height = 0; ///< The height of the font.
|
||||
int ascender = 0; ///< The ascender value of the font.
|
||||
int descender = 0; ///< The descender value of the font.
|
||||
|
||||
FontCache(FontSize fs) : fs(fs) {}
|
||||
static void Register(std::unique_ptr<FontCache> &&fc);
|
||||
static void Register(std::unique_ptr<FontCache> &&fc, FontLoadReason load_reason);
|
||||
static void LoadDefaultFonts(FontSize fs);
|
||||
static void LoadFallbackFonts(FontSize fs);
|
||||
|
||||
public:
|
||||
virtual ~FontCache() = default;
|
||||
@@ -46,12 +68,36 @@ public:
|
||||
|
||||
static int GetDefaultFontHeight(FontSize fs);
|
||||
|
||||
static inline int GetFontBaseline(FontSize fs)
|
||||
{
|
||||
return FontCache::metrics[fs].baseline;
|
||||
}
|
||||
|
||||
static void AddFallback(FontSizes fontsizes, FontLoadReason load_reason, std::string_view name, std::span<const std::byte> os_data = {});
|
||||
|
||||
/**
|
||||
* Add a fallback font, with OS-specific handle.
|
||||
* @param fontsizes Fontsizes to add fallback to.
|
||||
* @param name Name of font to add.
|
||||
* @param handle OS-specific handle or data of font.
|
||||
*/
|
||||
template <typename T>
|
||||
static void AddFallbackWithHandle(FontSizes fontsizes, FontLoadReason load_reason, std::string_view name, T &handle)
|
||||
{
|
||||
auto os_data = std::as_bytes(std::span(&handle, 1));
|
||||
FontCache::AddFallback(fontsizes, load_reason, name, os_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the FontSize of the font.
|
||||
* @return The FontSize.
|
||||
*/
|
||||
inline FontSize GetSize() const { return this->fs; }
|
||||
|
||||
inline FontIndex GetIndex() const { return this->font_index; }
|
||||
|
||||
inline FontLoadReason GetFontLoadReason() const { return this->load_reason; }
|
||||
|
||||
/**
|
||||
* Get the height of the font.
|
||||
* @return The height of the font.
|
||||
@@ -102,10 +148,9 @@ public:
|
||||
/**
|
||||
* Map a character into a glyph.
|
||||
* @param key The character.
|
||||
* @param fallback Allow fallback to the parent font.
|
||||
* @return The glyph ID used to draw the character.
|
||||
*/
|
||||
virtual GlyphID MapCharToGlyph(char32_t key, bool fallback = true) = 0;
|
||||
virtual GlyphID MapCharToGlyph(char32_t key) = 0;
|
||||
|
||||
/**
|
||||
* Get the native OS font handle, if there is one.
|
||||
@@ -122,25 +167,57 @@ public:
|
||||
*/
|
||||
virtual std::string GetFontName() = 0;
|
||||
|
||||
virtual int GetGlyphYOffset();
|
||||
|
||||
/**
|
||||
* Get span of all FontCaches.
|
||||
* @return Span of all FontCaches.
|
||||
*/
|
||||
static inline std::span<const std::unique_ptr<FontCache>> Get()
|
||||
{
|
||||
return FontCache::caches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the font cache of a given font size.
|
||||
* @param fs The font size to look up.
|
||||
* @return The font cache.
|
||||
*/
|
||||
static inline FontCache *Get(FontSize fs)
|
||||
static inline FontCache *Get(FontIndex font_index)
|
||||
{
|
||||
assert(fs < FS_END);
|
||||
return FontCache::caches[fs].get();
|
||||
assert(font_index < FontCache::caches.size());
|
||||
return FontCache::caches[font_index].get();
|
||||
}
|
||||
|
||||
static std::string GetName(FontSize fs);
|
||||
|
||||
/**
|
||||
* Check whether the font cache has a parent.
|
||||
*/
|
||||
inline bool HasParent()
|
||||
static inline int GetCharacterHeight(FontSize fs)
|
||||
{
|
||||
return this->parent != nullptr;
|
||||
return FontCache::metrics[fs].height;
|
||||
}
|
||||
|
||||
static void UpdateCharacterHeight(FontSize fs);
|
||||
|
||||
static inline FontIndex GetDefaultFontIndex(FontSize fs)
|
||||
{
|
||||
return FontCache::default_font_index[fs];
|
||||
}
|
||||
|
||||
static inline class FontCache *GetDefaultFontCache(FontSize fs)
|
||||
{
|
||||
FontIndex index = FontCache::GetDefaultFontIndex(fs);
|
||||
if (index != INVALID_FONT_INDEX) return FontCache::Get(index);
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
||||
static inline FontIndex GetFontIndexForCharacter(FontSize fs, char32_t c)
|
||||
{
|
||||
for (auto it = std::rbegin(FontCache::caches); it != std::rend(FontCache::caches); ++it) {
|
||||
FontCache *fc = it->get();
|
||||
if (fc == nullptr) continue;
|
||||
if (fc->GetSize() != fs) continue;
|
||||
if (fc->MapCharToGlyph(c) == 0) continue;
|
||||
return std::distance(std::begin(FontCache::caches), std::next(it).base());
|
||||
}
|
||||
return INVALID_FONT_INDEX;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,28 +229,33 @@ public:
|
||||
/** Get the Sprite for a glyph */
|
||||
inline const Sprite *GetGlyph(FontSize size, char32_t key)
|
||||
{
|
||||
FontCache *fc = FontCache::Get(size);
|
||||
FontIndex font_index = FontCache::GetFontIndexForCharacter(size, key);
|
||||
FontCache *fc = font_index != INVALID_FONT_INDEX ? FontCache::Get(font_index) : FontCache::GetDefaultFontCache(size);
|
||||
if (fc == nullptr) return nullptr;
|
||||
return fc->GetGlyph(fc->MapCharToGlyph(key));
|
||||
}
|
||||
|
||||
/** Get the width of a glyph */
|
||||
inline uint GetGlyphWidth(FontSize size, char32_t key)
|
||||
{
|
||||
FontCache *fc = FontCache::Get(size);
|
||||
FontIndex font_index = FontCache::GetFontIndexForCharacter(size, key);
|
||||
FontCache *fc = font_index != INVALID_FONT_INDEX ? FontCache::Get(font_index) : FontCache::GetDefaultFontCache(size);
|
||||
if (fc == nullptr) return 0;
|
||||
return fc->GetGlyphWidth(fc->MapCharToGlyph(key));
|
||||
}
|
||||
|
||||
inline bool GetDrawGlyphShadow(FontSize size)
|
||||
{
|
||||
return FontCache::Get(size)->GetDrawGlyphShadow();
|
||||
}
|
||||
|
||||
/** Settings for a single font. */
|
||||
struct FontCacheSubSetting {
|
||||
std::string font; ///< The name of the font, or path to the font.
|
||||
uint size; ///< The (requested) size of the font.
|
||||
|
||||
const void *os_handle = nullptr; ///< Optional native OS font info. Only valid during font search.
|
||||
struct FontCacheFallback {
|
||||
FontLoadReason load_reason = FontLoadReason::LanguageFallback;
|
||||
std::string name;
|
||||
std::vector<std::byte> os_handle;
|
||||
};
|
||||
|
||||
std::vector<FontCacheFallback> fallback_fonts;
|
||||
};
|
||||
|
||||
/** Settings for the four different fonts. */
|
||||
@@ -184,6 +266,7 @@ struct FontCacheSettings {
|
||||
FontCacheSubSetting mono; ///< The mono space font used for license/readme viewers.
|
||||
bool prefer_sprite; ///< Whether to prefer the built-in sprite font over resizable fonts.
|
||||
bool global_aa; ///< Whether to anti alias all font sizes.
|
||||
bool prefer_default; ///< Prefer OpenTTD's default font over autodetected fallback fonts.
|
||||
};
|
||||
|
||||
extern FontCacheSettings _fcsettings;
|
||||
@@ -229,14 +312,14 @@ public:
|
||||
ProviderManager<FontCacheFactory>::Unregister(*this);
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype) const = 0;
|
||||
virtual bool FindFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) const = 0;
|
||||
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;
|
||||
virtual bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, class MissingGlyphSearcher *callback) const = 0;
|
||||
};
|
||||
|
||||
class FontProviderManager : ProviderManager<FontCacheFactory> {
|
||||
public:
|
||||
static std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype);
|
||||
static bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback);
|
||||
static std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span<const std::byte> os_handle);
|
||||
static bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, class MissingGlyphSearcher *callback);
|
||||
};
|
||||
|
||||
/* Implemented in spritefontcache.cpp */
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
FreeTypeFontCache(FontSize fs, FT_Face face, int pixels);
|
||||
~FreeTypeFontCache();
|
||||
void ClearFontCache() override;
|
||||
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override;
|
||||
GlyphID MapCharToGlyph(char32_t key) override;
|
||||
std::string GetFontName() override { return fmt::format("{}, {}", face->family_name, face->style_name); }
|
||||
bool IsBuiltInFont() override { return false; }
|
||||
const void *GetOSHandle() override { return &face; }
|
||||
@@ -194,17 +194,11 @@ const Sprite *FreeTypeFontCache::InternalGetGlyph(GlyphID key, bool aa)
|
||||
}
|
||||
|
||||
|
||||
GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
|
||||
GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key)
|
||||
{
|
||||
assert(IsPrintable(key));
|
||||
|
||||
FT_UInt glyph = FT_Get_Char_Index(this->face, key);
|
||||
|
||||
if (glyph == 0 && allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
|
||||
return this->parent->MapCharToGlyph(key);
|
||||
}
|
||||
|
||||
return glyph;
|
||||
return FT_Get_Char_Index(this->face, key);
|
||||
}
|
||||
|
||||
FT_Library _ft_library = nullptr;
|
||||
@@ -226,15 +220,10 @@ public:
|
||||
* format is 'font family name' or 'font family name, font style'.
|
||||
* @param fs The font size to load.
|
||||
*/
|
||||
std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype) const override
|
||||
std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span<const std::byte> os_handle) const override
|
||||
{
|
||||
if (fonttype != FontType::TrueType) return nullptr;
|
||||
|
||||
FontCacheSubSetting *settings = GetFontCacheSubSetting(fs);
|
||||
|
||||
std::string font = GetFontCacheFontName(fs);
|
||||
if (font.empty()) return nullptr;
|
||||
|
||||
if (_ft_library == nullptr) {
|
||||
if (FT_Init_FreeType(&_ft_library) != FT_Err_Ok) {
|
||||
ShowInfo("Unable to initialize FreeType, using sprite fonts instead");
|
||||
@@ -248,7 +237,9 @@ public:
|
||||
|
||||
/* If font is an absolute path to a ttf, try loading that first. */
|
||||
int32_t index = 0;
|
||||
if (settings->os_handle != nullptr) index = *static_cast<const int32_t *>(settings->os_handle);
|
||||
if (os_handle.size() == sizeof(index)) {
|
||||
index = *reinterpret_cast<const int32_t *>(os_handle.data());
|
||||
}
|
||||
FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face);
|
||||
|
||||
if (error != FT_Err_Ok) {
|
||||
@@ -260,8 +251,8 @@ public:
|
||||
}
|
||||
|
||||
#ifdef WITH_FONTCONFIG
|
||||
/* Try loading based on font face name (OS-wide fonts). */
|
||||
if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face);
|
||||
/* If allowed to search, try loading based on font face name (OS-wide fonts). */
|
||||
if (error != FT_Err_Ok && search) error = GetFontByFaceName(font, &face);
|
||||
#endif /* WITH_FONTCONFIG */
|
||||
|
||||
if (error != FT_Err_Ok) {
|
||||
@@ -272,10 +263,10 @@ public:
|
||||
return LoadFont(fs, face, font, GetFontCacheFontSize(fs));
|
||||
}
|
||||
|
||||
bool FindFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) const override
|
||||
bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override
|
||||
{
|
||||
#ifdef WITH_FONTCONFIG
|
||||
if (FontConfigFindFallbackFont(settings, language_isocode, callback)) return true;
|
||||
if (FontConfigFindFallbackFont(language_isocode, fontsizes, callback)) return true;
|
||||
#endif /* WITH_FONTCONFIG */
|
||||
|
||||
return false;
|
||||
@@ -309,7 +300,7 @@ private:
|
||||
if (error != FT_Err_Ok) {
|
||||
FT_Done_Face(face);
|
||||
|
||||
ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), error);
|
||||
ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}", font_name, FontSizeToName(fs), error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,16 +23,6 @@
|
||||
|
||||
static const int ASCII_LETTERSTART = 32; ///< First printable ASCII letter.
|
||||
|
||||
/**
|
||||
* Scale traditional pixel dimensions to font zoom level, for drawing sprite fonts.
|
||||
* @param value Pixel amount at #ZOOM_BASE (traditional "normal" interface size).
|
||||
* @return Pixel amount at _font_zoom (current interface size).
|
||||
*/
|
||||
static int ScaleFontTrad(int value)
|
||||
{
|
||||
return UnScaleByZoom(value * ZOOM_BASE, _font_zoom);
|
||||
}
|
||||
|
||||
static std::array<std::unordered_map<char32_t, SpriteID>, FS_END> _char_maps{}; ///< Glyph map for each font size.
|
||||
|
||||
/**
|
||||
@@ -116,37 +106,48 @@ void InitializeUnicodeGlyphMap()
|
||||
*/
|
||||
SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs)
|
||||
{
|
||||
this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs));
|
||||
this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2;
|
||||
this->UpdateMetrics();
|
||||
}
|
||||
|
||||
void SpriteFontCache::ClearFontCache()
|
||||
{
|
||||
Layouter::ResetFontCache(this->fs);
|
||||
this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs));
|
||||
this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2;
|
||||
this->UpdateMetrics();
|
||||
}
|
||||
|
||||
void SpriteFontCache::UpdateMetrics()
|
||||
{
|
||||
this->height = ScaleFontTrad(DEFAULT_FONT_HEIGHT[this->fs]);
|
||||
this->ascender = ScaleGUITrad(DEFAULT_FONT_ASCENDER[this->fs]);
|
||||
this->descender = ScaleGUITrad(DEFAULT_FONT_ASCENDER[this->fs] - DEFAULT_FONT_HEIGHT[this->fs]);
|
||||
this->scaled_ascender = ScaleFontTrad(DEFAULT_FONT_ASCENDER[this->fs]);
|
||||
}
|
||||
|
||||
int SpriteFontCache::GetGlyphYOffset()
|
||||
{
|
||||
return FontCache::GetFontBaseline(this->fs) - this->scaled_ascender;
|
||||
}
|
||||
|
||||
const Sprite *SpriteFontCache::GetGlyph(GlyphID key)
|
||||
{
|
||||
SpriteID sprite = static_cast<SpriteID>(key & ~SPRITE_GLYPH);
|
||||
SpriteID sprite = static_cast<SpriteID>(key);
|
||||
if (sprite == 0) sprite = GetUnicodeGlyph(this->fs, '?');
|
||||
return GetSprite(sprite, SpriteType::Font);
|
||||
}
|
||||
|
||||
uint SpriteFontCache::GetGlyphWidth(GlyphID key)
|
||||
{
|
||||
SpriteID sprite = static_cast<SpriteID>(key & ~SPRITE_GLYPH);
|
||||
SpriteID sprite = static_cast<SpriteID>(key);
|
||||
if (sprite == 0) sprite = GetUnicodeGlyph(this->fs, '?');
|
||||
return SpriteExists(sprite) ? GetSprite(sprite, SpriteType::Font)->width + ScaleFontTrad(this->fs != FS_NORMAL ? 1 : 0) : 0;
|
||||
}
|
||||
|
||||
GlyphID SpriteFontCache::MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback)
|
||||
GlyphID SpriteFontCache::MapCharToGlyph(char32_t key)
|
||||
{
|
||||
assert(IsPrintable(key));
|
||||
SpriteID sprite = GetUnicodeGlyph(this->fs, key);
|
||||
if (sprite == 0) return 0;
|
||||
return SPRITE_GLYPH | sprite;
|
||||
return static_cast<GlyphID>(sprite);
|
||||
}
|
||||
|
||||
bool SpriteFontCache::GetDrawGlyphShadow()
|
||||
@@ -158,14 +159,14 @@ class SpriteFontCacheFactory : public FontCacheFactory {
|
||||
public:
|
||||
SpriteFontCacheFactory() : FontCacheFactory("sprite", "Sprite font provider") {}
|
||||
|
||||
std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype) const override
|
||||
std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype, bool, const std::string &, std::span<const std::byte>) const override
|
||||
{
|
||||
if (fonttype != FontType::Sprite) return nullptr;
|
||||
|
||||
return std::make_unique<SpriteFontCache>(fs);
|
||||
}
|
||||
|
||||
bool FindFallbackFont(struct FontCacheSettings *, const std::string &, class MissingGlyphSearcher *) const override
|
||||
bool FindFallbackFont(const std::string &, FontSizes, class MissingGlyphSearcher *) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -17,12 +17,17 @@ class SpriteFontCache : public FontCache {
|
||||
public:
|
||||
SpriteFontCache(FontSize fs);
|
||||
void ClearFontCache() override;
|
||||
int GetGlyphYOffset() override;
|
||||
const Sprite *GetGlyph(GlyphID key) override;
|
||||
uint GetGlyphWidth(GlyphID key) override;
|
||||
bool GetDrawGlyphShadow() override;
|
||||
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override;
|
||||
GlyphID MapCharToGlyph(char32_t key) override;
|
||||
std::string GetFontName() override { return "sprite"; }
|
||||
bool IsBuiltInFont() override { return true; }
|
||||
|
||||
private:
|
||||
void UpdateMetrics();
|
||||
int scaled_ascender;
|
||||
};
|
||||
|
||||
#endif /* SPRITEFONTCACHE_H */
|
||||
|
||||
@@ -64,8 +64,6 @@ bool TrueTypeFontCache::GetDrawGlyphShadow()
|
||||
|
||||
uint TrueTypeFontCache::GetGlyphWidth(GlyphID key)
|
||||
{
|
||||
if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyphWidth(key);
|
||||
|
||||
GlyphEntry *glyph = this->GetGlyphPtr(key);
|
||||
if (glyph == nullptr || glyph->data == nullptr) {
|
||||
this->GetGlyph(key);
|
||||
@@ -77,8 +75,6 @@ uint TrueTypeFontCache::GetGlyphWidth(GlyphID key)
|
||||
|
||||
const Sprite *TrueTypeFontCache::GetGlyph(GlyphID key)
|
||||
{
|
||||
if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyph(key);
|
||||
|
||||
/* Check for the glyph in our cache */
|
||||
GlyphEntry *glyph = this->GetGlyphPtr(key);
|
||||
if (glyph != nullptr && glyph->data != nullptr) return glyph->GetSprite();
|
||||
|
||||
19
src/gfx.cpp
19
src/gfx.cpp
@@ -537,7 +537,7 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
|
||||
* another size would be chosen it won't have truncated too little for
|
||||
* the truncation dots.
|
||||
*/
|
||||
truncation_layout.emplace(GetEllipsis(), INT32_MAX, line.GetVisualRun(0).GetFont()->fc->GetSize());
|
||||
truncation_layout.emplace(GetEllipsis(), INT32_MAX, line.GetVisualRun(0).GetFont().GetFontCache().GetSize());
|
||||
truncation_width = truncation_layout->GetBounds().width;
|
||||
|
||||
/* Is there enough space even for an ellipsis? */
|
||||
@@ -593,15 +593,15 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
|
||||
const ParagraphLayouter::VisualRun &run = line.GetVisualRun(run_index);
|
||||
const auto &glyphs = run.GetGlyphs();
|
||||
const auto &positions = run.GetPositions();
|
||||
const Font *f = run.GetFont();
|
||||
const Font &f = run.GetFont();
|
||||
|
||||
FontCache *fc = f->fc;
|
||||
TextColour colour = f->colour;
|
||||
FontCache &fc = f.GetFontCache();
|
||||
TextColour colour = f.colour;
|
||||
if (colour == TC_INVALID || HasFlag(initial_colour, TC_FORCED)) colour = initial_colour;
|
||||
bool colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
|
||||
/* Update the last colour for the truncation ellipsis. */
|
||||
last_colour = colour;
|
||||
if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue;
|
||||
if (do_shadow && (!fc.GetDrawGlyphShadow() || !colour_has_shadow)) continue;
|
||||
SetColourRemap(do_shadow ? TC_BLACK : colour);
|
||||
|
||||
for (int i = 0; i < run.GetGlyphCount(); i++) {
|
||||
@@ -616,13 +616,10 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
|
||||
|
||||
/* Truncated away. */
|
||||
if (truncation && (begin_x < min_x || end_x > max_x)) continue;
|
||||
/* Outside the clipping area. */
|
||||
if (begin_x > dpi_right || end_x < dpi_left) continue;
|
||||
|
||||
const Sprite *sprite = fc->GetGlyph(glyph);
|
||||
/* Check clipping (the "+ 1" is for the shadow). */
|
||||
if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue;
|
||||
|
||||
if (do_shadow && (glyph & SPRITE_GLYPH) != 0) continue;
|
||||
|
||||
const Sprite *sprite = fc.GetGlyph(glyph);
|
||||
GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,21 +37,6 @@
|
||||
/** Cache of ParagraphLayout lines. */
|
||||
std::unique_ptr<Layouter::LineCache> Layouter::linecache;
|
||||
|
||||
/** Cache of Font instances. */
|
||||
Layouter::FontColourMap Layouter::fonts[FS_END];
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new font.
|
||||
* @param size The font size to use for this font.
|
||||
* @param colour The colour to draw this font in.
|
||||
*/
|
||||
Font::Font(FontSize size, TextColour colour) :
|
||||
fc(FontCache::Get(size)), colour(colour)
|
||||
{
|
||||
assert(size < FS_END);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting a ParagraphLayouter of the given type.
|
||||
*
|
||||
@@ -71,7 +56,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
|
||||
const typename T::CharType *buffer_last = buff_begin + str.size() + 1;
|
||||
typename T::CharType *buff = buff_begin;
|
||||
FontMap &font_mapping = line.runs;
|
||||
Font *f = Layouter::GetFont(state.fontsize, state.cur_colour);
|
||||
Font f{state.font_index, state.cur_colour};
|
||||
|
||||
font_mapping.clear();
|
||||
|
||||
@@ -80,7 +65,10 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
|
||||
* whenever the font changes, and convert the wide characters into a format
|
||||
* usable by ParagraphLayout.
|
||||
*/
|
||||
for (char32_t c : Utf8View(str)) {
|
||||
Utf8View view(str);
|
||||
for (auto it = view.begin(); it != view.end(); /* nothing */) {
|
||||
auto cur = it;
|
||||
uint32_t c = *it++;
|
||||
if (c == '\0' || c == '\n') {
|
||||
/* Caller should already have filtered out these characters. */
|
||||
NOT_REACHED();
|
||||
@@ -95,19 +83,40 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
|
||||
} else {
|
||||
/* Filter out non printable characters */
|
||||
if (!IsPrintable(c)) continue;
|
||||
/* Filter out text direction characters that shouldn't be drawn, and
|
||||
* will not be handled in the fallback case because they are mostly
|
||||
* needed for RTL languages which need more proper shaping support. */
|
||||
if (!T::SUPPORTS_RTL && IsTextDirectionChar(c)) continue;
|
||||
buff += T::AppendToBuffer(buff, buffer_last, c);
|
||||
if (buff >= buffer_last) break;
|
||||
continue;
|
||||
|
||||
if (IsTextDirectionChar(c)) {
|
||||
/* Filter out text direction characters that shouldn't be drawn, and
|
||||
* will not be handled in the fallback case because they are mostly
|
||||
* needed for RTL languages which need more proper shaping support. */
|
||||
if constexpr (!T::SUPPORTS_RTL) continue;
|
||||
|
||||
buff += T::AppendToBuffer(buff, buffer_last, c);
|
||||
if (buff >= buffer_last) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
FontIndex font_index = FontCache::GetFontIndexForCharacter(state.fontsize, c);
|
||||
|
||||
if (font_index == INVALID_FONT_INDEX) {
|
||||
font_index = FontCache::GetDefaultFontIndex(state.fontsize);
|
||||
}
|
||||
|
||||
if (state.font_index == font_index) {
|
||||
buff += T::AppendToBuffer(buff, buffer_last, c);
|
||||
if (buff >= buffer_last) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* This character goes in the next run so don't advance. */
|
||||
state.font_index = font_index;
|
||||
|
||||
it = cur;
|
||||
}
|
||||
|
||||
if (font_mapping.empty() || font_mapping.back().first != buff - buff_begin) {
|
||||
if (buff - buff_begin > 0 && (font_mapping.empty() || font_mapping.back().first != buff - buff_begin)) {
|
||||
font_mapping.emplace_back(buff - buff_begin, f);
|
||||
}
|
||||
f = Layouter::GetFont(state.fontsize, state.cur_colour);
|
||||
f = {state.font_index, state.cur_colour};
|
||||
}
|
||||
|
||||
/* Better safe than sorry. */
|
||||
@@ -116,6 +125,14 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
|
||||
if (font_mapping.empty() || font_mapping.back().first != buff - buff_begin) {
|
||||
font_mapping.emplace_back(buff - buff_begin, f);
|
||||
}
|
||||
|
||||
if constexpr (!std::is_same_v<T, FallbackParagraphLayoutFactory>) {
|
||||
/* Don't layout if all runs use a built-in font and we're not using the fallback layouter. */
|
||||
if (std::ranges::all_of(font_mapping, [](const auto &i) { return i.second.GetFontCache().IsBuiltInFont(); })) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
line.layout = T::GetParagraphLayout(buff_begin, buff, font_mapping);
|
||||
line.state_after = state;
|
||||
}
|
||||
@@ -128,7 +145,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
|
||||
*/
|
||||
Layouter::Layouter(std::string_view str, int maxw, FontSize fontsize) : string(str)
|
||||
{
|
||||
FontState state(TC_INVALID, fontsize);
|
||||
FontState state(TC_INVALID, fontsize, FontCache::GetDefaultFontIndex(fontsize));
|
||||
|
||||
while (true) {
|
||||
auto line_length = str.find_first_of('\n');
|
||||
@@ -338,18 +355,6 @@ ptrdiff_t Layouter::GetCharAtPosition(int x, size_t line_index) const
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a static font instance.
|
||||
*/
|
||||
Font *Layouter::GetFont(FontSize size, TextColour colour)
|
||||
{
|
||||
FontColourMap::iterator it = fonts[size].find(colour);
|
||||
if (it != fonts[size].end()) return it->second.get();
|
||||
|
||||
fonts[size][colour] = std::make_unique<Font>(size, colour);
|
||||
return fonts[size][colour].get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform initialization of layout engine.
|
||||
*/
|
||||
@@ -362,12 +367,9 @@ void Layouter::Initialize()
|
||||
|
||||
/**
|
||||
* Reset cached font information.
|
||||
* @param size Font size to reset.
|
||||
*/
|
||||
void Layouter::ResetFontCache(FontSize size)
|
||||
void Layouter::ResetFontCache([[maybe_unused]] FontSize size)
|
||||
{
|
||||
fonts[size].clear();
|
||||
|
||||
/* We must reset the linecache since it references the just freed fonts */
|
||||
ResetLineCache();
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
#include "misc/lrucache.hpp"
|
||||
#include "fontcache.h"
|
||||
#include "gfx_func.h"
|
||||
#include "core/math_func.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
@@ -22,12 +20,13 @@
|
||||
* of the same text, e.g. on line breaks.
|
||||
*/
|
||||
struct FontState {
|
||||
FontSize fontsize; ///< Current font size.
|
||||
TextColour cur_colour; ///< Current text colour.
|
||||
FontSize fontsize; ///< Current font size.
|
||||
FontIndex font_index; ///< Current font index.
|
||||
TextColour cur_colour; ///< Current text colour.
|
||||
std::vector<TextColour> colour_stack; ///< Stack of colours to assist with colour switching.
|
||||
|
||||
FontState() : fontsize(FS_END), cur_colour(TC_INVALID) {}
|
||||
FontState(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour) {}
|
||||
FontState() : fontsize(FS_END), font_index(INVALID_FONT_INDEX), cur_colour(TC_INVALID) {}
|
||||
FontState(TextColour colour, FontSize fontsize, FontIndex font_index) : fontsize(fontsize), font_index(font_index), cur_colour(colour) {}
|
||||
|
||||
auto operator<=>(const FontState &) const = default;
|
||||
|
||||
@@ -67,6 +66,7 @@ struct FontState {
|
||||
inline void SetFontSize(FontSize f)
|
||||
{
|
||||
this->fontsize = f;
|
||||
this->font_index = FontCache::GetDefaultFontIndex(this->fontsize);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,9 +85,10 @@ template <> struct std::hash<FontState> {
|
||||
std::size_t operator()(const FontState &state) const noexcept
|
||||
{
|
||||
size_t h1 = std::hash<FontSize>{}(state.fontsize);
|
||||
size_t h2 = std::hash<TextColour>{}(state.cur_colour);
|
||||
size_t h3 = std::hash<std::vector<TextColour>>{}(state.colour_stack);
|
||||
return h1 ^ (h2 << 1) ^ (h3 << 2);
|
||||
size_t h2 = std::hash<FontIndex>{}(state.font_index);
|
||||
size_t h3 = std::hash<TextColour>{}(state.cur_colour);
|
||||
size_t h4 = std::hash<std::vector<TextColour>>{}(state.colour_stack);
|
||||
return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,14 +97,14 @@ template <> struct std::hash<FontState> {
|
||||
*/
|
||||
class Font {
|
||||
public:
|
||||
FontCache *fc; ///< The font we are using.
|
||||
TextColour colour; ///< The colour this font has to be.
|
||||
FontIndex font_index = INVALID_FONT_INDEX; ///< The font we are using.
|
||||
TextColour colour = TC_INVALID; ///< The colour this font has to be.
|
||||
|
||||
Font(FontSize size, TextColour colour);
|
||||
inline FontCache &GetFontCache() const { return *FontCache::Get(this->font_index); }
|
||||
};
|
||||
|
||||
/** Mapping from index to font. The pointer is owned by FontColourMap. */
|
||||
using FontMap = std::vector<std::pair<int, Font *>>;
|
||||
using FontMap = std::vector<std::pair<int, Font>>;
|
||||
|
||||
/**
|
||||
* Interface to glue fallback and normal layouter into one.
|
||||
@@ -129,7 +130,7 @@ public:
|
||||
class VisualRun {
|
||||
public:
|
||||
virtual ~VisualRun() = default;
|
||||
virtual const Font *GetFont() const = 0;
|
||||
virtual const Font &GetFont() const = 0;
|
||||
virtual int GetGlyphCount() const = 0;
|
||||
virtual std::span<const GlyphID> GetGlyphs() const = 0;
|
||||
virtual std::span<const Position> GetPositions() const = 0;
|
||||
@@ -205,18 +206,14 @@ private:
|
||||
|
||||
static LineCacheItem &GetCachedParagraphLayout(std::string_view str, const FontState &state);
|
||||
|
||||
using FontColourMap = std::map<TextColour, std::unique_ptr<Font>>;
|
||||
static FontColourMap fonts[FS_END];
|
||||
public:
|
||||
static Font *GetFont(FontSize size, TextColour colour);
|
||||
|
||||
Layouter(std::string_view str, int maxw = INT32_MAX, FontSize fontsize = FS_NORMAL);
|
||||
Dimension GetBounds();
|
||||
ParagraphLayouter::Position GetCharPosition(std::string_view::const_iterator ch) const;
|
||||
ptrdiff_t GetCharAtPosition(int x, size_t line_index) const;
|
||||
|
||||
static void Initialize();
|
||||
static void ResetFontCache(FontSize size);
|
||||
static void ResetFontCache(FontSize fs);
|
||||
static void ResetLineCache();
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "gfx_layout_fallback.h"
|
||||
#include "gfx_func.h"
|
||||
#include "string_func.h"
|
||||
#include "zoom_func.h"
|
||||
|
||||
@@ -43,15 +44,15 @@ public:
|
||||
std::vector<Position> positions; ///< The positions of the glyphs.
|
||||
std::vector<int> glyph_to_char; ///< The char index of the glyphs.
|
||||
|
||||
Font *font; ///< The font used to layout these.
|
||||
Font font; ///< The font used to layout these.
|
||||
|
||||
public:
|
||||
FallbackVisualRun(Font *font, const char32_t *chars, int glyph_count, int char_offset, int x);
|
||||
const Font *GetFont() const override { return this->font; }
|
||||
FallbackVisualRun(const Font &font, const char32_t *chars, int glyph_count, int char_offset, int x);
|
||||
const Font &GetFont() const override { return this->font; }
|
||||
int GetGlyphCount() const override { return static_cast<int>(this->glyphs.size()); }
|
||||
std::span<const GlyphID> GetGlyphs() const override { return this->glyphs; }
|
||||
std::span<const Position> GetPositions() const override { return this->positions; }
|
||||
int GetLeading() const override { return this->GetFont()->fc->GetHeight(); }
|
||||
int GetLeading() const override { return GetCharacterHeight(this->GetFont().GetFontCache().GetSize()); }
|
||||
std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; }
|
||||
};
|
||||
|
||||
@@ -109,26 +110,20 @@ public:
|
||||
* @param char_offset This run's offset from the start of the layout input string.
|
||||
* @param x The initial x position for this run.
|
||||
*/
|
||||
FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(Font *font, const char32_t *chars, int char_count, int char_offset, int x) :
|
||||
FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(const Font &font, const char32_t *chars, int char_count, int char_offset, int x) :
|
||||
font(font)
|
||||
{
|
||||
const bool isbuiltin = font->fc->IsBuiltInFont();
|
||||
|
||||
this->glyphs.reserve(char_count);
|
||||
this->glyph_to_char.reserve(char_count);
|
||||
this->positions.reserve(char_count);
|
||||
|
||||
FontCache &fc = this->font.GetFontCache();
|
||||
int y_offset = fc.GetGlyphYOffset();;
|
||||
int advance = x;
|
||||
for (int i = 0; i < char_count; i++) {
|
||||
const GlyphID &glyph_id = this->glyphs.emplace_back(font->fc->MapCharToGlyph(chars[i]));
|
||||
int x_advance = font->fc->GetGlyphWidth(glyph_id);
|
||||
if (isbuiltin) {
|
||||
this->positions.emplace_back(advance, advance + x_advance - 1, font->fc->GetAscender()); // Apply sprite font's ascender.
|
||||
} else if (chars[i] >= SCC_SPRITE_START && chars[i] <= SCC_SPRITE_END) {
|
||||
this->positions.emplace_back(advance, advance + x_advance - 1, (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2); // Align sprite font to centre
|
||||
} else {
|
||||
this->positions.emplace_back(advance, advance + x_advance - 1, 0); // No ascender adjustment.
|
||||
}
|
||||
const GlyphID &glyph_id = this->glyphs.emplace_back(fc.MapCharToGlyph(chars[i]));
|
||||
int x_advance = fc.GetGlyphWidth(glyph_id);
|
||||
this->positions.emplace_back(advance, advance + x_advance - 1, y_offset); // No ascender adjustment.
|
||||
advance += x_advance;
|
||||
this->glyph_to_char.push_back(char_offset + i);
|
||||
}
|
||||
@@ -233,7 +228,8 @@ std::unique_ptr<const ParagraphLayouter::Line> FallbackParagraphLayout::NextLine
|
||||
assert(iter != this->runs.end());
|
||||
}
|
||||
|
||||
const FontCache *fc = iter->second->fc;
|
||||
const FontCache *fc = &iter->second.GetFontCache();
|
||||
assert(fc != nullptr);
|
||||
const char32_t *next_run = this->buffer_begin + iter->first;
|
||||
|
||||
const char32_t *begin = this->buffer;
|
||||
@@ -251,6 +247,7 @@ std::unique_ptr<const ParagraphLayouter::Line> FallbackParagraphLayout::NextLine
|
||||
|
||||
if (this->buffer == next_run) {
|
||||
int w = l->GetWidth();
|
||||
assert(iter->second.font_index != INVALID_FONT_INDEX);
|
||||
l->emplace_back(iter->second, begin, this->buffer - begin, begin - this->buffer_begin, w);
|
||||
++iter;
|
||||
assert(iter != this->runs.end());
|
||||
|
||||
@@ -26,5 +26,24 @@ public:
|
||||
static size_t AppendToBuffer(char32_t *buff, const char32_t *buffer_last, char32_t c);
|
||||
};
|
||||
|
||||
/**
|
||||
* Swap paired brackets for fallback RTL layouting.
|
||||
* @param c Character to swap.
|
||||
* @return Swapped character, or original character if it is not a paired bracket.
|
||||
*/
|
||||
inline char32_t SwapRtlPairedCharacters(char32_t c)
|
||||
{
|
||||
/* There are many more paired brackets, but for fallback purposes we only handle ASCII brackets. */
|
||||
/* https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt */
|
||||
switch (c) {
|
||||
case U'(': return U')';
|
||||
case U')': return U'(';
|
||||
case U'[': return U']';
|
||||
case U']': return U'[';
|
||||
case U'{': return U'}';
|
||||
case U'}': return U'{';
|
||||
default: return c;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* GFX_LAYOUT_FALLBACK_H */
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#include "gfx_layout_icu.h"
|
||||
|
||||
#include "debug.h"
|
||||
#include "gfx_func.h"
|
||||
#include "gfx_layout_fallback.h"
|
||||
#include "string_func.h"
|
||||
#include "strings_func.h"
|
||||
#include "language.h"
|
||||
#include "table/control_codes.h"
|
||||
@@ -40,7 +43,7 @@ public:
|
||||
int length; ///< Length of the run in the buffer.
|
||||
UBiDiLevel level; ///< Embedding level of the run.
|
||||
UScriptCode script; ///< Script of the run.
|
||||
Font *font; ///< Font of the run.
|
||||
Font font; ///< Font of the run.
|
||||
|
||||
std::vector<GlyphID> glyphs; ///< The glyphs of the run. Valid after Shape() is called.
|
||||
std::vector<int> advance; ///< The advance (width) of the glyphs. Valid after Shape() is called.
|
||||
@@ -48,9 +51,10 @@ public:
|
||||
std::vector<ParagraphLayouter::Position> positions; ///< The positions of the glyphs. Valid after Shape() is called.
|
||||
int total_advance = 0; ///< The total advance of the run. Valid after Shape() is called.
|
||||
|
||||
ICURun(int start, int length, UBiDiLevel level, UScriptCode script = USCRIPT_UNKNOWN, Font *font = nullptr) : start(start), length(length), level(level), script(script), font(font) {}
|
||||
ICURun(int start, int length, UBiDiLevel level, UScriptCode script, const Font &font) : start(start), length(length), level(level), script(script), font(font) {}
|
||||
|
||||
void Shape(UChar *buff, size_t length);
|
||||
void FallbackShape(UChar *buff);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -66,7 +70,7 @@ public:
|
||||
std::vector<int> glyph_to_char;
|
||||
|
||||
int total_advance;
|
||||
const Font *font;
|
||||
Font font;
|
||||
|
||||
public:
|
||||
ICUVisualRun(const ICURun &run, int x);
|
||||
@@ -75,8 +79,8 @@ public:
|
||||
std::span<const Position> GetPositions() const override { return this->positions; }
|
||||
std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; }
|
||||
|
||||
const Font *GetFont() const override { return this->font; }
|
||||
int GetLeading() const override { return this->font->fc->GetHeight(); }
|
||||
const Font &GetFont() const override { return this->font; }
|
||||
int GetLeading() const override { return GetCharacterHeight(this->font.GetFontCache().GetSize()); }
|
||||
int GetGlyphCount() const override { return this->glyphs.size(); }
|
||||
int GetAdvance() const { return this->total_advance; }
|
||||
};
|
||||
@@ -141,6 +145,46 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) :
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually shape a run for built-in non-truetype fonts.
|
||||
* Similar to but not quite the same as \a UniscribeRun::FallbackShape.
|
||||
* @param buff The complete buffer of the run.
|
||||
*/
|
||||
void ICURun::FallbackShape(UChar *buff)
|
||||
{
|
||||
FontCache &fc = this->font.GetFontCache();
|
||||
|
||||
this->glyphs.reserve(this->length);
|
||||
this->glyph_to_char.reserve(this->length);
|
||||
|
||||
/* Read each UTF-16 character, mapping to an appropriate glyph. */
|
||||
for (int i = this->start; i < this->start + this->length; i += Utf16IsLeadSurrogate(buff[i]) ? 2 : 1) {
|
||||
char32_t c = Utf16DecodeChar(reinterpret_cast<uint16_t *>(buff + i));
|
||||
if (this->level & 1) c = SwapRtlPairedCharacters(c);
|
||||
this->glyphs.emplace_back(fc.MapCharToGlyph(c));
|
||||
this->glyph_to_char.push_back(i);
|
||||
}
|
||||
|
||||
/* Reverse the sequence if this run is RTL. */
|
||||
if (this->level & 1) {
|
||||
std::reverse(std::begin(this->glyphs), std::end(this->glyphs));
|
||||
std::reverse(std::begin(this->glyph_to_char), std::end(this->glyph_to_char));
|
||||
}
|
||||
|
||||
this->positions.reserve(this->glyphs.size());
|
||||
|
||||
/* Set positions of each glyph. */
|
||||
int y_offset = fc.GetGlyphYOffset();
|
||||
int advance = 0;
|
||||
for (const GlyphID glyph : this->glyphs) {
|
||||
int x_advance = fc.GetGlyphWidth(glyph);
|
||||
this->positions.emplace_back(advance, advance + x_advance - 1, y_offset);
|
||||
this->advance.push_back(x_advance);
|
||||
advance += x_advance;
|
||||
}
|
||||
this->total_advance = advance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shape a single run.
|
||||
*
|
||||
@@ -149,7 +193,20 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) :
|
||||
*/
|
||||
void ICURun::Shape(UChar *buff, size_t buff_length)
|
||||
{
|
||||
auto hbfont = hb_ft_font_create_referenced(*(static_cast<const FT_Face *>(font->fc->GetOSHandle())));
|
||||
FontCache &fc = this->font.GetFontCache();
|
||||
|
||||
/* Make sure any former run is lost. */
|
||||
this->glyphs.clear();
|
||||
this->glyph_to_char.clear();
|
||||
this->positions.clear();
|
||||
this->advance.clear();
|
||||
|
||||
if (fc.IsBuiltInFont()) {
|
||||
this->FallbackShape(buff);
|
||||
return;
|
||||
}
|
||||
|
||||
auto hbfont = hb_ft_font_create_referenced(*(static_cast<const FT_Face *>(fc.GetOSHandle())));
|
||||
/* Match the flags with how we render the glyphs. */
|
||||
hb_ft_font_set_load_flags(hbfont, GetFontAAState() ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO);
|
||||
|
||||
@@ -170,12 +227,6 @@ void ICURun::Shape(UChar *buff, size_t buff_length)
|
||||
auto glyph_info = hb_buffer_get_glyph_infos(hbbuf, &glyph_count);
|
||||
auto glyph_pos = hb_buffer_get_glyph_positions(hbbuf, &glyph_count);
|
||||
|
||||
/* Make sure any former run is lost. */
|
||||
this->glyphs.clear();
|
||||
this->glyph_to_char.clear();
|
||||
this->positions.clear();
|
||||
this->advance.clear();
|
||||
|
||||
/* Reserve space, as we already know the size. */
|
||||
this->glyphs.reserve(glyph_count);
|
||||
this->glyph_to_char.reserve(glyph_count);
|
||||
@@ -183,20 +234,12 @@ void ICURun::Shape(UChar *buff, size_t buff_length)
|
||||
this->advance.reserve(glyph_count);
|
||||
|
||||
/* Prepare the glyphs/position. ICUVisualRun will give the position an offset if needed. */
|
||||
int y_offset = fc.GetGlyphYOffset();
|
||||
hb_position_t advance = 0;
|
||||
for (unsigned int i = 0; i < glyph_count; i++) {
|
||||
int x_advance;
|
||||
|
||||
if (buff[glyph_info[i].cluster] >= SCC_SPRITE_START && buff[glyph_info[i].cluster] <= SCC_SPRITE_END && glyph_info[i].codepoint == 0) {
|
||||
auto glyph = this->font->fc->MapCharToGlyph(buff[glyph_info[i].cluster]);
|
||||
x_advance = this->font->fc->GetGlyphWidth(glyph);
|
||||
this->glyphs.push_back(glyph);
|
||||
this->positions.emplace_back(advance, advance + x_advance - 1, (this->font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(this->font->fc->GetSize()))) / 2); // Align sprite font to centre
|
||||
} else {
|
||||
x_advance = glyph_pos[i].x_advance / FONT_SCALE;
|
||||
this->glyphs.push_back(glyph_info[i].codepoint);
|
||||
this->positions.emplace_back(glyph_pos[i].x_offset / FONT_SCALE + advance, glyph_pos[i].x_offset / FONT_SCALE + advance + x_advance - 1, glyph_pos[i].y_offset / FONT_SCALE);
|
||||
}
|
||||
int x_advance = glyph_pos[i].x_advance / FONT_SCALE;
|
||||
this->glyphs.push_back(glyph_info[i].codepoint);
|
||||
this->positions.emplace_back(glyph_pos[i].x_offset / FONT_SCALE + advance, glyph_pos[i].x_offset / FONT_SCALE + advance + x_advance - 1, glyph_pos[i].y_offset / FONT_SCALE + y_offset);
|
||||
|
||||
this->glyph_to_char.push_back(glyph_info[i].cluster);
|
||||
this->advance.push_back(x_advance);
|
||||
@@ -280,7 +323,7 @@ std::vector<ICURun> ItemizeBidi(UChar *buff, size_t length)
|
||||
UBiDiLevel level;
|
||||
ubidi_getLogicalRun(ubidi, start_pos, &logical_pos, &level);
|
||||
|
||||
runs.emplace_back(start_pos, logical_pos - start_pos, level);
|
||||
runs.emplace_back(start_pos, logical_pos - start_pos, level, USCRIPT_UNKNOWN, Font{});
|
||||
}
|
||||
|
||||
assert(static_cast<size_t>(count) == runs.size());
|
||||
@@ -311,7 +354,7 @@ std::vector<ICURun> ItemizeScript(UChar *buff, size_t length, std::vector<ICURun
|
||||
int stop_pos = std::min(script_itemizer.getScriptEnd(), cur_run->start + cur_run->length);
|
||||
assert(stop_pos - cur_pos > 0);
|
||||
|
||||
runs.emplace_back(cur_pos, stop_pos - cur_pos, cur_run->level, script_itemizer.getScriptCode());
|
||||
runs.emplace_back(cur_pos, stop_pos - cur_pos, cur_run->level, script_itemizer.getScriptCode(), Font{});
|
||||
|
||||
if (stop_pos == cur_run->start + cur_run->length) cur_run++;
|
||||
cur_pos = stop_pos;
|
||||
@@ -359,11 +402,6 @@ std::vector<ICURun> ItemizeStyle(std::vector<ICURun> &runs_current, FontMap &fon
|
||||
/* Can't layout an empty string. */
|
||||
if (length == 0) return nullptr;
|
||||
|
||||
/* Can't layout our in-built sprite fonts. */
|
||||
for (auto const &[position, font] : font_mapping) {
|
||||
if (font->fc->IsBuiltInFont()) return nullptr;
|
||||
}
|
||||
|
||||
auto runs = ItemizeBidi(buff, length);
|
||||
runs = ItemizeScript(buff, length, runs);
|
||||
runs = ItemizeStyle(runs, font_mapping);
|
||||
|
||||
@@ -1090,6 +1090,8 @@ STR_GAME_OPTIONS_GUI_SCALE_BEVELS_TOOLTIP :Check this box
|
||||
|
||||
STR_GAME_OPTIONS_GUI_FONT_SPRITE :Use traditional sprite font
|
||||
STR_GAME_OPTIONS_GUI_FONT_SPRITE_TOOLTIP :Check this box if you prefer to use the traditional fixed-size sprite font
|
||||
STR_GAME_OPTIONS_GUI_FONT_DEFAULT :Prefer default font
|
||||
STR_GAME_OPTIONS_GUI_FONT_DEFAULT_TOOLTIP :Check this box if you prefer to use the default font over discovered fallback fonts
|
||||
STR_GAME_OPTIONS_GUI_FONT_AA :Anti-alias fonts
|
||||
STR_GAME_OPTIONS_GUI_FONT_AA_TOOLTIP :Check this box to anti-alias resizable fonts
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ void CoreTextFontCache::SetFontSize(int pixels)
|
||||
Debug(fontcache, 2, "Loaded font '{}' with size {}", this->font_name, pixels);
|
||||
}
|
||||
|
||||
GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
|
||||
GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key)
|
||||
{
|
||||
assert(IsPrintable(key));
|
||||
|
||||
@@ -112,10 +112,6 @@ GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
|
||||
return glyph[0];
|
||||
}
|
||||
|
||||
if (allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
|
||||
return this->parent->MapCharToGlyph(key);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -211,29 +207,19 @@ public:
|
||||
* fallback search, use it. Otherwise, try to resolve it by font name.
|
||||
* @param fs The font size to load.
|
||||
*/
|
||||
std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype) const override
|
||||
std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span<const std::byte>) const override
|
||||
{
|
||||
if (fonttype != FontType::TrueType) return nullptr;
|
||||
|
||||
FontCacheSubSetting *settings = GetFontCacheSubSetting(fs);
|
||||
|
||||
std::string font = GetFontCacheFontName(fs);
|
||||
if (font.empty()) return nullptr;
|
||||
|
||||
CFAutoRelease<CTFontDescriptorRef> font_ref;
|
||||
|
||||
if (settings->os_handle != nullptr) {
|
||||
font_ref.reset(static_cast<CTFontDescriptorRef>(const_cast<void *>(settings->os_handle)));
|
||||
CFRetain(font_ref.get()); // Increase ref count to match a later release.
|
||||
}
|
||||
|
||||
if (!font_ref && MacOSVersionIsAtLeast(10, 6, 0)) {
|
||||
if (MacOSVersionIsAtLeast(10, 6, 0)) {
|
||||
/* Might be a font file name, try load it. */
|
||||
font_ref.reset(LoadFontFromFile(font));
|
||||
if (!font_ref) ShowInfo("Unable to load file '{}' for {} font, using default OS font selection instead", font, FontSizeToName(fs));
|
||||
}
|
||||
|
||||
if (!font_ref) {
|
||||
if (!font_ref && search) {
|
||||
CFAutoRelease<CFStringRef> name(CFStringCreateWithCString(kCFAllocatorDefault, font.c_str(), kCFStringEncodingUTF8));
|
||||
|
||||
/* Simply creating the font using CTFontCreateWithNameAndSize will *always* return
|
||||
@@ -259,7 +245,7 @@ public:
|
||||
return std::make_unique<CoreTextFontCache>(fs, std::move(font_ref), GetFontCacheFontSize(fs));
|
||||
}
|
||||
|
||||
bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) const override
|
||||
bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override
|
||||
{
|
||||
/* Determine fallback font using CoreText. This uses the language isocode
|
||||
* to find a suitable font. CoreText is available from 10.5 onwards. */
|
||||
@@ -305,7 +291,7 @@ public:
|
||||
/* 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;
|
||||
if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != fontsizes.Test(FS_MONO)) continue;
|
||||
|
||||
/* Get font name. */
|
||||
char buffer[128];
|
||||
@@ -323,8 +309,9 @@ public:
|
||||
if (name.starts_with(".") || name.starts_with("LastResort")) continue;
|
||||
|
||||
/* Save result. */
|
||||
callback->SetFontNames(settings, name);
|
||||
if (!callback->FindMissingGlyphs()) {
|
||||
FontCache::AddFallback(fontsizes, callback->GetLoadReason(), name);
|
||||
|
||||
if (callback->FindMissingGlyphs().None()) {
|
||||
Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name);
|
||||
result = true;
|
||||
break;
|
||||
@@ -335,8 +322,8 @@ public:
|
||||
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();
|
||||
FontCache::AddFallback(fontsizes, callback->GetLoadReason(), "Arial Unicode MS");
|
||||
result = callback->FindMissingGlyphs().None();
|
||||
}
|
||||
|
||||
callback->FindMissingGlyphs();
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
~CoreTextFontCache() {}
|
||||
|
||||
void ClearFontCache() override;
|
||||
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override;
|
||||
GlyphID MapCharToGlyph(char32_t key) override;
|
||||
std::string GetFontName() override { return font_name; }
|
||||
bool IsBuiltInFont() override { return false; }
|
||||
const void *GetOSHandle() override { return font.get(); }
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "string_osx.h"
|
||||
#include "../../gfx_func.h"
|
||||
#include "../../string_func.h"
|
||||
#include "../../strings_func.h"
|
||||
#include "../../core/utf8.hpp"
|
||||
@@ -52,7 +53,7 @@ extern "C" {
|
||||
/** Cached current locale. */
|
||||
static CFAutoRelease<CFLocaleRef> _osx_locale;
|
||||
/** CoreText cache for font information, cleared when OTTD changes fonts. */
|
||||
static CFAutoRelease<CTFontRef> _font_cache[FS_END];
|
||||
static std::unordered_map<FontIndex, CFAutoRelease<CTFontRef>> _font_cache;
|
||||
|
||||
|
||||
/**
|
||||
@@ -60,7 +61,6 @@ static CFAutoRelease<CTFontRef> _font_cache[FS_END];
|
||||
*/
|
||||
class CoreTextParagraphLayout : public ParagraphLayouter {
|
||||
private:
|
||||
const CoreTextParagraphLayoutFactory::CharType *text_buffer;
|
||||
ptrdiff_t length;
|
||||
const FontMap &font_map;
|
||||
|
||||
@@ -77,18 +77,18 @@ public:
|
||||
std::vector<int> glyph_to_char;
|
||||
|
||||
int total_advance = 0;
|
||||
Font *font;
|
||||
Font font;
|
||||
|
||||
public:
|
||||
CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff);
|
||||
CoreTextVisualRun(CTRunRef run, const Font &font);
|
||||
CoreTextVisualRun(CoreTextVisualRun &&other) = default;
|
||||
|
||||
std::span<const GlyphID> GetGlyphs() const override { return this->glyphs; }
|
||||
std::span<const Position> GetPositions() const override { return this->positions; }
|
||||
std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; }
|
||||
|
||||
const Font *GetFont() const override { return this->font; }
|
||||
int GetLeading() const override { return this->font->fc->GetHeight(); }
|
||||
const Font &GetFont() const override { return this->font; }
|
||||
int GetLeading() const override { return GetCharacterHeight(this->font.GetFontCache().GetSize()); }
|
||||
int GetGlyphCount() const override { return (int)this->glyphs.size(); }
|
||||
int GetAdvance() const { return this->total_advance; }
|
||||
};
|
||||
@@ -96,7 +96,7 @@ public:
|
||||
/** A single line worth of VisualRuns. */
|
||||
class CoreTextLine : public std::vector<CoreTextVisualRun>, public ParagraphLayouter::Line {
|
||||
public:
|
||||
CoreTextLine(CFAutoRelease<CTLineRef> line, const FontMap &font_mapping, const CoreTextParagraphLayoutFactory::CharType *buff)
|
||||
CoreTextLine(CFAutoRelease<CTLineRef> line, const FontMap &font_mapping)
|
||||
{
|
||||
CFArrayRef runs = CTLineGetGlyphRuns(line.get());
|
||||
for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) {
|
||||
@@ -104,9 +104,9 @@ public:
|
||||
|
||||
/* Extract font information for this run. */
|
||||
CFRange chars = CTRunGetStringRange(run);
|
||||
auto map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair<int, Font *>::first);
|
||||
const auto &map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair<int, Font>::first);
|
||||
|
||||
this->emplace_back(run, map->second, buff);
|
||||
this->emplace_back(run, map->second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
CoreTextParagraphLayout(CFAutoRelease<CTTypesetterRef> typesetter, const CoreTextParagraphLayoutFactory::CharType *buffer, ptrdiff_t len, const FontMap &font_mapping) : text_buffer(buffer), length(len), font_map(font_mapping), typesetter(std::move(typesetter))
|
||||
CoreTextParagraphLayout(CFAutoRelease<CTTypesetterRef> typesetter, ptrdiff_t len, const FontMap &font_mapping) : length(len), font_map(font_mapping), typesetter(std::move(typesetter))
|
||||
{
|
||||
this->Reflow();
|
||||
}
|
||||
@@ -137,17 +137,17 @@ public:
|
||||
|
||||
|
||||
/** Get the width of an encoded sprite font character. */
|
||||
static CGFloat SpriteFontGetWidth(void *ref_con)
|
||||
static CGFloat CustomFontGetWidth(void *ref_con)
|
||||
{
|
||||
FontSize fs = (FontSize)((size_t)ref_con >> 24);
|
||||
char32_t c = (char32_t)((size_t)ref_con & 0xFFFFFF);
|
||||
FontIndex fi = static_cast<FontIndex>(reinterpret_cast<uintptr_t>(ref_con) >> 24);
|
||||
char32_t c = static_cast<char32_t>(reinterpret_cast<uintptr_t>(ref_con) & 0xFFFFFF);
|
||||
|
||||
return GetGlyphWidth(fs, c);
|
||||
return FontCache::Get(fi)->GetGlyphWidth(c);
|
||||
}
|
||||
|
||||
static const CTRunDelegateCallbacks _sprite_font_callback = {
|
||||
kCTRunDelegateCurrentVersion, nullptr, nullptr, nullptr,
|
||||
&SpriteFontGetWidth
|
||||
&CustomFontGetWidth
|
||||
};
|
||||
|
||||
/* static */ std::unique_ptr<ParagraphLayouter> CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &font_mapping)
|
||||
@@ -158,11 +158,6 @@ static const CTRunDelegateCallbacks _sprite_font_callback = {
|
||||
ptrdiff_t length = buff_end - buff;
|
||||
if (length == 0) return nullptr;
|
||||
|
||||
/* Can't layout our in-built sprite fonts. */
|
||||
for (const auto &[position, font] : font_mapping) {
|
||||
if (font->fc->IsBuiltInFont()) return nullptr;
|
||||
}
|
||||
|
||||
/* Make attributed string with embedded font information. */
|
||||
CFAutoRelease<CFMutableAttributedStringRef> str(CFAttributedStringCreateMutable(kCFAllocatorDefault, 0));
|
||||
CFAttributedStringBeginEditing(str.get());
|
||||
@@ -179,25 +174,26 @@ static const CTRunDelegateCallbacks _sprite_font_callback = {
|
||||
for (const auto &[position, font] : font_mapping) {
|
||||
if (position - last == 0) continue;
|
||||
|
||||
CTFontRef font_handle = static_cast<CTFontRef>(font->fc->GetOSHandle());
|
||||
FontCache &fc = font.GetFontCache();
|
||||
CTFontRef font_handle = static_cast<CTFontRef>(fc.GetOSHandle());
|
||||
if (font_handle == nullptr) {
|
||||
if (!_font_cache[font->fc->GetSize()]) {
|
||||
if (!_font_cache[fc.GetIndex()]) {
|
||||
/* Cache font information. */
|
||||
CFAutoRelease<CFStringRef> font_name(CFStringCreateWithCString(kCFAllocatorDefault, font->fc->GetFontName().c_str(), kCFStringEncodingUTF8));
|
||||
_font_cache[font->fc->GetSize()].reset(CTFontCreateWithName(font_name.get(), font->fc->GetFontSize(), nullptr));
|
||||
CFAutoRelease<CFStringRef> font_name(CFStringCreateWithCString(kCFAllocatorDefault, fc.GetFontName().c_str(), kCFStringEncodingUTF8));
|
||||
_font_cache[fc.GetIndex()].reset(CTFontCreateWithName(font_name.get(), fc.GetFontSize(), nullptr));
|
||||
}
|
||||
font_handle = _font_cache[font->fc->GetSize()].get();
|
||||
font_handle = _font_cache[fc.GetIndex()].get();
|
||||
}
|
||||
CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle);
|
||||
|
||||
CGColorRef colour = CGColorCreateGenericGray((uint8_t)font->colour / 255.0f, 1.0f); // We don't care about the real colours, just that they are different.
|
||||
CGColorRef colour = CGColorCreateGenericGray((uint8_t)font.colour / 255.0f, 1.0f); // We don't care about the real colours, just that they are different.
|
||||
CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, colour);
|
||||
CGColorRelease(colour);
|
||||
|
||||
/* Install a size callback for our special private-use sprite glyphs in case the font does not provide them. */
|
||||
for (ssize_t c = last; c < position; c++) {
|
||||
if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END && font->fc->MapCharToGlyph(buff[c], false) == 0) {
|
||||
CFAutoRelease<CTRunDelegateRef> del(CTRunDelegateCreate(&_sprite_font_callback, (void *)(size_t)(buff[c] | (font->fc->GetSize() << 24))));
|
||||
/* Install a size callback for our custom fonts. */
|
||||
if (fc.IsBuiltInFont()) {
|
||||
for (ssize_t c = last; c < position; c++) {
|
||||
CFAutoRelease<CTRunDelegateRef> del(CTRunDelegateCreate(&_sprite_font_callback, reinterpret_cast<void *>(static_cast<size_t>(buff[c] | (fc.GetIndex() << 24)))));
|
||||
/* According to the official documentation, if a run delegate is used, the char should always be 0xFFFC. */
|
||||
CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacement_str.get());
|
||||
CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get());
|
||||
@@ -211,7 +207,7 @@ static const CTRunDelegateCallbacks _sprite_font_callback = {
|
||||
/* Create and return typesetter for the string. */
|
||||
CFAutoRelease<CTTypesetterRef> typesetter(CTTypesetterCreateWithAttributedString(str.get()));
|
||||
|
||||
return typesetter ? std::make_unique<CoreTextParagraphLayout>(std::move(typesetter), buff, length, font_mapping) : nullptr;
|
||||
return typesetter ? std::make_unique<CoreTextParagraphLayout>(std::move(typesetter), length, font_mapping) : nullptr;
|
||||
}
|
||||
|
||||
/* virtual */ std::unique_ptr<const ParagraphLayouter::Line> CoreTextParagraphLayout::NextLine(int max_width)
|
||||
@@ -227,10 +223,10 @@ static const CTRunDelegateCallbacks _sprite_font_callback = {
|
||||
this->cur_offset += len;
|
||||
|
||||
if (!line) return nullptr;
|
||||
return std::make_unique<CoreTextLine>(std::move(line), this->font_map, this->text_buffer);
|
||||
return std::make_unique<CoreTextLine>(std::move(line), this->font_map);
|
||||
}
|
||||
|
||||
CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff) : font(font)
|
||||
CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, const Font &font) : font(font)
|
||||
{
|
||||
this->glyphs.resize(CTRunGetGlyphCount(run));
|
||||
|
||||
@@ -247,19 +243,15 @@ CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font
|
||||
CTRunGetAdvances(run, CFRangeMake(0, 0), advs.get());
|
||||
this->positions.reserve(this->glyphs.size());
|
||||
|
||||
int y_offset = this->font.GetFontCache().GetGlyphYOffset();
|
||||
|
||||
/* Convert glyph array to our data type. At the same time, substitute
|
||||
* the proper glyphs for our private sprite glyphs. */
|
||||
auto gl = std::make_unique<CGGlyph[]>(this->glyphs.size());
|
||||
CTRunGetGlyphs(run, CFRangeMake(0, 0), gl.get());
|
||||
for (size_t i = 0; i < this->glyphs.size(); i++) {
|
||||
if (buff[this->glyph_to_char[i]] >= SCC_SPRITE_START && buff[this->glyph_to_char[i]] <= SCC_SPRITE_END && (gl[i] == 0 || gl[i] == 3)) {
|
||||
/* A glyph of 0 indicates not found, while apparently 3 is what char 0xFFFC maps to. */
|
||||
this->glyphs[i] = font->fc->MapCharToGlyph(buff[this->glyph_to_char[i]]);
|
||||
this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2); // Align sprite font to centre
|
||||
} else {
|
||||
this->glyphs[i] = gl[i];
|
||||
this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y);
|
||||
}
|
||||
this->glyphs[i] = gl[i];
|
||||
this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y + y_offset);
|
||||
}
|
||||
this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nullptr, nullptr, nullptr));
|
||||
}
|
||||
|
||||
@@ -120,6 +120,10 @@ FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face)
|
||||
}
|
||||
}
|
||||
|
||||
if (err != FT_Err_Ok) {
|
||||
ShowInfo("Unable to find '{}' font", font_name);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -139,7 +143,7 @@ static int GetPreferredWeightDistance(int weight)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback)
|
||||
bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
@@ -148,22 +152,25 @@ bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &
|
||||
auto fc_instance = AutoRelease<FcConfig, FcConfigDestroy>(FcConfigReference(nullptr));
|
||||
assert(fc_instance != nullptr);
|
||||
|
||||
/* Get set of required characters. */
|
||||
auto chars = callback->GetRequiredGlyphs(fontsizes);
|
||||
|
||||
/* 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 = language_isocode.empty() ? "" : fmt::format(":lang={}", language_isocode.substr(0, language_isocode.find('_')));
|
||||
|
||||
/* First create a pattern to match the wanted language. */
|
||||
auto pat = AutoRelease<FcPattern, FcPatternDestroy>(FcNameParse(ToFcString(lang)));
|
||||
/* We only want to know these attributes. */
|
||||
auto os = AutoRelease<FcObjectSet, FcObjectSetDestroy>(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, nullptr));
|
||||
auto os = AutoRelease<FcObjectSet, FcObjectSetDestroy>(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, FC_CHARSET, nullptr));
|
||||
/* Get the list of filenames matching the wanted language. */
|
||||
auto fs = AutoRelease<FcFontSet, FcFontSetDestroy>(FcFontList(nullptr, pat.get(), os.get()));
|
||||
|
||||
if (fs == nullptr) return ret;
|
||||
|
||||
int best_weight = -1;
|
||||
const char *best_font = nullptr;
|
||||
std::string best_font;
|
||||
int best_index = 0;
|
||||
|
||||
for (FcPattern *font : std::span(fs->fonts, fs->nfont)) {
|
||||
@@ -174,7 +181,7 @@ bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &
|
||||
/* Get a font with the right spacing .*/
|
||||
int value = 0;
|
||||
FcPatternGetInteger(font, FC_SPACING, 0, &value);
|
||||
if (callback->Monospace() != (value == FC_MONO) && value != FC_DUAL) continue;
|
||||
if (fontsizes.Test(FS_MONO) != (value == FC_MONO) && value != FC_DUAL) continue;
|
||||
|
||||
/* Do not use those that explicitly say they're slanted. */
|
||||
FcPatternGetInteger(font, FC_SLANT, 0, &value);
|
||||
@@ -185,26 +192,32 @@ bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &
|
||||
int weight = GetPreferredWeightDistance(value);
|
||||
if (best_weight != -1 && weight > best_weight) continue;
|
||||
|
||||
size_t matching_chars = 0;
|
||||
FcCharSet *charset;
|
||||
FcPatternGetCharSet(font, FC_CHARSET, 0, &charset);
|
||||
for (const char32_t &c : chars) {
|
||||
if (FcCharSetHasChar(charset, c)) ++matching_chars;
|
||||
}
|
||||
|
||||
if (matching_chars < chars.size()) {
|
||||
Debug(fontcache, 1, "Font \"{}\" misses {} glyphs", reinterpret_cast<const char *>(file), chars.size() - matching_chars);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Possible match based on attributes, get index. */
|
||||
int32_t index;
|
||||
res = FcPatternGetInteger(font, FC_INDEX, 0, &index);
|
||||
if (res != FcResultMatch) continue;
|
||||
|
||||
callback->SetFontNames(settings, FromFcString(file), &index);
|
||||
|
||||
bool missing = callback->FindMissingGlyphs();
|
||||
Debug(fontcache, 1, "Font \"{}\" misses{} glyphs", FromFcString(file), missing ? "" : " no");
|
||||
|
||||
if (!missing) {
|
||||
best_weight = weight;
|
||||
best_font = FromFcString(file);
|
||||
best_index = index;
|
||||
}
|
||||
best_weight = weight;
|
||||
best_font = FromFcString(file);
|
||||
best_index = index;
|
||||
}
|
||||
|
||||
if (best_font == nullptr) return false;
|
||||
if (best_font.empty()) return false;
|
||||
|
||||
FontCache::AddFallbackWithHandle(fontsizes, callback->GetLoadReason(), best_font, best_index);
|
||||
FontCache::LoadFontCaches(fontsizes);
|
||||
|
||||
callback->SetFontNames(settings, best_font, &best_index);
|
||||
FontCache::LoadFontCaches(callback->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face);
|
||||
|
||||
bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback);
|
||||
bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback);
|
||||
|
||||
#endif /* WITH_FONTCONFIG */
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
#include "../../safeguards.h"
|
||||
|
||||
struct EFCParam {
|
||||
FontCacheSettings *settings;
|
||||
LOCALESIGNATURE locale;
|
||||
LOCALESIGNATURE locale;
|
||||
FontSizes fontsizes;
|
||||
MissingGlyphSearcher *callback;
|
||||
std::vector<std::wstring> fonts;
|
||||
|
||||
@@ -58,7 +58,7 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
|
||||
/* Don't use SYMBOL fonts */
|
||||
if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1;
|
||||
/* Use monospaced fonts when asked for it. */
|
||||
if (info->callback->Monospace() && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1;
|
||||
if (info->fontsizes.Test(FS_MONO) && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1;
|
||||
|
||||
/* The font has to have at least one of the supported locales to be usable. */
|
||||
auto check_bitfields = [&]() {
|
||||
@@ -77,8 +77,8 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
|
||||
char font_name[MAX_PATH];
|
||||
convert_from_fs(logfont->elfFullName, font_name);
|
||||
|
||||
info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont);
|
||||
if (info->callback->FindMissingGlyphs()) return 1;
|
||||
FontCache::AddFallbackWithHandle(info->fontsizes, info->callback->GetLoadReason(), font_name, logfont->elfLogFont);
|
||||
if (info->callback->FindMissingGlyphs().None()) return 1;
|
||||
Debug(fontcache, 1, "Fallback font: {}", font_name);
|
||||
return 0; // stop enumerating
|
||||
}
|
||||
@@ -158,7 +158,7 @@ void Win32FontCache::SetFontSize(int pixels)
|
||||
|
||||
this->fontname = FS2OTTD((LPWSTR)((BYTE *)otm + (ptrdiff_t)otm->otmpFaceName));
|
||||
|
||||
Debug(fontcache, 2, "Loaded font '{}' with size {}", this->fontname, pixels);
|
||||
Debug(fontcache, 2, "Win32FontCache: Loaded font '{}' with size {}", this->fontname, pixels);
|
||||
delete[] (BYTE*)otm;
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ void Win32FontCache::ClearFontCache()
|
||||
return this->SetGlyphPtr(key, std::move(new_glyph)).GetSprite();
|
||||
}
|
||||
|
||||
/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
|
||||
/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(char32_t key)
|
||||
{
|
||||
assert(IsPrintable(key));
|
||||
|
||||
@@ -262,7 +262,7 @@ void Win32FontCache::ClearFontCache()
|
||||
GetGlyphIndicesW(this->dc, chars, key >= 0x010000U ? 2 : 1, glyphs, GGI_MARK_NONEXISTING_GLYPHS);
|
||||
|
||||
if (glyphs[0] != 0xFFFF) return glyphs[0];
|
||||
return allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END ? this->parent->MapCharToGlyph(key) : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
class Win32FontCacheFactory : FontCacheFactory {
|
||||
@@ -270,32 +270,28 @@ public:
|
||||
Win32FontCacheFactory() : FontCacheFactory("win32", "Win32 font loader") {}
|
||||
|
||||
/**
|
||||
* Loads the GDI font.
|
||||
* If a GDI font description is present, e.g. from the automatic font
|
||||
* fallback search, use it. Otherwise, try to resolve it by font name.
|
||||
* @param fs The font size to load.
|
||||
*/
|
||||
std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype) const override
|
||||
* Loads the GDI font.
|
||||
* If a GDI font description is present, e.g. from the automatic font
|
||||
* fallback search, use it. Otherwise, try to resolve it by font name.
|
||||
* @param fs The font size to load.
|
||||
*/
|
||||
std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span<const std::byte> os_handle) const override
|
||||
{
|
||||
if (fonttype != FontType::TrueType) return nullptr;
|
||||
|
||||
FontCacheSubSetting *settings = GetFontCacheSubSetting(fs);
|
||||
|
||||
std::string font = GetFontCacheFontName(fs);
|
||||
if (font.empty()) return nullptr;
|
||||
|
||||
LOGFONT logfont{};
|
||||
logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH;
|
||||
logfont.lfCharSet = DEFAULT_CHARSET;
|
||||
logfont.lfOutPrecision = OUT_OUTLINE_PRECIS;
|
||||
logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
||||
|
||||
if (settings->os_handle != nullptr) {
|
||||
logfont = *(const LOGFONT *)settings->os_handle;
|
||||
if (!os_handle.empty()) {
|
||||
logfont = *reinterpret_cast<const LOGFONT *>(os_handle.data());
|
||||
} else if (font.find('.') != std::string::npos) {
|
||||
/* Might be a font file name, try load it. */
|
||||
if (!TryLoadFontFromFile(font, logfont)) {
|
||||
ShowInfo("Unable to load file '{}' for {} font, using default windows font selection instead", font, FontSizeToName(fs));
|
||||
if (!search) return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +303,7 @@ public:
|
||||
return LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font);
|
||||
}
|
||||
|
||||
bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) const override
|
||||
bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override
|
||||
{
|
||||
Debug(fontcache, 1, "Trying fallback fonts");
|
||||
EFCParam langInfo;
|
||||
@@ -317,7 +313,7 @@ public:
|
||||
Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode);
|
||||
return false;
|
||||
}
|
||||
langInfo.settings = settings;
|
||||
langInfo.fontsizes = fontsizes;
|
||||
langInfo.callback = callback;
|
||||
|
||||
LOGFONT font;
|
||||
|
||||
@@ -37,11 +37,11 @@ public:
|
||||
Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels);
|
||||
~Win32FontCache();
|
||||
void ClearFontCache() override;
|
||||
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override;
|
||||
GlyphID MapCharToGlyph(char32_t key) override;
|
||||
std::string GetFontName() override { return this->fontname; }
|
||||
const void *GetOSHandle() override { return &this->logfont; }
|
||||
};
|
||||
|
||||
void LoadWin32Font(FontSize fs);
|
||||
void LoadWin32Font(FontSize fs, bool search, const std::string &font_name, std::span<const std::byte> os_handle);
|
||||
|
||||
#endif /* FONT_WIN32_H */
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "../../stdafx.h"
|
||||
#include "../../debug.h"
|
||||
#include "string_uniscribe.h"
|
||||
#include "../../gfx_func.h"
|
||||
#include "../../gfx_layout_fallback.h"
|
||||
#include "../../language.h"
|
||||
#include "../../strings_func.h"
|
||||
#include "../../string_func.h"
|
||||
@@ -29,7 +31,7 @@
|
||||
|
||||
|
||||
/** Uniscribe cache for internal font information, cleared when OTTD changes fonts. */
|
||||
static SCRIPT_CACHE _script_cache[FS_END];
|
||||
static std::map<FontIndex, SCRIPT_CACHE> _script_cache;
|
||||
|
||||
/**
|
||||
* Contains all information about a run of characters. A run are consecutive
|
||||
@@ -38,7 +40,7 @@ static SCRIPT_CACHE _script_cache[FS_END];
|
||||
struct UniscribeRun {
|
||||
int pos;
|
||||
int len;
|
||||
Font *font;
|
||||
Font font;
|
||||
|
||||
std::vector<GlyphID> ft_glyphs;
|
||||
|
||||
@@ -51,7 +53,9 @@ struct UniscribeRun {
|
||||
std::vector<GOFFSET> offsets;
|
||||
int total_advance;
|
||||
|
||||
UniscribeRun(int pos, int len, Font *font, SCRIPT_ANALYSIS &sa) : pos(pos), len(len), font(font), sa(sa) {}
|
||||
UniscribeRun(int pos, int len, const Font &font, SCRIPT_ANALYSIS &sa) : pos(pos), len(len), font(font), sa(sa) {}
|
||||
|
||||
void FallbackShape(const UniscribeParagraphLayoutFactory::CharType *buff);
|
||||
};
|
||||
|
||||
/** Break a string into language formatting ranges. */
|
||||
@@ -81,7 +85,7 @@ public:
|
||||
int start_pos;
|
||||
int total_advance;
|
||||
int num_glyphs;
|
||||
Font *font;
|
||||
Font font;
|
||||
|
||||
mutable std::vector<int> glyph_to_char;
|
||||
|
||||
@@ -93,8 +97,8 @@ public:
|
||||
std::span<const Position> GetPositions() const override { return this->positions; }
|
||||
std::span<const int> GetGlyphToCharMap() const override;
|
||||
|
||||
const Font *GetFont() const override { return this->font; }
|
||||
int GetLeading() const override { return this->font->fc->GetHeight(); }
|
||||
const Font &GetFont() const override { return this->font; }
|
||||
int GetLeading() const override { return GetCharacterHeight(this->font.GetFontCache().GetSize()); }
|
||||
int GetGlyphCount() const override { return this->num_glyphs; }
|
||||
int GetAdvance() const { return this->total_advance; }
|
||||
};
|
||||
@@ -130,31 +134,71 @@ public:
|
||||
std::unique_ptr<const Line> NextLine(int max_width) override;
|
||||
};
|
||||
|
||||
void UniscribeResetScriptCache(FontSize size)
|
||||
void UniscribeResetScriptCache(FontSize)
|
||||
{
|
||||
if (_script_cache[size] != nullptr) {
|
||||
ScriptFreeCache(&_script_cache[size]);
|
||||
_script_cache[size] = nullptr;
|
||||
for (auto &sc : _script_cache) {
|
||||
ScriptFreeCache(&sc.second);
|
||||
}
|
||||
_script_cache.clear();
|
||||
}
|
||||
|
||||
/** Load the matching native Windows font. */
|
||||
static HFONT HFontFromFont(Font *font)
|
||||
static HFONT HFontFromFont(const Font &font)
|
||||
{
|
||||
if (font->fc->GetOSHandle() != nullptr) return CreateFontIndirect(reinterpret_cast<PLOGFONT>(const_cast<void *>(font->fc->GetOSHandle())));
|
||||
FontCache &fc = font.GetFontCache();
|
||||
|
||||
if (fc.GetOSHandle() != nullptr) return CreateFontIndirect(reinterpret_cast<PLOGFONT>(const_cast<void *>(fc.GetOSHandle())));
|
||||
|
||||
LOGFONT logfont{};
|
||||
logfont.lfHeight = font->fc->GetHeight();
|
||||
logfont.lfHeight = fc.GetHeight();
|
||||
logfont.lfWeight = FW_NORMAL;
|
||||
logfont.lfCharSet = DEFAULT_CHARSET;
|
||||
convert_to_fs(font->fc->GetFontName(), logfont.lfFaceName);
|
||||
convert_to_fs(fc.GetFontName(), logfont.lfFaceName);
|
||||
|
||||
return CreateFontIndirect(&logfont);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually shape a run for built-in non-truetype fonts.
|
||||
* Similar to but not quite the same as \a ICURun::FallbackShape.
|
||||
* @param buff The complete buffer of the run.
|
||||
*/
|
||||
void UniscribeRun::FallbackShape(const UniscribeParagraphLayoutFactory::CharType *buff)
|
||||
{
|
||||
FontCache &fc = this->font.GetFontCache();
|
||||
|
||||
this->glyphs.reserve(this->len);
|
||||
|
||||
/* Read each UTF-16 character, mapping to an appropriate glyph. */
|
||||
for (int i = this->pos; i < this->pos + this->len; i += Utf16IsLeadSurrogate(buff[i]) ? 2 : 1) {
|
||||
char32_t c = Utf16DecodeChar(reinterpret_cast<const uint16_t *>(buff + i));
|
||||
if (this->sa.fRTL) c = SwapRtlPairedCharacters(c);
|
||||
this->glyphs.emplace_back(fc.MapCharToGlyph(c));
|
||||
}
|
||||
|
||||
/* Reverse the sequence if this run is RTL. */
|
||||
if (this->sa.fRTL) {
|
||||
std::reverse(std::begin(this->glyphs), std::end(this->glyphs));
|
||||
}
|
||||
|
||||
this->offsets.reserve(this->glyphs.size());
|
||||
|
||||
/* Set positions of each glyph. */
|
||||
int y_offset = fc.GetGlyphYOffset();
|
||||
int advance = 0;
|
||||
for (const GlyphID glyph : this->glyphs) {
|
||||
this->offsets.emplace_back(advance, y_offset);
|
||||
int x_advance = fc.GetGlyphWidth(glyph);
|
||||
this->advances.push_back(x_advance);
|
||||
advance += x_advance;
|
||||
}
|
||||
}
|
||||
|
||||
/** Determine the glyph positions for a run. */
|
||||
static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *buff, UniscribeRun &range)
|
||||
{
|
||||
FontCache &fc = range.font.GetFontCache();
|
||||
|
||||
/* Initial size guess for the number of glyphs recommended by Uniscribe. */
|
||||
range.glyphs.resize(range.len * 3 / 2 + 16);
|
||||
range.vis_attribs.resize(range.glyphs.size());
|
||||
@@ -166,10 +210,15 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b
|
||||
HFONT old_font = nullptr;
|
||||
HFONT cur_font = nullptr;
|
||||
|
||||
if (fc.IsBuiltInFont()) {
|
||||
range.FallbackShape(buff);
|
||||
return true;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
/* Shape the text run by determining the glyphs needed for display. */
|
||||
int glyphs_used = 0;
|
||||
HRESULT hr = ScriptShape(temp_dc, &_script_cache[range.font->fc->GetSize()], buff + range.pos, range.len, (int)range.glyphs.size(), &range.sa, &range.glyphs[0], &range.char_to_glyph[0], &range.vis_attribs[0], &glyphs_used);
|
||||
HRESULT hr = ScriptShape(temp_dc, &_script_cache[fc.GetIndex()], buff + range.pos, range.len, (int)range.glyphs.size(), &range.sa, &range.glyphs[0], &range.char_to_glyph[0], &range.vis_attribs[0], &glyphs_used);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
range.glyphs.resize(glyphs_used);
|
||||
@@ -179,7 +228,7 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b
|
||||
ABC abc;
|
||||
range.advances.resize(range.glyphs.size());
|
||||
range.offsets.resize(range.glyphs.size());
|
||||
hr = ScriptPlace(temp_dc, &_script_cache[range.font->fc->GetSize()], &range.glyphs[0], (int)range.glyphs.size(), &range.vis_attribs[0], &range.sa, &range.advances[0], &range.offsets[0], &abc);
|
||||
hr = ScriptPlace(temp_dc, &_script_cache[fc.GetIndex()], &range.glyphs[0], (int)range.glyphs.size(), &range.vis_attribs[0], &range.sa, &range.advances[0], &range.offsets[0], &abc);
|
||||
if (SUCCEEDED(hr)) {
|
||||
/* We map our special sprite chars to values that don't fit into a WORD. Copy the glyphs
|
||||
* into a new vector and query the real glyph to use for these special chars. */
|
||||
@@ -187,22 +236,12 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b
|
||||
for (size_t g_id = 0; g_id < range.glyphs.size(); g_id++) {
|
||||
range.ft_glyphs[g_id] = range.glyphs[g_id];
|
||||
}
|
||||
for (int i = 0; i < range.len; i++) {
|
||||
if (buff[range.pos + i] >= SCC_SPRITE_START && buff[range.pos + i] <= SCC_SPRITE_END) {
|
||||
auto pos = range.char_to_glyph[i];
|
||||
if (range.ft_glyphs[pos] == 0) { // Font doesn't have our special glyph, so remap.
|
||||
range.ft_glyphs[pos] = range.font->fc->MapCharToGlyph(buff[range.pos + i]);
|
||||
range.offsets[pos].dv = (range.font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(range.font->fc->GetSize()))) / 2; // Align sprite font to centre
|
||||
range.advances[pos] = range.font->fc->GetGlyphWidth(range.ft_glyphs[pos]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
range.total_advance = 0;
|
||||
for (size_t i = 0; i < range.advances.size(); i++) {
|
||||
#ifdef WITH_FREETYPE
|
||||
/* FreeType and GDI/Uniscribe seems to occasionally disagree over the width of a glyph. */
|
||||
if (range.advances[i] > 0 && range.ft_glyphs[i] != 0xFFFF) range.advances[i] = range.font->fc->GetGlyphWidth(range.ft_glyphs[i]);
|
||||
if (range.advances[i] > 0 && range.glyphs[i] != 0xFFFF) range.advances[i] = fc.GetGlyphWidth(range.glyphs[i]);
|
||||
#endif
|
||||
range.total_advance += range.advances[i];
|
||||
}
|
||||
@@ -280,11 +319,6 @@ static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutF
|
||||
/* Can't layout an empty string. */
|
||||
if (length == 0) return nullptr;
|
||||
|
||||
/* Can't layout our in-built sprite fonts. */
|
||||
for (auto const &[position, font] : font_mapping) {
|
||||
if (font->fc->IsBuiltInFont()) return nullptr;
|
||||
}
|
||||
|
||||
/* Itemize text. */
|
||||
std::vector<SCRIPT_ITEM> items = UniscribeItemizeString(buff, length);
|
||||
if (items.empty()) return nullptr;
|
||||
|
||||
@@ -658,6 +658,9 @@ struct GameOptionsWindow : Window {
|
||||
case WID_GO_GUI_FONT_SPRITE_TEXT:
|
||||
return GetToggleString(STR_GAME_OPTIONS_GUI_FONT_SPRITE, WID_GO_GUI_FONT_SPRITE);
|
||||
|
||||
case WID_GO_GUI_FONT_DEFAULT_TEXT:
|
||||
return GetToggleString(STR_GAME_OPTIONS_GUI_FONT_DEFAULT, WID_GO_GUI_FONT_DEFAULT);
|
||||
|
||||
case WID_GO_GUI_FONT_AA_TEXT:
|
||||
return GetToggleString(STR_GAME_OPTIONS_GUI_FONT_AA, WID_GO_GUI_FONT_AA);
|
||||
|
||||
@@ -928,6 +931,18 @@ struct GameOptionsWindow : Window {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAS_TRUETYPE_FONT
|
||||
static void ReloadFonts()
|
||||
{
|
||||
FontCache::LoadFontCaches(FONTSIZES_ALL);
|
||||
FontCache::ClearFontCaches(FONTSIZES_ALL);
|
||||
CheckForMissingGlyphs();
|
||||
SetupWidgetDimensions();
|
||||
UpdateAllVirtCoords();
|
||||
ReInitAllWindows(true);
|
||||
}
|
||||
#endif /* HAS_TRUETYPE_FONT */
|
||||
|
||||
void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
|
||||
{
|
||||
if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_CONTENT_END) {
|
||||
@@ -1038,13 +1053,15 @@ struct GameOptionsWindow : Window {
|
||||
this->SetWidgetLoweredState(WID_GO_GUI_FONT_SPRITE, _fcsettings.prefer_sprite);
|
||||
this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA, _fcsettings.prefer_sprite);
|
||||
this->SetDirty();
|
||||
ReloadFonts();
|
||||
break;
|
||||
|
||||
FontCache::LoadFontCaches(FONTSIZES_ALL);
|
||||
FontCache::ClearFontCaches(FONTSIZES_ALL);
|
||||
CheckForMissingGlyphs();
|
||||
SetupWidgetDimensions();
|
||||
UpdateAllVirtCoords();
|
||||
ReInitAllWindows(true);
|
||||
case WID_GO_GUI_FONT_DEFAULT:
|
||||
_fcsettings.prefer_default = !_fcsettings.prefer_default;
|
||||
|
||||
this->SetWidgetLoweredState(WID_GO_GUI_FONT_DEFAULT, _fcsettings.prefer_default);
|
||||
this->SetDirty();
|
||||
ReloadFonts();
|
||||
break;
|
||||
|
||||
case WID_GO_GUI_FONT_AA:
|
||||
@@ -1542,6 +1559,7 @@ struct GameOptionsWindow : Window {
|
||||
this->SetWidgetLoweredState(WID_GO_GUI_SCALE_BEVEL_BUTTON, _settings_client.gui.scale_bevels);
|
||||
#ifdef HAS_TRUETYPE_FONT
|
||||
this->SetWidgetLoweredState(WID_GO_GUI_FONT_SPRITE, _fcsettings.prefer_sprite);
|
||||
this->SetWidgetLoweredState(WID_GO_GUI_FONT_DEFAULT, _fcsettings.prefer_default);
|
||||
this->SetWidgetLoweredState(WID_GO_GUI_FONT_AA, _fcsettings.global_aa);
|
||||
this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA, _fcsettings.prefer_sprite);
|
||||
#endif /* HAS_TRUETYPE_FONT */
|
||||
@@ -1672,6 +1690,10 @@ static constexpr std::initializer_list<NWidgetPart> _nested_game_options_widgets
|
||||
NWidget(WWT_BOOLBTN, GAME_OPTIONS_BACKGROUND, WID_GO_GUI_FONT_SPRITE), SetAlternateColourTip(GAME_OPTIONS_BUTTON, STR_GAME_OPTIONS_GUI_FONT_SPRITE_TOOLTIP),
|
||||
NWidget(WWT_TEXT, INVALID_COLOUR, WID_GO_GUI_FONT_SPRITE_TEXT), SetFill(1, 0), SetResize(1, 0), SetTextStyle(GAME_OPTIONS_LABEL),
|
||||
EndContainer(),
|
||||
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
|
||||
NWidget(WWT_BOOLBTN, GAME_OPTIONS_BACKGROUND, WID_GO_GUI_FONT_DEFAULT), SetAlternateColourTip(GAME_OPTIONS_BUTTON, STR_GAME_OPTIONS_GUI_FONT_DEFAULT_TOOLTIP),
|
||||
NWidget(WWT_TEXT, INVALID_COLOUR, WID_GO_GUI_FONT_DEFAULT_TEXT), SetFill(1, 0), SetResize(1, 0), SetTextStyle(GAME_OPTIONS_LABEL),
|
||||
EndContainer(),
|
||||
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
|
||||
NWidget(WWT_BOOLBTN, GAME_OPTIONS_BACKGROUND, WID_GO_GUI_FONT_AA), SetAlternateColourTip(GAME_OPTIONS_BUTTON, STR_GAME_OPTIONS_GUI_FONT_AA_TOOLTIP),
|
||||
NWidget(WWT_TEXT, INVALID_COLOUR, WID_GO_GUI_FONT_AA_TEXT), SetFill(1, 0), SetResize(1, 0), SetTextStyle(GAME_OPTIONS_LABEL),
|
||||
|
||||
@@ -2274,32 +2274,56 @@ std::string_view GetCurrentLanguageIsoCode()
|
||||
|
||||
/**
|
||||
* Check whether there are glyphs missing in the current language.
|
||||
* @return If glyphs are missing, return \c true, else return \c false.
|
||||
* @return Bit mask of font sizes have any missing glyphs.
|
||||
*/
|
||||
bool MissingGlyphSearcher::FindMissingGlyphs()
|
||||
FontSizes MissingGlyphSearcher::FindMissingGlyphs()
|
||||
{
|
||||
FontCache::LoadFontCaches(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
|
||||
FontCache::LoadFontCaches(this->fontsizes);
|
||||
|
||||
FontSizes bad_fontsizes{};
|
||||
|
||||
for (FontSize size : this->fontsizes) {
|
||||
auto set = this->GetRequiredGlyphs(size);
|
||||
if (set.empty()) continue;
|
||||
|
||||
Debug(fontcache, 1, "Missing {} glyphs in {} font size", set.size(), FontSizeToName(size));
|
||||
bad_fontsizes.Set(size);
|
||||
}
|
||||
|
||||
return bad_fontsizes;
|
||||
}
|
||||
|
||||
std::set<char32_t> BaseStringMissingGlyphSearcher::GetRequiredGlyphs(FontSizes fontsizes)
|
||||
{
|
||||
std::set<char32_t> glyphs{};
|
||||
|
||||
this->Reset();
|
||||
for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
|
||||
FontSize size = this->DefaultSize();
|
||||
FontCache *fc = FontCache::Get(size);
|
||||
for (char32_t c : Utf8View(*text)) {
|
||||
if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
|
||||
size = (FontSize)(c - SCC_FIRST_FONT);
|
||||
fc = FontCache::Get(size);
|
||||
} else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) {
|
||||
/* The character is printable, but not in the normal font. This is the case we were testing for. */
|
||||
Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", static_cast<uint32_t>(c), FontSizeToName(size));
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END)) continue;
|
||||
if (!IsPrintable(c) || IsTextDirectionChar(c)) continue;
|
||||
if (fontsizes.Test(size)) continue;
|
||||
if (FontCache::GetFontIndexForCharacter(size, c) != INVALID_FONT_INDEX) continue;
|
||||
|
||||
glyphs.insert(c);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
return glyphs;
|
||||
}
|
||||
|
||||
/** Helper for searching through the language pack. */
|
||||
class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
|
||||
class LanguagePackGlyphSearcher : public BaseStringMissingGlyphSearcher {
|
||||
public:
|
||||
LanguagePackGlyphSearcher() : BaseStringMissingGlyphSearcher({FS_NORMAL, FS_SMALL, FS_LARGE}) {}
|
||||
|
||||
private:
|
||||
uint i; ///< Iterator for the primary language tables.
|
||||
uint j; ///< Iterator for the secondary language tables.
|
||||
|
||||
@@ -2328,26 +2352,9 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Monospace() override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data) override
|
||||
{
|
||||
#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
|
||||
settings->small.font = font_name;
|
||||
settings->medium.font = font_name;
|
||||
settings->large.font = font_name;
|
||||
|
||||
settings->small.os_handle = os_data;
|
||||
settings->medium.os_handle = os_data;
|
||||
settings->large.os_handle = os_data;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the currently loaded language pack
|
||||
* uses characters that the currently loaded font
|
||||
@@ -2364,20 +2371,21 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
|
||||
{
|
||||
static LanguagePackGlyphSearcher pack_searcher;
|
||||
if (searcher == nullptr) searcher = &pack_searcher;
|
||||
bool bad_font = searcher->FindMissingGlyphs();
|
||||
|
||||
for (FontSize size : searcher->fontsizes) {
|
||||
GetFontCacheSubSetting(size)->fallback_fonts.clear();
|
||||
}
|
||||
|
||||
FontSizes fontsizes = searcher->FindMissingGlyphs();
|
||||
bool bad_font = fontsizes.Any();
|
||||
|
||||
#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
|
||||
if (bad_font) {
|
||||
/* We found an unprintable character... lets try whether we can find
|
||||
* a fallback font that can print the characters in the current language. */
|
||||
bool any_font_configured = !_fcsettings.medium.font.empty();
|
||||
FontCacheSettings backup = _fcsettings;
|
||||
|
||||
_fcsettings.mono.os_handle = nullptr;
|
||||
_fcsettings.medium.os_handle = nullptr;
|
||||
|
||||
bad_font = !FontProviderManager::FindFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher);
|
||||
|
||||
_fcsettings = std::move(backup);
|
||||
bad_font = !FontProviderManager::FindFallbackFont(_langpack.langpack->isocode, fontsizes, searcher);
|
||||
|
||||
if (!bad_font && any_font_configured) {
|
||||
/* If the user configured a bad font, and we found a better one,
|
||||
@@ -2394,7 +2402,7 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
|
||||
/* Our fallback font does miss characters too, so keep the
|
||||
* user chosen font as that is more likely to be any good than
|
||||
* the wild guess we made */
|
||||
FontCache::LoadFontCaches(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
|
||||
FontCache::LoadFontCaches(fontsizes);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -2411,12 +2419,12 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
|
||||
ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING);
|
||||
|
||||
/* Reset the font width */
|
||||
LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
|
||||
LoadStringWidthTable(fontsizes);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update the font with cache */
|
||||
LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
|
||||
LoadStringWidthTable(searcher->fontsizes);
|
||||
|
||||
#if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
|
||||
/*
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#ifndef STRINGS_FUNC_H
|
||||
#define STRINGS_FUNC_H
|
||||
|
||||
#include "fontcache.h"
|
||||
#include "strings_type.h"
|
||||
#include "string_type.h"
|
||||
#include "gfx_type.h"
|
||||
@@ -158,9 +159,33 @@ EncodedString GetEncodedString(StringID string, const Args&... args)
|
||||
*/
|
||||
class MissingGlyphSearcher {
|
||||
public:
|
||||
FontSizes fontsizes; ///< Font sizes to search for.
|
||||
|
||||
MissingGlyphSearcher(FontSizes fontsizes) : fontsizes(fontsizes) {}
|
||||
|
||||
/** Make sure everything gets destructed right. */
|
||||
virtual ~MissingGlyphSearcher() = default;
|
||||
|
||||
/**
|
||||
* Test if any glyphs are missing.
|
||||
* @return Font sizes which have missing glyphs.
|
||||
*/
|
||||
FontSizes FindMissingGlyphs();
|
||||
|
||||
virtual FontLoadReason GetLoadReason() = 0;
|
||||
|
||||
/**
|
||||
* Get set of glyphs required for the current language.
|
||||
* @param fontsizes Font sizes to test.
|
||||
* @return Set of required glyphs.
|
||||
**/
|
||||
virtual std::set<char32_t> GetRequiredGlyphs(FontSizes fontsizes) = 0;
|
||||
};
|
||||
|
||||
class BaseStringMissingGlyphSearcher : public MissingGlyphSearcher {
|
||||
public:
|
||||
BaseStringMissingGlyphSearcher(FontSizes fontsizes) : MissingGlyphSearcher(fontsizes) {}
|
||||
|
||||
/**
|
||||
* Get the next string to search through.
|
||||
* @return The next string or nullopt if there is none.
|
||||
@@ -178,23 +203,11 @@ public:
|
||||
*/
|
||||
virtual void Reset() = 0;
|
||||
|
||||
/**
|
||||
* Whether to search for a monospace font or not.
|
||||
* @return True if searching for monospace.
|
||||
*/
|
||||
virtual bool Monospace() = 0;
|
||||
FontLoadReason GetLoadReason() override { return FontLoadReason::LanguageFallback; }
|
||||
|
||||
/**
|
||||
* Set the right font names.
|
||||
* @param settings The settings to modify.
|
||||
* @param font_name The new font name.
|
||||
* @param os_data Opaque pointer to OS-specific data.
|
||||
*/
|
||||
virtual void SetFontNames(struct FontCacheSettings *settings, std::string_view font_name, const void *os_data = nullptr) = 0;
|
||||
|
||||
bool FindMissingGlyphs();
|
||||
std::set<char32_t> GetRequiredGlyphs(FontSizes fontsizes) override;
|
||||
};
|
||||
|
||||
void CheckForMissingGlyphs(MissingGlyphSearcher *search = nullptr);
|
||||
void CheckForMissingGlyphs(MissingGlyphSearcher *searcher = nullptr);
|
||||
|
||||
#endif /* STRINGS_FUNC_H */
|
||||
|
||||
@@ -298,10 +298,16 @@ void SurveyConfiguration(nlohmann::json &survey)
|
||||
*/
|
||||
void SurveyFont(nlohmann::json &survey)
|
||||
{
|
||||
survey["small"] = FontCache::Get(FS_SMALL)->GetFontName();
|
||||
survey["medium"] = FontCache::Get(FS_NORMAL)->GetFontName();
|
||||
survey["large"] = FontCache::Get(FS_LARGE)->GetFontName();
|
||||
survey["mono"] = FontCache::Get(FS_MONO)->GetFontName();
|
||||
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
|
||||
const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
|
||||
auto &font = survey[std::string(FontSizeToName(fs))];
|
||||
font["configured"]["font"] = setting->font;
|
||||
font["configured"]["size"] = setting->size;
|
||||
}
|
||||
for (const auto &fc : FontCache::Get()) {
|
||||
auto &font = survey[std::string(FontSizeToName(fc->GetSize()))];
|
||||
font["active"].push_back(fc->GetFontName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -259,6 +259,12 @@ name = ""prefer_sprite_font""
|
||||
var = _fcsettings.prefer_sprite
|
||||
def = false
|
||||
|
||||
[SDTG_BOOL]
|
||||
ifdef = HAS_TRUETYPE_FONT
|
||||
name = ""prefer_default_font""
|
||||
var = _fcsettings.prefer_default
|
||||
def = false
|
||||
|
||||
[SDTG_VAR]
|
||||
name = ""sprite_cache_size_px""
|
||||
type = SLE_UINT
|
||||
|
||||
@@ -25,15 +25,16 @@ public:
|
||||
const Sprite *GetGlyph(GlyphID) override { return nullptr; }
|
||||
uint GetGlyphWidth(GlyphID) override { return this->height / 2; }
|
||||
bool GetDrawGlyphShadow() override { return false; }
|
||||
GlyphID MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback = true) override { return key; }
|
||||
GlyphID MapCharToGlyph(char32_t key) override { return key; }
|
||||
std::string GetFontName() override { return "mock"; }
|
||||
bool IsBuiltInFont() override { return true; }
|
||||
|
||||
static void InitializeFontCaches()
|
||||
{
|
||||
FontCache::caches.clear();
|
||||
for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) {
|
||||
if (FontCache::Get(fs) != nullptr) continue;
|
||||
FontCache::Register(std::make_unique<MockFontCache>(fs));
|
||||
FontCache::Register(std::make_unique<MockFontCache>(fs), FontLoadReason::Default);
|
||||
FontCache::UpdateCharacterHeight(fs);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -83,7 +83,7 @@ static WindowDesc _textfile_desc(
|
||||
_nested_textfile_widgets
|
||||
);
|
||||
|
||||
TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), file_type(file_type)
|
||||
TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), BaseStringMissingGlyphSearcher(FS_MONO), file_type(file_type)
|
||||
{
|
||||
/* Init of nested tree is deferred.
|
||||
* TextfileWindow::ConstructWindow must be called by the inheriting window. */
|
||||
@@ -754,19 +754,6 @@ bool TextfileWindow::IsTextWrapped() const
|
||||
return this->lines[this->search_iterator++].text;
|
||||
}
|
||||
|
||||
/* virtual */ bool TextfileWindow::Monospace()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* virtual */ void TextfileWindow::SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data)
|
||||
{
|
||||
#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
|
||||
settings->mono.font = font_name;
|
||||
settings->mono.os_handle = os_data;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(WITH_ZLIB)
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
std::optional<std::string> GetTextfile(TextfileType type, Subdirectory dir, std::string_view filename);
|
||||
|
||||
/** Window for displaying a textfile */
|
||||
struct TextfileWindow : public Window, MissingGlyphSearcher {
|
||||
struct TextfileWindow : public Window, BaseStringMissingGlyphSearcher {
|
||||
TextfileType file_type{}; ///< Type of textfile to view.
|
||||
Scrollbar *vscroll = nullptr; ///< Vertical scrollbar.
|
||||
Scrollbar *hscroll = nullptr; ///< Horizontal scrollbar.
|
||||
@@ -38,8 +38,6 @@ struct TextfileWindow : public Window, MissingGlyphSearcher {
|
||||
void Reset() override;
|
||||
FontSize DefaultSize() override;
|
||||
std::optional<std::string_view> NextString() override;
|
||||
bool Monospace() override;
|
||||
void SetFontNames(FontCacheSettings *settings, std::string_view font_name, const void *os_data) override;
|
||||
void ScrollToLine(size_t line);
|
||||
bool IsTextWrapped() const;
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ enum GameOptionsWidgets : WidgetID {
|
||||
WID_GO_GUI_SCALE_BEVEL_TEXT, ///< Text for chunky bevels.
|
||||
WID_GO_GUI_FONT_SPRITE, ///< Toggle whether to prefer the sprite font over TTF fonts.
|
||||
WID_GO_GUI_FONT_SPRITE_TEXT, ///< Text for sprite font toggle.
|
||||
WID_GO_GUI_FONT_DEFAULT, ///< Toggle whether to prefer the default font over fallback fonts.
|
||||
WID_GO_GUI_FONT_DEFAULT_TEXT, ///< Text for default font toggle.
|
||||
WID_GO_GUI_FONT_AA, ///< Toggle whether to anti-alias fonts.
|
||||
WID_GO_GUI_FONT_AA_TEXT, ///< Text for anti-alias toggle.
|
||||
WID_GO_BASE_GRF_DROPDOWN, ///< Use to select a base GRF.
|
||||
|
||||
@@ -119,4 +119,14 @@ inline int ScaleGUITrad(int value)
|
||||
return value * _gui_scale / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale traditional pixel dimensions to font zoom level, for drawing sprite fonts.
|
||||
* @param value Pixel amount at #ZOOM_BASE (traditional "normal" interface size).
|
||||
* @return Pixel amount at _font_zoom (current interface size).
|
||||
*/
|
||||
inline int ScaleFontTrad(int value)
|
||||
{
|
||||
return UnScaleByZoom(value * ZOOM_BASE, _font_zoom);
|
||||
}
|
||||
|
||||
#endif /* ZOOM_FUNC_H */
|
||||
|
||||
Reference in New Issue
Block a user