1
0
Fork 0

Change: Support side-by-side fallback FontCaches instead of hierarchical.

The text layouter system can now support using different fonts for different glyphs, including mixing scalable and sprite glyphs.
pull/13303/head
Peter Nelson 2024-05-17 12:49:26 +01:00
parent ee2f0f46ee
commit 8c9a3fc3ee
No known key found for this signature in database
GPG Key ID: 8EF8F0A467DF75ED
29 changed files with 651 additions and 410 deletions

View File

@ -2360,17 +2360,18 @@ static bool ConFont(std::span<std::string_view> argv)
SetFont(argfs, font, size); SetFont(argfs, font, size);
} }
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { IConsolePrint(CC_INFO, "Configured fonts:");
FontCache *fc = FontCache::Get(fs); for (uint i = 0; FontSize fs : FONTSIZES_ALL) {
FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
/* Make sure all non sprite fonts are loaded. */ IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\", size {}", i, FontSizeToName(fs), setting->font, setting->size);
if (!setting->font.empty() && !fc->HasParent()) { ++i;
InitFontCache(fs); }
fc = FontCache::Get(fs);
} IConsolePrint(CC_INFO, "Currently active fonts:");
IConsolePrint(CC_DEFAULT, "{} font:", FontSizeToName(fs)); for (uint i = 0; const auto &fc : FontCache::Get()) {
IConsolePrint(CC_DEFAULT, "Currently active: \"{}\", size {}", fc->GetFontName(), fc->GetFontSize()); if (fc == nullptr) continue;
IConsolePrint(CC_DEFAULT, "Requested: \"{}\", size {}", setting->font, setting->size); IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\" size {}", i, FontSizeToName(fc->GetSize()), fc->GetFontName(), fc->GetFontSize());
++i;
} }
return true; return true;

View File

@ -8,6 +8,8 @@
/** @file fontcache.cpp Cache for characters from fonts. */ /** @file fontcache.cpp Cache for characters from fonts. */
#include "stdafx.h" #include "stdafx.h"
#include "core/string_consumer.hpp"
#include "fontcache.h" #include "fontcache.h"
#include "blitter/factory.hpp" #include "blitter/factory.hpp"
#include "gfx_layout.h" #include "gfx_layout.h"
@ -17,13 +19,10 @@
#include "viewport_func.h" #include "viewport_func.h"
#include "window_func.h" #include "window_func.h"
#include "fileio_func.h" #include "fileio_func.h"
#include "zoom_func.h"
#include "safeguards.h" #include "safeguards.h"
/** Default heights for the different sizes of fonts. */
static const int _default_font_height[FS_END] = {10, 6, 18, 10};
static const int _default_font_ascender[FS_END] = { 8, 5, 15, 8};
FontCacheSettings _fcsettings; FontCacheSettings _fcsettings;
/** /**
@ -31,10 +30,10 @@ FontCacheSettings _fcsettings;
* @param fs Font size to load. * @param fs Font size to load.
* @param fonttype Font type requested. * @param fonttype Font type requested.
*/ */
/* static */ void FontProviderManager::LoadFont(FontSize fs, FontType fonttype) /* static */ void 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()) { for (auto &provider : FontProviderManager::GetProviders()) {
provider->LoadFont(fs, fonttype); provider->LoadFont(fs, fonttype, search, font_name, os_handle);
} }
} }
@ -47,10 +46,10 @@ FontCacheSettings _fcsettings;
* @param callback The function to call to check for missing glyphs. * @param callback The function to call to check for missing glyphs.
* @return true if a font has been set, false otherwise. * @return true if a font has been set, false otherwise.
*/ */
/* static */ bool FontProviderManager::SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) /* static */ bool FontProviderManager::SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, class MissingGlyphSearcher *callback)
{ {
for (auto &provider : FontProviderManager::GetProviders()) { for (auto &provider : FontProviderManager::GetProviders()) {
if (provider->SetFallbackFont(settings, language_isocode, callback)) { if (provider->SetFallbackFont(language_isocode, bad_mask, callback)) {
return true; return true;
} }
} }
@ -61,42 +60,49 @@ FontCacheSettings _fcsettings;
* Create a new font cache. * Create a new font cache.
* @param fs The size of the font. * @param fs The size of the font.
*/ */
FontCache::FontCache(FontSize fs) : parent(FontCache::Get(fs)), fs(fs), height(_default_font_height[fs]), FontCache::FontCache(FontSize fs) : fs(fs)
ascender(_default_font_ascender[fs]), descender(_default_font_ascender[fs] - _default_font_height[fs])
{ {
assert(this->parent == nullptr || this->fs == this->parent->fs); /* Find an empty font cache slot. */
FontCache::caches[this->fs] = this; 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);
/* Register this font cache in the slot. */
it->reset(this);
/* Set up our font index and make us the default font cache for this font size. */
this->font_index = static_cast<FontIndex>(std::distance(std::begin(FontCache::caches), it));
FontCache::default_font_index[fs] = this->font_index;
Layouter::ResetFontCache(this->fs); Layouter::ResetFontCache(this->fs);
} }
/** Clean everything up. */ /** Clean everything up. */
FontCache::~FontCache() FontCache::~FontCache()
{ {
assert(this->fs == this->parent->fs);
FontCache::caches[this->fs] = this->parent;
Layouter::ResetFontCache(this->fs); Layouter::ResetFontCache(this->fs);
} }
int FontCache::GetDefaultFontHeight(FontSize fs) int FontCache::GetDefaultFontHeight(FontSize fs)
{ {
return _default_font_height[fs]; return DEFAULT_FONT_HEIGHT[fs];
} }
/** /* static */ void FontCache::UpdateCharacterHeight(FontSize 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)
{ {
FontCache *fc = FontCache::Get(fs); FontCache::max_height[fs] = 0;
if (fc != nullptr) { for (const auto &fc : FontCache::caches) {
return fc->GetFontName(); if (fc == nullptr || fc->fs != fs) continue;
} else { FontCache::max_height[fs] = std::max(FontCache::max_height[fs], fc->height);
return "[NULL]";
} }
if (FontCache::max_height[fs] == 0) FontCache::max_height[fs] = GetDefaultFontHeight(fs);
} }
int FontCache::GetGlyphYOffset()
{
int fs_height = FontCache::GetCharacterHeight(this->GetSize());
int height = this->GetAscender() - this->GetDescender();
return (fs_height - height) / 2;
}
/** /**
* Get height of a character for a given font size. * Get height of a character for a given font size.
@ -105,16 +111,19 @@ std::string FontCache::GetName(FontSize fs)
*/ */
int GetCharacterHeight(FontSize size) 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 */ FontCache::FontCaches FontCache::caches;
/* static */ FontCache *FontCache::caches[FS_END]; /* static */ std::array<int, FS_END> FontCache::max_height{};
/* static */ std::array<FontIndex, FS_END> FontCache::default_font_index{};
/* static */ void FontCache::InitializeFontCaches() /* static */ void FontCache::InitializeFontCaches()
{ {
for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { for (FontSize fs : FONTSIZES_ALL) {
if (FontCache::caches[fs] == nullptr) FontProviderManager::LoadFont(fs, FontType::Sprite); /* FontCache inserts itself into to the cache. */ FontCache::max_height[fs] = ScaleSpriteTrad(GetDefaultFontHeight(fs));
} }
} }
@ -145,15 +154,8 @@ void SetFont(FontSize fontsize, const std::string &font, uint size)
if (!changed) return; if (!changed) return;
if (fontsize != FS_MONO) { if (fontsize != FS_MONO) {
/* Try to reload only the modified font. */ /* Check if fallback fonts are needed. */
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() : "";
}
CheckForMissingGlyphs(); CheckForMissingGlyphs();
_fcsettings = std::move(backup);
} else { } else {
InitFontCache(fontsize); InitFontCache(fontsize);
} }
@ -171,7 +173,7 @@ void SetFont(FontSize fontsize, const std::string &font, uint size)
*/ */
static bool IsDefaultFont(const FontCacheSubSetting &setting) static bool IsDefaultFont(const FontCacheSubSetting &setting)
{ {
return setting.font.empty() && setting.os_handle == nullptr; return setting.font.empty();
} }
/** /**
@ -208,7 +210,7 @@ static std::string GetDefaultTruetypeFont(FontSize fs)
* @param fs Font size. * @param fs Font size.
* @return Full path of default font file. * @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) #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
/* Find font file. */ /* Find font file. */
@ -237,13 +239,52 @@ std::string GetFontCacheFontName(FontSize fs)
*/ */
void InitFontCache(FontSizes fontsizes) void InitFontCache(FontSizes fontsizes)
{ {
FontCache::InitializeFontCaches(); static constexpr std::string_view DEFAULT_FONT = "default";
for (FontSize fs : fontsizes) { for (FontSize fs : fontsizes) {
FontCache *fc = FontCache::Get(fs); Layouter::ResetFontCache(fs);
if (fc->HasParent()) delete fc; FontCache::default_font_index[fs] = INVALID_FONT_INDEX;
}
FontProviderManager::LoadFont(fs, FontType::TrueType); /* Remove all existing FontCaches. */
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) {
const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
/* Add all detected fallback fonts. */
for (auto &fallback : setting->fallback_fonts) {
FontProviderManager::LoadFont(fs, FontType::TrueType, /*fallback.dynamic ? "missing-fallback" : "language-fallback", */ false, fallback.name, fallback.os_handle);
}
/* Parse configured fonts, separated by ';' into a list. */
std::vector<std::string_view> fontnames;
StringConsumer consumer(setting->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 font as lowest priority if not manually specified. */
if (std::ranges::find(fontnames, DEFAULT_FONT) == std::end(fontnames)) fontnames.push_back(DEFAULT_FONT);
/* 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) {
/* Load the sprite font, even if it's not preferred. */
FontProviderManager::LoadFont(fs, FontType::Sprite, /*"default"*/ false, {}, {});
if (!_fcsettings.prefer_sprite) {
/* Load the default truetype font if sprite not isn't preferred. */
FontProviderManager::LoadFont(fs, FontType::TrueType, /*"default",*/ false, GetDefaultTruetypeFontFile(fs), {});
}
} else {
FontProviderManager::LoadFont(fs, FontType::TrueType, /*"configured",*/ true, std::string{*it}, {});
}
}
} }
} }
@ -252,8 +293,5 @@ void InitFontCache(FontSizes fontsizes)
*/ */
void UninitFontCache() void UninitFontCache()
{ {
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { FontCache::caches.clear();
FontCache *fc = FontCache::Get(fs);
if (fc->HasParent()) delete fc;
}
} }

View File

@ -16,17 +16,31 @@
/** Glyphs are characters from a font. */ /** Glyphs are characters from a font. */
typedef uint32_t GlyphID; 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();
/** Default heights for the different sizes of fonts. */
static constexpr int DEFAULT_FONT_HEIGHT[FS_END] = {10, 6, 18, 10};
static constexpr int DEFAULT_FONT_ASCENDER[FS_END] = { 8, 5, 15, 8};
/** Font cache for basic fonts. */ /** Font cache for basic fonts. */
class FontCache { class FontCache {
protected: protected:
static FontCache *caches[FS_END]; ///< All the font caches. using FontCaches = std::vector<std::unique_ptr<FontCache>>;
FontCache *parent; ///< The parent of this font cache. static FontCaches caches;
static std::array<int, FS_END> max_height;
static std::array<FontIndex, FS_END> default_font_index;
const FontSize fs; ///< The size of the font. const FontSize fs; ///< The size of the font.
int height; ///< The height of the font. FontIndex font_index; ///< The index of the font.
int ascender; ///< The ascender value of the font. int height = 0; ///< The height of the font.
int descender; ///< The descender value of the font. int ascender = 0; ///< The ascender value of the font.
int descender = 0; ///< The descender value of the font.
friend void UninitFontCache();
friend void InitFontCache(FontSizes fontsizes);
public: public:
FontCache(FontSize fs); FontCache(FontSize fs);
@ -42,6 +56,8 @@ public:
*/ */
inline FontSize GetSize() const { return this->fs; } inline FontSize GetSize() const { return this->fs; }
inline FontIndex GetIndex() const { return this->font_index; }
/** /**
* Get the height of the font. * Get the height of the font.
* @return The height of the font. * @return The height of the font.
@ -92,10 +108,9 @@ public:
/** /**
* Map a character into a glyph. * Map a character into a glyph.
* @param key The character. * @param key The character.
* @param fallback Allow fallback to the parent font.
* @return The glyph ID used to draw the character. * @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. * Get the native OS font handle, if there is one.
@ -112,25 +127,57 @@ public:
*/ */
virtual std::string GetFontName() = 0; 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. * Get the font cache of a given font size.
* @param fs The font size to look up. * @param fs The font size to look up.
* @return The font cache. * @return The font cache.
*/ */
static inline FontCache *Get(FontSize fs) static inline FontCache *Get(FontIndex font_index)
{ {
assert(fs < FS_END); assert(font_index < FontCache::caches.size());
return FontCache::caches[fs]; return FontCache::caches[font_index].get();
} }
static std::string GetName(FontSize fs); static inline int GetCharacterHeight(FontSize fs)
/**
* Check whether the font cache has a parent.
*/
inline bool HasParent()
{ {
return this->parent != nullptr; return FontCache::max_height[fs];
}
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;
} }
/** /**
@ -141,36 +188,55 @@ public:
inline void ClearFontCache(FontSizes fontsizes) inline void ClearFontCache(FontSizes fontsizes)
{ {
for (FontSize fs : fontsizes) { for (const auto &fc : FontCache::Get()) {
FontCache::Get(fs)->ClearFontCache(); if (fc == nullptr) continue;
if (!fontsizes.Test(fc->GetSize())) continue;
fc->ClearFontCache();
} }
} }
/** Get the Sprite for a glyph */ /** Get the Sprite for a glyph */
inline const Sprite *GetGlyph(FontSize size, char32_t key) 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)); return fc->GetGlyph(fc->MapCharToGlyph(key));
} }
/** Get the width of a glyph */ /** Get the width of a glyph */
inline uint GetGlyphWidth(FontSize size, char32_t key) 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)); return fc->GetGlyphWidth(fc->MapCharToGlyph(key));
} }
inline bool GetDrawGlyphShadow(FontSize size)
{
return FontCache::Get(size)->GetDrawGlyphShadow();
}
/** Settings for a single font. */ /** Settings for a single font. */
struct FontCacheSubSetting { struct FontCacheSubSetting {
std::string font; ///< The name of the font, or path to the font. std::string font; ///< The name of the font, or path to the font.
uint size; ///< The (requested) size of 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 {
std::string name;
std::vector<std::byte> os_handle;
bool dynamic;
};
std::vector<FontCacheFallback> fallback_fonts;
/**
* Add a fallback font this font size configuration.
* @param name Name of font to add.
* @param handle OS-specific handle or data of font.
*/
template <typename T>
void AddFallback(const std::string &name, T &handle)
{
auto os_data = std::as_bytes(std::span(&handle, 1));
this->fallback_fonts.emplace_back(name, std::vector<std::byte>{os_data.begin(), os_data.end()});
}
}; };
/** Settings for the four different fonts. */ /** Settings for the four different fonts. */
@ -228,14 +294,14 @@ public:
ProviderManager<FontCacheFactory>::Unregister(*this); ProviderManager<FontCacheFactory>::Unregister(*this);
} }
virtual void LoadFont(FontSize fs, FontType fonttype) = 0; virtual void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span<const std::byte> os_handle) = 0;
virtual bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) = 0; virtual bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, class MissingGlyphSearcher *callback) = 0;
}; };
class FontProviderManager : ProviderManager<FontCacheFactory> { class FontProviderManager : ProviderManager<FontCacheFactory> {
public: public:
static void LoadFont(FontSize fs, FontType fonttype); static void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span<const std::byte> os_handle);
static bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); static bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, class MissingGlyphSearcher *callback);
}; };
/* Implemented in spritefontcache.cpp */ /* Implemented in spritefontcache.cpp */

View File

@ -40,7 +40,7 @@ public:
FreeTypeFontCache(FontSize fs, FT_Face face, int pixels); FreeTypeFontCache(FontSize fs, FT_Face face, int pixels);
~FreeTypeFontCache(); ~FreeTypeFontCache();
void ClearFontCache() override; 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); } std::string GetFontName() override { return fmt::format("{}, {}", face->family_name, face->style_name); }
bool IsBuiltInFont() override { return false; } bool IsBuiltInFont() override { return false; }
const void *GetOSHandle() override { return &face; } const void *GetOSHandle() override { return &face; }
@ -104,6 +104,7 @@ void FreeTypeFontCache::SetFontSize(int pixels)
this->ascender = this->face->size->metrics.ascender >> 6; this->ascender = this->face->size->metrics.ascender >> 6;
this->descender = this->face->size->metrics.descender >> 6; this->descender = this->face->size->metrics.descender >> 6;
this->height = this->ascender - this->descender; this->height = this->ascender - this->descender;
FontCache::UpdateCharacterHeight(this->fs);
} else { } else {
/* Both FT_Set_Pixel_Sizes and FT_Select_Size failed. */ /* Both FT_Set_Pixel_Sizes and FT_Select_Size failed. */
Debug(fontcache, 0, "Font size selection failed. Using FontCache defaults."); Debug(fontcache, 0, "Font size selection failed. Using FontCache defaults.");
@ -193,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)); assert(IsPrintable(key));
FT_UInt glyph = FT_Get_Char_Index(this->face, key); return 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;
} }
FT_Library _ft_library = nullptr; FT_Library _ft_library = nullptr;
@ -225,15 +220,10 @@ public:
* format is 'font family name' or 'font family name, font style'. * format is 'font family name' or 'font family name, font style'.
* @param fs The font size to load. * @param fs The font size to load.
*/ */
void LoadFont(FontSize fs, FontType fonttype) override void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span<const std::byte> os_handle) override
{ {
if (fonttype != FontType::TrueType) return; if (fonttype != FontType::TrueType) return;
FontCacheSubSetting *settings = GetFontCacheSubSetting(fs);
std::string font = GetFontCacheFontName(fs);
if (font.empty()) return;
if (_ft_library == nullptr) { if (_ft_library == nullptr) {
if (FT_Init_FreeType(&_ft_library) != FT_Err_Ok) { if (FT_Init_FreeType(&_ft_library) != FT_Err_Ok) {
ShowInfo("Unable to initialize FreeType, using sprite fonts instead"); ShowInfo("Unable to initialize FreeType, using sprite fonts instead");
@ -247,7 +237,9 @@ public:
/* If font is an absolute path to a ttf, try loading that first. */ /* If font is an absolute path to a ttf, try loading that first. */
int32_t index = 0; 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); FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face);
if (error != FT_Err_Ok) { if (error != FT_Err_Ok) {
@ -259,24 +251,24 @@ public:
} }
#ifdef WITH_FONTCONFIG #ifdef WITH_FONTCONFIG
/* Try loading based on font face name (OS-wide fonts). */ /* If allowed to search, try loading based on font face name (OS-wide fonts). */
if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face); if (error != FT_Err_Ok && search) error = GetFontByFaceName(font, &face);
#endif /* WITH_FONTCONFIG */ #endif /* WITH_FONTCONFIG */
if (error == FT_Err_Ok) { if (error == FT_Err_Ok) {
error = LoadFont(fs, face, font, GetFontCacheFontSize(fs)); error = LoadFont(fs, face, font, GetFontCacheFontSize(fs));
if (error != FT_Err_Ok) { if (error != FT_Err_Ok) {
ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}, using sprite font instead", font, FontSizeToName(fs), error); ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}", font, FontSizeToName(fs), error);
} }
} else { } else {
FT_Done_Face(face); FT_Done_Face(face);
} }
} }
bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) override bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, MissingGlyphSearcher *callback) override
{ {
#ifdef WITH_FONTCONFIG #ifdef WITH_FONTCONFIG
if (FontConfigSetFallbackFont(settings, language_isocode, callback)) return true; if (FontConfigSetFallbackFont(language_isocode, bad_mask, callback)) return true;
#endif /* WITH_FONTCONFIG */ #endif /* WITH_FONTCONFIG */
return false; return false;
@ -293,8 +285,8 @@ private:
if (error == FT_Err_Invalid_CharMap_Handle) { if (error == FT_Err_Invalid_CharMap_Handle) {
/* Try to pick a different character map instead. We default to /* Try to pick a different character map instead. We default to
* the first map, but platform_id 0 encoding_id 0 should also * the first map, but platform_id 0 encoding_id 0 should also
* be unicode (strange system...) */ * be unicode (strange system...) */
FT_CharMap found = face->charmaps[0]; FT_CharMap found = face->charmaps[0];
int i; int i;

View File

@ -38,8 +38,15 @@ static int ScaleFontTrad(int value)
*/ */
SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs) SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs)
{ {
this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); this->UpdateMetrics();
this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2; }
void SpriteFontCache::UpdateMetrics()
{
this->height = ScaleGUITrad(DEFAULT_FONT_HEIGHT[this->fs]);
this->ascender = ScaleFontTrad(DEFAULT_FONT_ASCENDER[fs]);
this->descender = ScaleFontTrad(DEFAULT_FONT_ASCENDER[fs] - DEFAULT_FONT_HEIGHT[fs]);
FontCache::UpdateCharacterHeight(this->fs);
} }
/** /**
@ -60,30 +67,29 @@ SpriteID SpriteFontCache::GetSpriteIDForChar(char32_t key)
void SpriteFontCache::ClearFontCache() void SpriteFontCache::ClearFontCache()
{ {
Layouter::ResetFontCache(this->fs); Layouter::ResetFontCache(this->fs);
this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); this->UpdateMetrics();
this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2;
} }
const Sprite *SpriteFontCache::GetGlyph(GlyphID key) const Sprite *SpriteFontCache::GetGlyph(GlyphID key)
{ {
SpriteID sprite = this->GetSpriteIDForChar(static_cast<char32_t>(key & ~SPRITE_GLYPH)); SpriteID sprite = this->GetSpriteIDForChar(static_cast<char32_t>(key));
if (sprite == 0) sprite = this->GetSpriteIDForChar('?'); if (sprite == 0) sprite = this->GetSpriteIDForChar('?');
return GetSprite(sprite, SpriteType::Font); return GetSprite(sprite, SpriteType::Font);
} }
uint SpriteFontCache::GetGlyphWidth(GlyphID key) uint SpriteFontCache::GetGlyphWidth(GlyphID key)
{ {
SpriteID sprite = this->GetSpriteIDForChar(static_cast<char32_t>(key & ~SPRITE_GLYPH)); SpriteID sprite = this->GetSpriteIDForChar(static_cast<char32_t>(key));
if (sprite == 0) sprite = this->GetSpriteIDForChar('?'); if (sprite == 0) sprite = this->GetSpriteIDForChar('?');
return SpriteExists(sprite) ? GetSprite(sprite, SpriteType::Font)->width + ScaleFontTrad(this->fs != FS_NORMAL ? 1 : 0) : 0; 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)); assert(IsPrintable(key));
SpriteID sprite = this->GetSpriteIDForChar(key); SpriteID sprite = this->GetSpriteIDForChar(key);
if (sprite == 0) return 0; if (sprite == 0) return 0;
return SPRITE_GLYPH | key; return static_cast<GlyphID>(key);
} }
bool SpriteFontCache::GetDrawGlyphShadow() bool SpriteFontCache::GetDrawGlyphShadow()
@ -95,14 +101,14 @@ class SpriteFontCacheFactory : public FontCacheFactory {
public: public:
SpriteFontCacheFactory() : FontCacheFactory("sprite", "Sprite font provider") {} SpriteFontCacheFactory() : FontCacheFactory("sprite", "Sprite font provider") {}
void LoadFont(FontSize fs, FontType fonttype) override void LoadFont(FontSize fs, FontType fonttype, bool, const std::string &, std::span<const std::byte>) override
{ {
if (fonttype != FontType::Sprite) return; if (fonttype != FontType::Sprite) return;
new SpriteFontCache(fs); new SpriteFontCache(fs);
} }
bool SetFallbackFont(struct FontCacheSettings *, const std::string &, class MissingGlyphSearcher *) override bool SetFallbackFont(const std::string &, FontSizes, class MissingGlyphSearcher *) override
{ {
return false; return false;
} }

View File

@ -21,11 +21,12 @@ public:
const Sprite *GetGlyph(GlyphID key) override; const Sprite *GetGlyph(GlyphID key) override;
uint GetGlyphWidth(GlyphID key) override; uint GetGlyphWidth(GlyphID key) override;
bool GetDrawGlyphShadow() 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"; } std::string GetFontName() override { return "sprite"; }
bool IsBuiltInFont() override { return true; } bool IsBuiltInFont() override { return true; }
private: private:
void UpdateMetrics();
SpriteID GetSpriteIDForChar(char32_t key); SpriteID GetSpriteIDForChar(char32_t key);
}; };

View File

@ -64,8 +64,6 @@ bool TrueTypeFontCache::GetDrawGlyphShadow()
uint TrueTypeFontCache::GetGlyphWidth(GlyphID key) uint TrueTypeFontCache::GetGlyphWidth(GlyphID key)
{ {
if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyphWidth(key);
GlyphEntry *glyph = this->GetGlyphPtr(key); GlyphEntry *glyph = this->GetGlyphPtr(key);
if (glyph == nullptr || glyph->data == nullptr) { if (glyph == nullptr || glyph->data == nullptr) {
this->GetGlyph(key); this->GetGlyph(key);
@ -77,8 +75,6 @@ uint TrueTypeFontCache::GetGlyphWidth(GlyphID key)
const Sprite *TrueTypeFontCache::GetGlyph(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 */ /* Check for the glyph in our cache */
GlyphEntry *glyph = this->GetGlyphPtr(key); GlyphEntry *glyph = this->GetGlyphPtr(key);
if (glyph != nullptr && glyph->data != nullptr) return glyph->GetSprite(); if (glyph != nullptr && glyph->data != nullptr) return glyph->GetSprite();

View File

@ -481,6 +481,12 @@ static void SetColourRemap(TextColour colour)
_colour_remap_ptr = _string_colourremap; _colour_remap_ptr = _string_colourremap;
} }
static void RenderGlyph(FontCache *fc, GlyphID glyph, int left, int, int top, int)
{
const Sprite *sprite = fc->GetGlyph(glyph);
GfxMainBlitter(sprite, left, top, BlitterMode::ColourRemap);
};
/** /**
* Drawing routine for drawing a laid out line of text. * Drawing routine for drawing a laid out line of text.
* @param line String to draw. * @param line String to draw.
@ -595,6 +601,9 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK; colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
SetColourRemap(do_shadow ? TC_BLACK : colour); // the last run also sets the colour for the truncation dots SetColourRemap(do_shadow ? TC_BLACK : colour); // the last run also sets the colour for the truncation dots
if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue; if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue;
int height = GetCharacterHeight(fc->GetSize());
auto render = RenderGlyph;
DrawPixelInfo *dpi = _cur_dpi; DrawPixelInfo *dpi = _cur_dpi;
int dpi_left = dpi->left; int dpi_left = dpi->left;
@ -612,14 +621,16 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
/* Truncated away. */ /* Truncated away. */
if (truncation && (begin_x < min_x || end_x > max_x)) continue; 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); if (do_shadow) {
/* Check clipping (the "+ 1" is for the shadow). */ begin_x += shadow_offset;
if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue; end_x += shadow_offset;
top += shadow_offset;
}
if (do_shadow && (glyph & SPRITE_GLYPH) != 0) continue; render(fc, glyph, begin_x, end_x, top, top + height - 1);
GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap);
} }
} }

View File

@ -38,18 +38,17 @@
std::unique_ptr<Layouter::LineCache> Layouter::linecache; std::unique_ptr<Layouter::LineCache> Layouter::linecache;
/** Cache of Font instances. */ /** Cache of Font instances. */
Layouter::FontColourMap Layouter::fonts[FS_END]; std::unordered_map<FontIndex, Layouter::FontColourMap> Layouter::fonts;
/** /**
* Construct a new font. * Construct a new font.
* @param size The font size to use for this font. * @param font_index The font index to use for this font.
* @param colour The colour to draw this font in. * @param colour The colour to draw this font in.
*/ */
Font::Font(FontSize size, TextColour colour) : Font::Font(FontIndex font_index, TextColour colour) :
fc(FontCache::Get(size)), colour(colour) fc(FontCache::Get(font_index)), colour(colour)
{ {
assert(size < FS_END);
} }
/** /**
@ -71,7 +70,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
const typename T::CharType *buffer_last = buff_begin + str.size() + 1; const typename T::CharType *buffer_last = buff_begin + str.size() + 1;
typename T::CharType *buff = buff_begin; typename T::CharType *buff = buff_begin;
FontMap &font_mapping = line.runs; FontMap &font_mapping = line.runs;
Font *f = Layouter::GetFont(state.fontsize, state.cur_colour); Font *f = Layouter::GetFont(state.font_index, state.cur_colour);
font_mapping.clear(); font_mapping.clear();
@ -80,7 +79,10 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
* whenever the font changes, and convert the wide characters into a format * whenever the font changes, and convert the wide characters into a format
* usable by ParagraphLayout. * 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') { if (c == '\0' || c == '\n') {
/* Caller should already have filtered out these characters. */ /* Caller should already have filtered out these characters. */
NOT_REACHED(); NOT_REACHED();
@ -95,19 +97,40 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
} else { } else {
/* Filter out non printable characters */ /* Filter out non printable characters */
if (!IsPrintable(c)) continue; 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 if (IsTextDirectionChar(c)) {
* needed for RTL languages which need more proper shaping support. */ /* Filter out text direction characters that shouldn't be drawn, and
if (!T::SUPPORTS_RTL && IsTextDirectionChar(c)) continue; * will not be handled in the fallback case because they are mostly
buff += T::AppendToBuffer(buff, buffer_last, c); * needed for RTL languages which need more proper shaping support. */
if (buff >= buffer_last) break; if constexpr (!T::SUPPORTS_RTL) continue;
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); font_mapping.emplace_back(buff - buff_begin, f);
} }
f = Layouter::GetFont(state.fontsize, state.cur_colour); f = Layouter::GetFont(state.font_index, state.cur_colour);
} }
/* Better safe than sorry. */ /* Better safe than sorry. */
@ -116,6 +139,14 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
if (font_mapping.empty() || font_mapping.back().first != buff - buff_begin) { if (font_mapping.empty() || font_mapping.back().first != buff - buff_begin) {
font_mapping.emplace_back(buff - buff_begin, f); 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::all_of(std::begin(font_mapping), std::end(font_mapping), [](const auto &i) { return i.second->fc->IsBuiltInFont(); })) {
return;
}
}
line.layout = T::GetParagraphLayout(buff_begin, buff, font_mapping); line.layout = T::GetParagraphLayout(buff_begin, buff, font_mapping);
line.state_after = state; line.state_after = state;
} }
@ -128,7 +159,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
*/ */
Layouter::Layouter(std::string_view str, int maxw, FontSize fontsize) : string(str) 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) { while (true) {
auto line_length = str.find_first_of('\n'); auto line_length = str.find_first_of('\n');
@ -341,13 +372,17 @@ ptrdiff_t Layouter::GetCharAtPosition(int x, size_t line_index) const
/** /**
* Get a static font instance. * Get a static font instance.
*/ */
Font *Layouter::GetFont(FontSize size, TextColour colour) Font *Layouter::GetFont(FontIndex font_index, TextColour colour)
{ {
FontColourMap::iterator it = fonts[size].find(colour); if (font_index == INVALID_FONT_INDEX) return nullptr;
if (it != fonts[size].end()) return it->second.get(); assert(font_index < FontCache::Get().size());
fonts[size][colour] = std::make_unique<Font>(size, colour); FontColourMap &fcm = Layouter::fonts[font_index];
return fonts[size][colour].get(); auto it = fcm.find(colour);
if (it != fcm.end()) return it->second.get();
fcm[colour] = std::make_unique<Font>(font_index, colour);
return fcm[colour].get();
} }
/** /**
@ -362,11 +397,10 @@ void Layouter::Initialize()
/** /**
* Reset cached font information. * 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(); Layouter::fonts.clear();
/* We must reset the linecache since it references the just freed fonts */ /* We must reset the linecache since it references the just freed fonts */
ResetLineCache(); ResetLineCache();

View File

@ -22,12 +22,13 @@
* of the same text, e.g. on line breaks. * of the same text, e.g. on line breaks.
*/ */
struct FontState { struct FontState {
FontSize fontsize; ///< Current font size. FontSize fontsize; ///< Current font size.
TextColour cur_colour; ///< Current text colour. 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. std::vector<TextColour> colour_stack; ///< Stack of colours to assist with colour switching.
FontState() : fontsize(FS_END), cur_colour(TC_INVALID) {} FontState() : fontsize(FS_END), font_index(INVALID_FONT_INDEX), cur_colour(TC_INVALID) {}
FontState(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour) {} FontState(TextColour colour, FontSize fontsize, FontIndex font_index) : fontsize(fontsize), font_index(font_index), cur_colour(colour) {}
auto operator<=>(const FontState &) const = default; auto operator<=>(const FontState &) const = default;
@ -67,6 +68,7 @@ struct FontState {
inline void SetFontSize(FontSize f) inline void SetFontSize(FontSize f)
{ {
this->fontsize = f; this->fontsize = f;
this->font_index = FontCache::GetDefaultFontIndex(this->fontsize);
} }
}; };
@ -85,9 +87,10 @@ template <> struct std::hash<FontState> {
std::size_t operator()(const FontState &state) const noexcept std::size_t operator()(const FontState &state) const noexcept
{ {
size_t h1 = std::hash<FontSize>{}(state.fontsize); size_t h1 = std::hash<FontSize>{}(state.fontsize);
size_t h2 = std::hash<TextColour>{}(state.cur_colour); size_t h2 = std::hash<FontIndex>{}(state.font_index);
size_t h3 = std::hash<std::vector<TextColour>>{}(state.colour_stack); size_t h3 = std::hash<TextColour>{}(state.cur_colour);
return h1 ^ (h2 << 1) ^ (h3 << 2); size_t h4 = std::hash<std::vector<TextColour>>{}(state.colour_stack);
return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3);
} }
}; };
@ -99,7 +102,7 @@ public:
FontCache *fc; ///< The font we are using. FontCache *fc; ///< The font we are using.
TextColour colour; ///< The colour this font has to be. TextColour colour; ///< The colour this font has to be.
Font(FontSize size, TextColour colour); Font(FontIndex font_index, TextColour colour);
}; };
/** Mapping from index to font. The pointer is owned by FontColourMap. */ /** Mapping from index to font. The pointer is owned by FontColourMap. */
@ -206,9 +209,9 @@ private:
static LineCacheItem &GetCachedParagraphLayout(std::string_view str, const FontState &state); static LineCacheItem &GetCachedParagraphLayout(std::string_view str, const FontState &state);
using FontColourMap = std::map<TextColour, std::unique_ptr<Font>>; using FontColourMap = std::map<TextColour, std::unique_ptr<Font>>;
static FontColourMap fonts[FS_END]; static std::unordered_map<FontIndex, FontColourMap> fonts;
public: public:
static Font *GetFont(FontSize size, TextColour colour); static Font *GetFont(FontIndex font_index, TextColour colour);
Layouter(std::string_view str, int maxw = INT32_MAX, FontSize fontsize = FS_NORMAL); Layouter(std::string_view str, int maxw = INT32_MAX, FontSize fontsize = FS_NORMAL);
Dimension GetBounds(); Dimension GetBounds();
@ -216,7 +219,7 @@ public:
ptrdiff_t GetCharAtPosition(int x, size_t line_index) const; ptrdiff_t GetCharAtPosition(int x, size_t line_index) const;
static void Initialize(); static void Initialize();
static void ResetFontCache(FontSize size); static void ResetFontCache(FontSize fs);
static void ResetLineCache(); static void ResetLineCache();
}; };

View File

@ -51,7 +51,7 @@ public:
int GetGlyphCount() const override { return static_cast<int>(this->glyphs.size()); } int GetGlyphCount() const override { return static_cast<int>(this->glyphs.size()); }
std::span<const GlyphID> GetGlyphs() const override { return this->glyphs; } std::span<const GlyphID> GetGlyphs() const override { return this->glyphs; }
std::span<const Position> GetPositions() const override { return this->positions; } 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()->fc->GetSize()); }
std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; } std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; }
}; };
@ -112,23 +112,17 @@ public:
FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(Font *font, const char32_t *chars, int char_count, int char_offset, int x) : FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(Font *font, const char32_t *chars, int char_count, int char_offset, int x) :
font(font) font(font)
{ {
const bool isbuiltin = font->fc->IsBuiltInFont();
this->glyphs.reserve(char_count); this->glyphs.reserve(char_count);
this->glyph_to_char.reserve(char_count); this->glyph_to_char.reserve(char_count);
this->positions.reserve(char_count); this->positions.reserve(char_count);
FontCache &fc = *this->font->fc;
int y_offset = fc.GetGlyphYOffset();;
int advance = x; int advance = x;
for (int i = 0; i < char_count; i++) { for (int i = 0; i < char_count; i++) {
const GlyphID &glyph_id = this->glyphs.emplace_back(font->fc->MapCharToGlyph(chars[i])); const GlyphID &glyph_id = this->glyphs.emplace_back(fc.MapCharToGlyph(chars[i]));
int x_advance = font->fc->GetGlyphWidth(glyph_id); int x_advance = fc.GetGlyphWidth(glyph_id);
if (isbuiltin) { this->positions.emplace_back(advance, advance + x_advance - 1, y_offset); // No ascender adjustment.
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.
}
advance += x_advance; advance += x_advance;
this->glyph_to_char.push_back(char_offset + i); this->glyph_to_char.push_back(char_offset + i);
} }
@ -234,6 +228,7 @@ std::unique_ptr<const ParagraphLayouter::Line> FallbackParagraphLayout::NextLine
} }
const FontCache *fc = iter->second->fc; const FontCache *fc = iter->second->fc;
assert(fc != nullptr);
const char32_t *next_run = this->buffer_begin + iter->first; const char32_t *next_run = this->buffer_begin + iter->first;
const char32_t *begin = this->buffer; const char32_t *begin = this->buffer;
@ -251,6 +246,7 @@ std::unique_ptr<const ParagraphLayouter::Line> FallbackParagraphLayout::NextLine
if (this->buffer == next_run) { if (this->buffer == next_run) {
int w = l->GetWidth(); int w = l->GetWidth();
assert(iter->second->fc != nullptr);
l->emplace_back(iter->second, begin, this->buffer - begin, begin - this->buffer_begin, w); l->emplace_back(iter->second, begin, this->buffer - begin, begin - this->buffer_begin, w);
++iter; ++iter;
assert(iter != this->runs.end()); assert(iter != this->runs.end());

View File

@ -26,5 +26,24 @@ public:
static size_t AppendToBuffer(char32_t *buff, const char32_t *buffer_last, char32_t c); 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 */ #endif /* GFX_LAYOUT_FALLBACK_H */

View File

@ -9,8 +9,10 @@
#include "stdafx.h" #include "stdafx.h"
#include "gfx_layout_icu.h" #include "gfx_layout_icu.h"
#include "gfx_layout_fallback.h"
#include "debug.h" #include "debug.h"
#include "string_func.h"
#include "strings_func.h" #include "strings_func.h"
#include "language.h" #include "language.h"
#include "table/control_codes.h" #include "table/control_codes.h"
@ -51,6 +53,7 @@ public:
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 = USCRIPT_UNKNOWN, Font *font = nullptr) : start(start), length(length), level(level), script(script), font(font) {}
void Shape(UChar *buff, size_t length); void Shape(UChar *buff, size_t length);
void FallbackShape(UChar *buff);
}; };
/** /**
@ -76,7 +79,7 @@ public:
std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; } std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; }
const Font *GetFont() const override { return this->font; } const Font *GetFont() const override { return this->font; }
int GetLeading() const override { return this->font->fc->GetHeight(); } int GetLeading() const override { return GetCharacterHeight(this->font->fc->GetSize()); }
int GetGlyphCount() const override { return this->glyphs.size(); } int GetGlyphCount() const override { return this->glyphs.size(); }
int GetAdvance() const { return this->total_advance; } int GetAdvance() const { return this->total_advance; }
}; };
@ -135,12 +138,52 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) :
assert(!run.positions.empty()); assert(!run.positions.empty());
this->positions.reserve(run.positions.size()); this->positions.reserve(run.positions.size());
int y_offset = this->font->fc->GetGlyphYOffset();
/* Copy positions, moving x coordinate by x offset. */ /* Copy positions, moving x coordinate by x offset. */
for (const auto &pos : run.positions) { for (const auto &pos : run.positions) {
this->positions.emplace_back(pos.left + x, pos.right + x, pos.top); this->positions.emplace_back(pos.left + x, pos.right + x, pos.top + y_offset);
} }
} }
/**
* 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)
{
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) {
char32_t c = Utf16DecodeChar(buff + i);
if (this->level & 1) c = SwapRtlPairedCharacters(c);
this->glyphs.emplace_back(this->font->fc->MapCharToGlyph(c));
this->glyph_to_char.push_back(i);
if (Utf16IsLeadSurrogate(*(buff + i))) ++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 = (GetCharacterHeight(this->font->fc->GetSize()) - this->font->fc->GetHeight()) / 2;
int advance = 0;
for (const GlyphID glyph : this->glyphs) {
int x_advance = this->font->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. * Shape a single run.
* *
@ -149,6 +192,17 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) :
*/ */
void ICURun::Shape(UChar *buff, size_t buff_length) void ICURun::Shape(UChar *buff, size_t buff_length)
{ {
/* Make sure any former run is lost. */
this->glyphs.clear();
this->glyph_to_char.clear();
this->positions.clear();
this->advance.clear();
if (this->font->fc->IsBuiltInFont()) {
this->FallbackShape(buff);
return;
}
auto hbfont = hb_ft_font_create_referenced(*(static_cast<const FT_Face *>(font->fc->GetOSHandle()))); auto hbfont = hb_ft_font_create_referenced(*(static_cast<const FT_Face *>(font->fc->GetOSHandle())));
/* Match the flags with how we render the glyphs. */ /* 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); hb_ft_font_set_load_flags(hbfont, GetFontAAState() ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO);
@ -170,12 +224,6 @@ void ICURun::Shape(UChar *buff, size_t buff_length)
auto glyph_info = hb_buffer_get_glyph_infos(hbbuf, &glyph_count); auto glyph_info = hb_buffer_get_glyph_infos(hbbuf, &glyph_count);
auto glyph_pos = hb_buffer_get_glyph_positions(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. */ /* Reserve space, as we already know the size. */
this->glyphs.reserve(glyph_count); this->glyphs.reserve(glyph_count);
this->glyph_to_char.reserve(glyph_count); this->glyph_to_char.reserve(glyph_count);
@ -183,20 +231,12 @@ void ICURun::Shape(UChar *buff, size_t buff_length)
this->advance.reserve(glyph_count); this->advance.reserve(glyph_count);
/* Prepare the glyphs/position. ICUVisualRun will give the position an offset if needed. */ /* Prepare the glyphs/position. ICUVisualRun will give the position an offset if needed. */
int y_offset = (GetCharacterHeight(this->font->fc->GetSize()) - this->font->fc->GetHeight()) / 2;
hb_position_t advance = 0; hb_position_t advance = 0;
for (unsigned int i = 0; i < glyph_count; i++) { for (unsigned int i = 0; i < glyph_count; i++) {
int x_advance; int x_advance = glyph_pos[i].x_advance / FONT_SCALE;
this->glyphs.push_back(glyph_info[i].codepoint);
if (buff[glyph_info[i].cluster] >= SCC_SPRITE_START && buff[glyph_info[i].cluster] <= SCC_SPRITE_END && glyph_info[i].codepoint == 0) { 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);
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);
}
this->glyph_to_char.push_back(glyph_info[i].cluster); this->glyph_to_char.push_back(glyph_info[i].cluster);
this->advance.push_back(x_advance); this->advance.push_back(x_advance);
@ -359,11 +399,6 @@ std::vector<ICURun> ItemizeStyle(std::vector<ICURun> &runs_current, FontMap &fon
/* Can't layout an empty string. */ /* Can't layout an empty string. */
if (length == 0) return nullptr; 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); auto runs = ItemizeBidi(buff, length);
runs = ItemizeScript(buff, length, runs); runs = ItemizeScript(buff, length, runs);
runs = ItemizeStyle(runs, font_mapping); runs = ItemizeStyle(runs, font_mapping);

View File

@ -26,6 +26,7 @@
CoreTextFontCache::CoreTextFontCache(FontSize fs, CFAutoRelease<CTFontDescriptorRef> &&font, int pixels) : TrueTypeFontCache(fs, pixels), font_desc(std::move(font)) CoreTextFontCache::CoreTextFontCache(FontSize fs, CFAutoRelease<CTFontDescriptorRef> &&font, int pixels) : TrueTypeFontCache(fs, pixels), font_desc(std::move(font))
{ {
this->SetFontSize(pixels); this->SetFontSize(pixels);
FontCache::UpdateCharacterHeight(this->fs);
} }
/** /**
@ -94,7 +95,7 @@ void CoreTextFontCache::SetFontSize(int pixels)
Debug(fontcache, 2, "Loaded font '{}' with size {}", this->font_name, 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)); assert(IsPrintable(key));
@ -112,10 +113,6 @@ GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
return glyph[0]; return glyph[0];
} }
if (allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
return this->parent->MapCharToGlyph(key);
}
return 0; return 0;
} }
@ -211,7 +208,7 @@ public:
* fallback search, use it. Otherwise, try to resolve it by font name. * fallback search, use it. Otherwise, try to resolve it by font name.
* @param fs The font size to load. * @param fs The font size to load.
*/ */
void LoadFont(FontSize fs, FontType fonttype) void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span<const std::byte> os_handle) override
{ {
if (fonttype != FontType::TrueType) return; if (fonttype != FontType::TrueType) return;
@ -259,7 +256,7 @@ public:
new CoreTextFontCache(fs, std::move(font_ref), GetFontCacheFontSize(fs)); new CoreTextFontCache(fs, std::move(font_ref), GetFontCacheFontSize(fs));
} }
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) override bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, MissingGlyphSearcher *callback)
{ {
/* Determine fallback font using CoreText. This uses the language isocode /* Determine fallback font using CoreText. This uses the language isocode
* to find a suitable font. CoreText is available from 10.5 onwards. */ * to find a suitable font. CoreText is available from 10.5 onwards. */
@ -305,7 +302,7 @@ public:
/* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */ /* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */
if (symbolic_traits & kCTFontBoldTrait) continue; if (symbolic_traits & kCTFontBoldTrait) continue;
/* Select monospaced fonts if asked for. */ /* 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. */ /* Get font name. */
char buffer[128]; char buffer[128];
@ -323,7 +320,13 @@ public:
if (name.starts_with(".") || name.starts_with("LastResort")) continue; if (name.starts_with(".") || name.starts_with("LastResort")) continue;
/* Save result. */ /* Save result. */
callback->SetFontNames(settings, name); for (FontSize fs : bad_mask) {
GetFontCacheSubSetting(fs)->AddFallback(name, std::nullopt);
}
// InitFontCache(bad_mask);
// return true;
if (!callback->FindMissingGlyphs()) { if (!callback->FindMissingGlyphs()) {
Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name); Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name);
result = true; result = true;
@ -335,7 +338,9 @@ public:
if (!result) { if (!result) {
/* For some OS versions, the font 'Arial Unicode MS' does not report all languages it /* 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. */ * supports. If we didn't find any other font, just try it, maybe we get lucky. */
callback->SetFontNames(settings, "Arial Unicode MS"); for (FontSize fs : bad_mask) {
GetFontCacheSubSetting(fs)->AddFallback("Arial Unicode MS", std::nullopt);
}
result = !callback->FindMissingGlyphs(); result = !callback->FindMissingGlyphs();
} }

View File

@ -28,7 +28,7 @@ public:
~CoreTextFontCache() {} ~CoreTextFontCache() {}
void ClearFontCache() override; 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; } std::string GetFontName() override { return font_name; }
bool IsBuiltInFont() override { return false; } bool IsBuiltInFont() override { return false; }
const void *GetOSHandle() override { return font.get(); } const void *GetOSHandle() override { return font.get(); }

View File

@ -52,7 +52,7 @@ extern "C" {
/** Cached current locale. */ /** Cached current locale. */
static CFAutoRelease<CFLocaleRef> _osx_locale; static CFAutoRelease<CFLocaleRef> _osx_locale;
/** CoreText cache for font information, cleared when OTTD changes fonts. */ /** 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;
/** /**
@ -88,7 +88,7 @@ public:
std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; } std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; }
const Font *GetFont() const override { return this->font; } const Font *GetFont() const override { return this->font; }
int GetLeading() const override { return this->font->fc->GetHeight(); } int GetLeading() const override { return GetCharacterHeight(this->font->fc->GetSize()); }
int GetGlyphCount() const override { return (int)this->glyphs.size(); } int GetGlyphCount() const override { return (int)this->glyphs.size(); }
int GetAdvance() const { return this->total_advance; } int GetAdvance() const { return this->total_advance; }
}; };
@ -137,17 +137,17 @@ public:
/** Get the width of an encoded sprite font character. */ /** 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); FontIndex fi = static_cast<FontIndex>(reinterpret_cast<uintptr_t>(ref_con) >> 24);
char32_t c = (char32_t)((size_t)ref_con & 0xFFFFFF); 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 = { static const CTRunDelegateCallbacks _sprite_font_callback = {
kCTRunDelegateCurrentVersion, nullptr, nullptr, nullptr, kCTRunDelegateCurrentVersion, nullptr, nullptr, nullptr,
&SpriteFontGetWidth &CustomFontGetWidth
}; };
/* static */ std::unique_ptr<ParagraphLayouter> CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &font_mapping) /* 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; ptrdiff_t length = buff_end - buff;
if (length == 0) return nullptr; 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. */ /* Make attributed string with embedded font information. */
CFAutoRelease<CFMutableAttributedStringRef> str(CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)); CFAutoRelease<CFMutableAttributedStringRef> str(CFAttributedStringCreateMutable(kCFAllocatorDefault, 0));
CFAttributedStringBeginEditing(str.get()); CFAttributedStringBeginEditing(str.get());
@ -181,12 +176,12 @@ static const CTRunDelegateCallbacks _sprite_font_callback = {
CTFontRef font_handle = static_cast<CTFontRef>(font->fc->GetOSHandle()); CTFontRef font_handle = static_cast<CTFontRef>(font->fc->GetOSHandle());
if (font_handle == nullptr) { if (font_handle == nullptr) {
if (!_font_cache[font->fc->GetSize()]) { if (!_font_cache[font->fc->GetIndex()]) {
/* Cache font information. */ /* Cache font information. */
CFAutoRelease<CFStringRef> font_name(CFStringCreateWithCString(kCFAllocatorDefault, font->fc->GetFontName().c_str(), kCFStringEncodingUTF8)); 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)); _font_cache[font->fc->GetIndex()].reset(CTFontCreateWithName(font_name.get(), font->fc->GetFontSize(), nullptr));
} }
font_handle = _font_cache[font->fc->GetSize()].get(); font_handle = _font_cache[font->fc->GetIndex()].get();
} }
CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle); CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle);
@ -194,10 +189,10 @@ static const CTRunDelegateCallbacks _sprite_font_callback = {
CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, color); CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, color);
CGColorRelease(color); CGColorRelease(color);
/* Install a size callback for our special private-use sprite glyphs in case the font does not provide them. */ /* Install a size callback for our custom fonts. */
for (ssize_t c = last; c < position; c++) { if (font->fc->IsBuiltInFont()) {
if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END && font->fc->MapCharToGlyph(buff[c], false) == 0) { for (ssize_t c = last; c < i.first; c++) {
CFAutoRelease<CTRunDelegateRef> del(CTRunDelegateCreate(&_sprite_font_callback, (void *)(size_t)(buff[c] | (font->fc->GetSize() << 24)))); CFAutoRelease<CTRunDelegateRef> del(CTRunDelegateCreate(&_custom_font_callback, static_cast<void *>(reinterpret_cast<uintptr_t>(buff[c] | (font->fc->GetIndex() << 24)))));
/* According to the official documentation, if a run delegate is used, the char should always be 0xFFFC. */ /* According to the official documentation, if a run delegate is used, the char should always be 0xFFFC. */
CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacment_str.get()); CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacment_str.get());
CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get()); CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get());
@ -247,19 +242,15 @@ CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font
CTRunGetAdvances(run, CFRangeMake(0, 0), advs); CTRunGetAdvances(run, CFRangeMake(0, 0), advs);
this->positions.reserve(this->glyphs.size()); this->positions.reserve(this->glyphs.size());
int y_offset = this->font->fc->GetGlyphYOffset();
/* Convert glyph array to our data type. At the same time, substitute /* Convert glyph array to our data type. At the same time, substitute
* the proper glyphs for our private sprite glyphs. */ * the proper glyphs for our private sprite glyphs. */
CGGlyph gl[this->glyphs.size()]; CGGlyph gl[this->glyphs.size()];
CTRunGetGlyphs(run, CFRangeMake(0, 0), gl); CTRunGetGlyphs(run, CFRangeMake(0, 0), gl);
for (size_t i = 0; i < this->glyphs.size(); i++) { 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)) { this->glyphs[i] = gl[i];
/* A glyph of 0 indidicates not found, while apparently 3 is what char 0xFFFC maps to. */ this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y + y_offset);
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->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nullptr, nullptr, nullptr)); this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nullptr, nullptr, nullptr));
} }

View File

@ -120,10 +120,14 @@ 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; return err;
} }
bool FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) bool FontConfigSetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, MissingGlyphSearcher *callback)
{ {
bool ret = false; bool ret = false;
@ -132,22 +136,25 @@ bool FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &l
auto fc_instance = AutoRelease<FcConfig, FcConfigDestroy>(FcConfigReference(nullptr)); auto fc_instance = AutoRelease<FcConfig, FcConfigDestroy>(FcConfigReference(nullptr));
assert(fc_instance != nullptr); assert(fc_instance != nullptr);
/* Get set of required characters. XXX Do we know what font size we want here? */
auto chars = callback->GetRequiredGlyphs(bad_mask);
/* Fontconfig doesn't handle full language isocodes, only the part /* Fontconfig doesn't handle full language isocodes, only the part
* before the _ of e.g. en_GB is used, so "remove" everything after * before the _ of e.g. en_GB is used, so "remove" everything after
* the _. */ * 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. */ /* First create a pattern to match the wanted language. */
auto pat = AutoRelease<FcPattern, FcPatternDestroy>(FcNameParse(ToFcString(lang))); auto pat = AutoRelease<FcPattern, FcPatternDestroy>(FcNameParse(ToFcString(lang)));
/* We only want to know these attributes. */ /* 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. */ /* Get the list of filenames matching the wanted language. */
auto fs = AutoRelease<FcFontSet, FcFontSetDestroy>(FcFontList(nullptr, pat.get(), os.get())); auto fs = AutoRelease<FcFontSet, FcFontSetDestroy>(FcFontList(nullptr, pat.get(), os.get()));
if (fs == nullptr) return ret; if (fs == nullptr) return ret;
int best_weight = -1; int best_weight = -1;
const char *best_font = nullptr; std::string best_font;
int best_index = 0; int best_index = 0;
for (FcPattern *font : std::span(fs->fonts, fs->nfont)) { for (FcPattern *font : std::span(fs->fonts, fs->nfont)) {
@ -158,7 +165,7 @@ bool FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &l
/* Get a font with the right spacing .*/ /* Get a font with the right spacing .*/
int value = 0; int value = 0;
FcPatternGetInteger(font, FC_SPACING, 0, &value); FcPatternGetInteger(font, FC_SPACING, 0, &value);
if (callback->Monospace() != (value == FC_MONO) && value != FC_DUAL) continue; if (bad_mask.Test(FS_MONO) != (value == FC_MONO) && value != FC_DUAL) continue;
/* Do not use those that explicitly say they're slanted. */ /* Do not use those that explicitly say they're slanted. */
FcPatternGetInteger(font, FC_SLANT, 0, &value); FcPatternGetInteger(font, FC_SLANT, 0, &value);
@ -168,26 +175,35 @@ bool FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &l
FcPatternGetInteger(font, FC_WEIGHT, 0, &value); FcPatternGetInteger(font, FC_WEIGHT, 0, &value);
if (value <= best_weight) continue; if (value <= 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, 0, "Font \"{}\" misses {} glyphs", (char *)file, chars.size() - matching_chars);
continue;
}
/* Possible match based on attributes, get index. */ /* Possible match based on attributes, get index. */
int32_t index; int32_t index;
res = FcPatternGetInteger(font, FC_INDEX, 0, &index); res = FcPatternGetInteger(font, FC_INDEX, 0, &index);
if (res != FcResultMatch) continue; if (res != FcResultMatch) continue;
callback->SetFontNames(settings, FromFcString(file), &index); best_weight = value;
best_font = FromFcString(file);
bool missing = callback->FindMissingGlyphs(); best_index = index;
Debug(fontcache, 1, "Font \"{}\" misses{} glyphs", FromFcString(file), missing ? "" : " no");
if (!missing) {
best_weight = value;
best_font = FromFcString(file);
best_index = index;
}
} }
if (best_font == nullptr) return false; if (best_font.empty()) return false;
for (FontSize fs : bad_mask) {
GetFontCacheSubSetting(fs)->AddFallback(best_font, best_index);
}
InitFontCache(bad_mask);
callback->SetFontNames(settings, best_font, &best_index);
InitFontCache(callback->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
return true; return true;
} }

View File

@ -19,7 +19,7 @@
FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face); FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face);
bool FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); bool FontConfigSetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, MissingGlyphSearcher *callback);
#endif /* WITH_FONTCONFIG */ #endif /* WITH_FONTCONFIG */

View File

@ -31,8 +31,8 @@
#include "../../safeguards.h" #include "../../safeguards.h"
struct EFCParam { struct EFCParam {
FontCacheSettings *settings; LOCALESIGNATURE locale;
LOCALESIGNATURE locale; FontSizes fontsizes;
MissingGlyphSearcher *callback; MissingGlyphSearcher *callback;
std::vector<std::wstring> fonts; std::vector<std::wstring> fonts;
@ -59,7 +59,7 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
/* Don't use SYMBOL fonts */ /* Don't use SYMBOL fonts */
if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1; if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1;
/* Use monospaced fonts when asked for it. */ /* 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. */ /* The font has to have at least one of the supported locales to be usable. */
auto check_bitfields = [&]() { auto check_bitfields = [&]() {
@ -78,8 +78,15 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
char font_name[MAX_PATH]; char font_name[MAX_PATH];
convert_from_fs(logfont->elfFullName, font_name); convert_from_fs(logfont->elfFullName, font_name);
info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont); Debug(misc, 0, "Trying font {}", font_name);
if (info->callback->FindMissingGlyphs()) return 1; for (FontSize fs : info->fontsizes) {
GetFontCacheSubSetting(fs)->AddFallback(font_name, logfont->elfLogFont);
}
InitFontCache(info->fontsizes);
if (info->callback->FindMissingGlyphs().None()) {
return 1;
}
Debug(fontcache, 1, "Fallback font: {}", font_name); Debug(fontcache, 1, "Fallback font: {}", font_name);
return 0; // stop enumerating return 0; // stop enumerating
} }
@ -156,10 +163,11 @@ void Win32FontCache::SetFontSize(int pixels)
this->height = this->ascender + this->descender; this->height = this->ascender + this->descender;
this->glyph_size.cx = otm->otmTextMetrics.tmMaxCharWidth; this->glyph_size.cx = otm->otmTextMetrics.tmMaxCharWidth;
this->glyph_size.cy = otm->otmTextMetrics.tmHeight; this->glyph_size.cy = otm->otmTextMetrics.tmHeight;
FontCache::UpdateCharacterHeight(this->fs);
this->fontname = FS2OTTD((LPWSTR)((BYTE *)otm + (ptrdiff_t)otm->otmpFaceName)); 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; delete[] (BYTE*)otm;
} }
@ -246,7 +254,7 @@ void Win32FontCache::ClearFontCache()
return this->SetGlyphPtr(key, std::move(new_glyph)).GetSprite(); 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)); assert(IsPrintable(key));
@ -263,7 +271,7 @@ void Win32FontCache::ClearFontCache()
GetGlyphIndicesW(this->dc, chars, key >= 0x010000U ? 2 : 1, glyphs, GGI_MARK_NONEXISTING_GLYPHS); GetGlyphIndicesW(this->dc, chars, key >= 0x010000U ? 2 : 1, glyphs, GGI_MARK_NONEXISTING_GLYPHS);
if (glyphs[0] != 0xFFFF) return glyphs[0]; 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 { class Win32FontCacheFactory : FontCacheFactory {
@ -271,32 +279,28 @@ public:
Win32FontCacheFactory() : FontCacheFactory("win32", "Win32 font loader") {} Win32FontCacheFactory() : FontCacheFactory("win32", "Win32 font loader") {}
/** /**
* Loads the GDI font. * Loads the GDI font.
* If a GDI font description is present, e.g. from the automatic 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. * fallback search, use it. Otherwise, try to resolve it by font name.
* @param fs The font size to load. * @param fs The font size to load.
*/ */
void LoadFont(FontSize fs, FontType fonttype) override void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span<const std::byte> os_handle) override
{ {
if (fonttype != FontType::TrueType) return; if (fonttype != FontType::TrueType) return;
FontCacheSubSetting *settings = GetFontCacheSubSetting(fs);
std::string font = GetFontCacheFontName(fs);
if (font.empty()) return;
LOGFONT logfont{}; LOGFONT logfont{};
logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH; logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH;
logfont.lfCharSet = DEFAULT_CHARSET; logfont.lfCharSet = DEFAULT_CHARSET;
logfont.lfOutPrecision = OUT_OUTLINE_PRECIS; logfont.lfOutPrecision = OUT_OUTLINE_PRECIS;
logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
if (settings->os_handle != nullptr) { if (!os_handle.empty()) {
logfont = *(const LOGFONT *)settings->os_handle; logfont = *reinterpret_cast<const LOGFONT *>(os_handle.data());
} else if (font.find('.') != std::string::npos) { } else if (font.find('.') != std::string::npos) {
/* Might be a font file name, try load it. */ /* Might be a font file name, try load it. */
if (!TryLoadFontFromFile(font, logfont)) { if (!TryLoadFontFromFile(font, logfont)) {
ShowInfo("Unable to load file '{}' for {} font, using default windows font selection instead", font, FontSizeToName(fs)); ShowInfo("Unable to load file '{}' for {} font, using default windows font selection instead", font, FontSizeToName(fs));
if (!search) return;
} }
} }
@ -308,7 +312,7 @@ public:
LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font);
} }
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) override bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, MissingGlyphSearcher *callback) override
{ {
Debug(fontcache, 1, "Trying fallback fonts"); Debug(fontcache, 1, "Trying fallback fonts");
EFCParam langInfo; EFCParam langInfo;
@ -318,7 +322,7 @@ public:
Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode); Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode);
return false; return false;
} }
langInfo.settings = settings; langInfo.fontsizes = bad_mask;
langInfo.callback = callback; langInfo.callback = callback;
LOGFONT font; LOGFONT font;

View File

@ -37,11 +37,11 @@ public:
Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels); Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels);
~Win32FontCache(); ~Win32FontCache();
void ClearFontCache() override; 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; } std::string GetFontName() override { return this->fontname; }
const void *GetOSHandle() override { return &this->logfont; } 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 */ #endif /* FONT_WIN32_H */

View File

@ -10,6 +10,7 @@
#include "../../stdafx.h" #include "../../stdafx.h"
#include "../../debug.h" #include "../../debug.h"
#include "string_uniscribe.h" #include "string_uniscribe.h"
#include "../../gfx_layout_fallback.h"
#include "../../language.h" #include "../../language.h"
#include "../../strings_func.h" #include "../../strings_func.h"
#include "../../string_func.h" #include "../../string_func.h"
@ -29,7 +30,7 @@
/** Uniscribe cache for internal font information, cleared when OTTD changes fonts. */ /** 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 * Contains all information about a run of characters. A run are consecutive
@ -52,6 +53,8 @@ struct UniscribeRun {
int total_advance; 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, 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. */ /** Break a string into language formatting ranges. */
@ -94,7 +97,7 @@ public:
std::span<const int> GetGlyphToCharMap() const override; std::span<const int> GetGlyphToCharMap() const override;
const Font *GetFont() const override { return this->font; } const Font *GetFont() const override { return this->font; }
int GetLeading() const override { return this->font->fc->GetHeight(); } int GetLeading() const override { return GetCharacterHeight(this->font->fc->GetSize()); }
int GetGlyphCount() const override { return this->num_glyphs; } int GetGlyphCount() const override { return this->num_glyphs; }
int GetAdvance() const { return this->total_advance; } int GetAdvance() const { return this->total_advance; }
}; };
@ -130,12 +133,12 @@ public:
std::unique_ptr<const Line> NextLine(int max_width) override; std::unique_ptr<const Line> NextLine(int max_width) override;
}; };
void UniscribeResetScriptCache(FontSize size) void UniscribeResetScriptCache(FontSize)
{ {
if (_script_cache[size] != nullptr) { for (auto &sc : _script_cache) {
ScriptFreeCache(&_script_cache[size]); ScriptFreeCache(&sc.second);
_script_cache[size] = nullptr;
} }
_script_cache.clear();
} }
/** Load the matching native Windows font. */ /** Load the matching native Windows font. */
@ -152,6 +155,41 @@ static HFONT HFontFromFont(Font *font)
return CreateFontIndirect(&logfont); 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)
{
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) {
char32_t c = Utf16DecodeChar(reinterpret_cast<const char16_t *>(buff + i));
if (this->sa.fRTL) c = SwapRtlPairedCharacters(c);
this->glyphs.emplace_back(this->font->fc->MapCharToGlyph(c));
if (Utf16IsLeadSurrogate(*(buff + i))) ++i;
}
/* 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 = this->font->fc->GetGlyphYOffset();
int advance = 0;
for (const GlyphID glyph : this->glyphs) {
this->offsets.emplace_back(advance, y_offset);
int x_advance = this->font->fc->GetGlyphWidth(glyph);
this->advances.push_back(x_advance);
advance += x_advance;
}
}
/** Determine the glyph positions for a run. */ /** Determine the glyph positions for a run. */
static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *buff, UniscribeRun &range) static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *buff, UniscribeRun &range)
{ {
@ -166,10 +204,15 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b
HFONT old_font = nullptr; HFONT old_font = nullptr;
HFONT cur_font = nullptr; HFONT cur_font = nullptr;
if (range.font->fc->IsBuiltInFont()) {
range.FallbackShape(buff);
return true;
}
while (true) { while (true) {
/* Shape the text run by determining the glyphs needed for display. */ /* Shape the text run by determining the glyphs needed for display. */
int glyphs_used = 0; 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[range.font->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)) { if (SUCCEEDED(hr)) {
range.glyphs.resize(glyphs_used); range.glyphs.resize(glyphs_used);
@ -179,7 +222,7 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b
ABC abc; ABC abc;
range.advances.resize(range.glyphs.size()); range.advances.resize(range.glyphs.size());
range.offsets.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[range.font->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)) { if (SUCCEEDED(hr)) {
/* We map our special sprite chars to values that don't fit into a WORD. Copy the glyphs /* 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. */ * into a new vector and query the real glyph to use for these special chars. */
@ -187,22 +230,12 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b
for (size_t g_id = 0; g_id < range.glyphs.size(); g_id++) { for (size_t g_id = 0; g_id < range.glyphs.size(); g_id++) {
range.ft_glyphs[g_id] = range.glyphs[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; range.total_advance = 0;
for (size_t i = 0; i < range.advances.size(); i++) { for (size_t i = 0; i < range.advances.size(); i++) {
#ifdef WITH_FREETYPE #ifdef WITH_FREETYPE
/* FreeType and GDI/Uniscribe seems to occasionally disagree over the width of a glyph. */ /* 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] = range.font->fc->GetGlyphWidth(range.glyphs[i]);
#endif #endif
range.total_advance += range.advances[i]; range.total_advance += range.advances[i];
} }
@ -280,11 +313,6 @@ static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutF
/* Can't layout an empty string. */ /* Can't layout an empty string. */
if (length == 0) return nullptr; 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. */ /* Itemize text. */
std::vector<SCRIPT_ITEM> items = UniscribeItemizeString(buff, length); std::vector<SCRIPT_ITEM> items = UniscribeItemizeString(buff, length);
if (items.empty()) return nullptr; if (items.empty()) return nullptr;

View File

@ -697,7 +697,7 @@ public:
* break point, but we only want word starts. Move to the next location in * break point, but we only want word starts. Move to the next location in
* case the new position points to whitespace. */ * case the new position points to whitespace. */
while (pos != icu::BreakIterator::DONE && while (pos != icu::BreakIterator::DONE &&
IsWhitespace(Utf16DecodeChar((const uint16_t *)&this->utf16_str[pos]))) { IsWhitespace(Utf16DecodeChar(&this->utf16_str[pos]))) {
int32_t new_pos = this->word_itr->next(); int32_t new_pos = this->word_itr->next();
/* Don't set it to DONE if it was valid before. Otherwise we'll return END /* Don't set it to DONE if it was valid before. Otherwise we'll return END
* even though the iterator wasn't at the end of the string before. */ * even though the iterator wasn't at the end of the string before. */
@ -729,7 +729,7 @@ public:
* break point, but we only want word starts. Move to the previous location in * break point, but we only want word starts. Move to the previous location in
* case the new position points to whitespace. */ * case the new position points to whitespace. */
while (pos != icu::BreakIterator::DONE && while (pos != icu::BreakIterator::DONE &&
IsWhitespace(Utf16DecodeChar((const uint16_t *)&this->utf16_str[pos]))) { IsWhitespace(Utf16DecodeChar(&this->utf16_str[pos]))) {
int32_t new_pos = this->word_itr->previous(); int32_t new_pos = this->word_itr->previous();
/* Don't set it to DONE if it was valid before. Otherwise we'll return END /* Don't set it to DONE if it was valid before. Otherwise we'll return END
* even though the iterator wasn't at the start of the string before. */ * even though the iterator wasn't at the start of the string before. */

View File

@ -94,7 +94,7 @@ inline char32_t Utf16DecodeSurrogate(uint lead, uint trail)
* @param c Pointer to one or two UTF-16 code points. * @param c Pointer to one or two UTF-16 code points.
* @return Decoded Unicode character. * @return Decoded Unicode character.
*/ */
inline char32_t Utf16DecodeChar(const uint16_t *c) inline char32_t Utf16DecodeChar(const char16_t *c)
{ {
if (Utf16IsLeadSurrogate(c[0])) { if (Utf16IsLeadSurrogate(c[0])) {
return Utf16DecodeSurrogate(c[0], c[1]); return Utf16DecodeSurrogate(c[0], c[1]);

View File

@ -2252,42 +2252,60 @@ std::string_view GetCurrentLanguageIsoCode()
/** /**
* Check whether there are glyphs missing in the current language. * 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()
{ {
InitFontCache(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); for (FontSize size : this->font_sizes) {
GetFontCacheSubSetting(size)->fallback_fonts.clear();
}
InitFontCache(this->font_sizes);
FontSizes bad_fontsizes{};
for (FontSize size : this->font_sizes) {
auto set = this->GetRequiredGlyphs(size);
if (set.empty()) continue;
Debug(fontcache, 0, "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(); this->Reset();
for (auto text = this->NextString(); text.has_value(); text = this->NextString()) { for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
FontSize size = this->DefaultSize(); FontSize size = this->DefaultSize();
FontCache *fc = FontCache::Get(size);
for (char32_t c : Utf8View(*text)) { for (char32_t c : Utf8View(*text)) {
if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) { if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
size = (FontSize)(c - SCC_FIRST_FONT); size = (FontSize)(c - SCC_FIRST_FONT);
fc = FontCache::Get(size); continue;
} 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. */
std::string size_name;
switch (size) {
case FS_NORMAL: size_name = "medium"; break;
case FS_SMALL: size_name = "small"; break;
case FS_LARGE: size_name = "large"; break;
case FS_MONO: size_name = "mono"; break;
default: NOT_REACHED();
}
Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", (int)c, size_name);
return true;
} }
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. */ /** 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 i; ///< Iterator for the primary language tables.
uint j; ///< Iterator for the secondary language tables. uint j; ///< Iterator for the secondary language tables.
@ -2316,26 +2334,11 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
return ret; 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
}
}; };
static LanguagePackGlyphSearcher _language_pack_searcher;
/** /**
* Check whether the currently loaded language pack * Check whether the currently loaded language pack
* uses characters that the currently loaded font * uses characters that the currently loaded font
@ -2351,22 +2354,17 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
*/ */
void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher) void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
{ {
static LanguagePackGlyphSearcher pack_searcher; if (searcher == nullptr) searcher = &_language_pack_searcher;
if (searcher == nullptr) searcher = &pack_searcher; FontSizes bad_mask = searcher->FindMissingGlyphs();
bool bad_font = !base_font || searcher->FindMissingGlyphs(); bool bad_font = bad_mask.Any();
#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
if (bad_font) { if (bad_mask.Any()) {
/* We found an unprintable character... lets try whether we can find /* We found an unprintable character... lets try whether we can find
* a fallback font that can print the characters in the current language. */ * a fallback font that can print the characters in the current language. */
bool any_font_configured = !_fcsettings.medium.font.empty(); bool any_font_configured = !_fcsettings.medium.font.empty();
FontCacheSettings backup = _fcsettings;
_fcsettings.mono.os_handle = nullptr; bad_font = !FontProviderManager::SetFallbackFont(_langpack.langpack->isocode, bad_mask, searcher);
_fcsettings.medium.os_handle = nullptr;
bad_font = !FontProviderManager::SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher);
_fcsettings = std::move(backup);
if (!bad_font && any_font_configured) { if (!bad_font && any_font_configured) {
/* If the user configured a bad font, and we found a better one, /* If the user configured a bad font, and we found a better one,
@ -2383,7 +2381,7 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
/* Our fallback font does miss characters too, so keep the /* Our fallback font does miss characters too, so keep the
* user chosen font as that is more likely to be any good than * user chosen font as that is more likely to be any good than
* the wild guess we made */ * the wild guess we made */
InitFontCache(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); InitFontCache(bad_mask);
} }
} }
#endif #endif
@ -2400,12 +2398,12 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING); ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING);
/* Reset the font width */ /* Reset the font width */
LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); LoadStringWidthTable(bad_mask);
return; return;
} }
/* Update the font with cache */ /* Update the font with cache */
LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); LoadStringWidthTable(searcher->font_sizes);
#if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA) #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
/* /*

View File

@ -157,9 +157,31 @@ EncodedString GetEncodedString(StringID string, const Args&... args)
*/ */
class MissingGlyphSearcher { class MissingGlyphSearcher {
public: public:
FontSizes font_sizes; ///< Font sizes to search for.
MissingGlyphSearcher(FontSizes font_sizes) : font_sizes(font_sizes) {}
/** Make sure everything gets destructed right. */ /** Make sure everything gets destructed right. */
virtual ~MissingGlyphSearcher() = default; virtual ~MissingGlyphSearcher() = default;
/**
* Test if any glyphs are missing.
* @return Font sizes which have missing glyphs.
*/
FontSizes FindMissingGlyphs();
/**
* 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 font_sizes) : MissingGlyphSearcher(font_sizes) {}
/** /**
* Get the next string to search through. * Get the next string to search through.
* @return The next string or nullopt if there is none. * @return The next string or nullopt if there is none.
@ -177,23 +199,9 @@ public:
*/ */
virtual void Reset() = 0; virtual void Reset() = 0;
/** std::set<char32_t> GetRequiredGlyphs(FontSizes fontsizes) override;
* Whether to search for a monospace font or not.
* @return True if searching for monospace.
*/
virtual bool Monospace() = 0;
/**
* 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();
}; };
void CheckForMissingGlyphs(bool base_font = true, MissingGlyphSearcher *search = nullptr); void CheckForMissingGlyphs(bool base_font = true, MissingGlyphSearcher *searcher = nullptr);
#endif /* STRINGS_FUNC_H */ #endif /* STRINGS_FUNC_H */

View File

@ -298,10 +298,16 @@ void SurveyConfiguration(nlohmann::json &survey)
*/ */
void SurveyFont(nlohmann::json &survey) void SurveyFont(nlohmann::json &survey)
{ {
survey["small"] = FontCache::Get(FS_SMALL)->GetFontName(); for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
survey["medium"] = FontCache::Get(FS_NORMAL)->GetFontName(); const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
survey["large"] = FontCache::Get(FS_LARGE)->GetFontName(); auto &font = survey[std::string(FontSizeToName(fs))];
survey["mono"] = FontCache::Get(FS_MONO)->GetFontName(); 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());
}
} }
/** /**

View File

@ -19,20 +19,22 @@ public:
MockFontCache(FontSize fs) : FontCache(fs) MockFontCache(FontSize fs) : FontCache(fs)
{ {
this->height = FontCache::GetDefaultFontHeight(this->fs); this->height = FontCache::GetDefaultFontHeight(this->fs);
FontCache::UpdateCharacterHeight(this->fs);
} }
void ClearFontCache() override {} void ClearFontCache() override {}
const Sprite *GetGlyph(GlyphID) override { return nullptr; } const Sprite *GetGlyph(GlyphID) override { return nullptr; }
uint GetGlyphWidth(GlyphID) override { return this->height / 2; } uint GetGlyphWidth(GlyphID) override { return this->height / 2; }
bool GetDrawGlyphShadow() override { return false; } 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"; } std::string GetFontName() override { return "mock"; }
bool IsBuiltInFont() override { return true; } bool IsBuiltInFont() override { return true; }
static void InitializeFontCaches() static void InitializeFontCaches()
{ {
FontCache::caches.clear();
for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) {
if (FontCache::caches[fs] == nullptr) new MockFontCache(fs); /* FontCache inserts itself into to the cache. */ new MockFontCache(fs); /* FontCache inserts itself into to the cache. */
} }
} }
}; };

View File

@ -83,7 +83,7 @@ static WindowDesc _textfile_desc(
_nested_textfile_widgets _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. /* Init of nested tree is deferred.
* TextfileWindow::ConstructWindow must be called by the inheriting window. */ * TextfileWindow::ConstructWindow must be called by the inheriting window. */
@ -754,19 +754,6 @@ bool TextfileWindow::IsTextWrapped() const
return this->lines[this->search_iterator++].text; 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) #if defined(WITH_ZLIB)
/** /**

View File

@ -19,7 +19,7 @@
std::optional<std::string> GetTextfile(TextfileType type, Subdirectory dir, std::string_view filename); std::optional<std::string> GetTextfile(TextfileType type, Subdirectory dir, std::string_view filename);
/** Window for displaying a textfile */ /** Window for displaying a textfile */
struct TextfileWindow : public Window, MissingGlyphSearcher { struct TextfileWindow : public Window, BaseStringMissingGlyphSearcher {
TextfileType file_type{}; ///< Type of textfile to view. TextfileType file_type{}; ///< Type of textfile to view.
Scrollbar *vscroll = nullptr; ///< Vertical scrollbar. Scrollbar *vscroll = nullptr; ///< Vertical scrollbar.
Scrollbar *hscroll = nullptr; ///< Horizontal scrollbar. Scrollbar *hscroll = nullptr; ///< Horizontal scrollbar.
@ -38,8 +38,6 @@ struct TextfileWindow : public Window, MissingGlyphSearcher {
void Reset() override; void Reset() override;
FontSize DefaultSize() override; FontSize DefaultSize() override;
std::optional<std::string_view> NextString() 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); void ScrollToLine(size_t line);
bool IsTextWrapped() const; bool IsTextWrapped() const;