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);
}
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
FontCache *fc = FontCache::Get(fs);
FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
/* Make sure all non sprite fonts are loaded. */
if (!setting->font.empty() && !fc->HasParent()) {
InitFontCache(fs);
fc = FontCache::Get(fs);
IConsolePrint(CC_INFO, "Configured fonts:");
for (uint i = 0; FontSize fs : FONTSIZES_ALL) {
const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\", size {}", i, FontSizeToName(fs), setting->font, setting->size);
++i;
}
IConsolePrint(CC_DEFAULT, "{} font:", FontSizeToName(fs));
IConsolePrint(CC_DEFAULT, "Currently active: \"{}\", size {}", fc->GetFontName(), fc->GetFontSize());
IConsolePrint(CC_DEFAULT, "Requested: \"{}\", size {}", setting->font, setting->size);
IConsolePrint(CC_INFO, "Currently active fonts:");
for (uint i = 0; const auto &fc : FontCache::Get()) {
if (fc == nullptr) continue;
IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\" size {}", i, FontSizeToName(fc->GetSize()), fc->GetFontName(), fc->GetFontSize());
++i;
}
return true;

View File

@ -8,6 +8,8 @@
/** @file fontcache.cpp Cache for characters from fonts. */
#include "stdafx.h"
#include "core/string_consumer.hpp"
#include "fontcache.h"
#include "blitter/factory.hpp"
#include "gfx_layout.h"
@ -17,13 +19,10 @@
#include "viewport_func.h"
#include "window_func.h"
#include "fileio_func.h"
#include "zoom_func.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;
/**
@ -31,10 +30,10 @@ FontCacheSettings _fcsettings;
* @param fs Font size to load.
* @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()) {
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.
* @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()) {
if (provider->SetFallbackFont(settings, language_isocode, callback)) {
if (provider->SetFallbackFont(language_isocode, bad_mask, callback)) {
return true;
}
}
@ -61,42 +60,49 @@ FontCacheSettings _fcsettings;
* Create a new font cache.
* @param fs The size of the font.
*/
FontCache::FontCache(FontSize fs) : parent(FontCache::Get(fs)), fs(fs), height(_default_font_height[fs]),
ascender(_default_font_ascender[fs]), descender(_default_font_ascender[fs] - _default_font_height[fs])
FontCache::FontCache(FontSize fs) : fs(fs)
{
assert(this->parent == nullptr || this->fs == this->parent->fs);
FontCache::caches[this->fs] = this;
/* Find an empty font cache slot. */
auto it = std::find(std::begin(FontCache::caches), std::end(FontCache::caches), nullptr);
if (it == std::end(FontCache::caches)) it = FontCache::caches.insert(it, nullptr);
/* 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);
}
/** Clean everything up. */
FontCache::~FontCache()
{
assert(this->fs == this->parent->fs);
FontCache::caches[this->fs] = this->parent;
Layouter::ResetFontCache(this->fs);
}
int FontCache::GetDefaultFontHeight(FontSize fs)
{
return _default_font_height[fs];
return DEFAULT_FONT_HEIGHT[fs];
}
/**
* Get the font name of a given font size.
* @param fs The font size to look up.
* @return The font name.
*/
std::string FontCache::GetName(FontSize fs)
/* static */ void FontCache::UpdateCharacterHeight(FontSize fs)
{
FontCache *fc = FontCache::Get(fs);
if (fc != nullptr) {
return fc->GetFontName();
} else {
return "[NULL]";
FontCache::max_height[fs] = 0;
for (const auto &fc : FontCache::caches) {
if (fc == nullptr || fc->fs != fs) continue;
FontCache::max_height[fs] = std::max(FontCache::max_height[fs], fc->height);
}
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.
@ -105,16 +111,19 @@ std::string FontCache::GetName(FontSize fs)
*/
int GetCharacterHeight(FontSize size)
{
return FontCache::Get(size)->GetHeight();
uint height = FontCache::GetCharacterHeight(size);
if (height == 0) height = ScaleGUITrad(FontCache::GetDefaultFontHeight(FS_MONO));
return height;
}
/* static */ FontCache *FontCache::caches[FS_END];
/* static */ FontCache::FontCaches FontCache::caches;
/* static */ std::array<int, FS_END> FontCache::max_height{};
/* static */ std::array<FontIndex, FS_END> FontCache::default_font_index{};
/* static */ void FontCache::InitializeFontCaches()
{
for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) {
if (FontCache::caches[fs] == nullptr) FontProviderManager::LoadFont(fs, FontType::Sprite); /* FontCache inserts itself into to the cache. */
for (FontSize fs : FONTSIZES_ALL) {
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 (fontsize != FS_MONO) {
/* Try to reload only the modified font. */
FontCacheSettings backup = _fcsettings;
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
if (fs == fontsize) continue;
FontCache *fc = FontCache::Get(fs);
GetFontCacheSubSetting(fs)->font = fc->HasParent() ? fc->GetFontName() : "";
}
/* Check if fallback fonts are needed. */
CheckForMissingGlyphs();
_fcsettings = std::move(backup);
} else {
InitFontCache(fontsize);
}
@ -171,7 +173,7 @@ void SetFont(FontSize fontsize, const std::string &font, uint size)
*/
static bool IsDefaultFont(const FontCacheSubSetting &setting)
{
return setting.font.empty() && setting.os_handle == nullptr;
return setting.font.empty();
}
/**
@ -208,7 +210,7 @@ static std::string GetDefaultTruetypeFont(FontSize fs)
* @param fs Font size.
* @return Full path of default font file.
*/
static std::string GetDefaultTruetypeFontFile([[maybe_unused]] FontSize fs)
std::string GetDefaultTruetypeFontFile([[maybe_unused]] FontSize fs)
{
#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
/* Find font file. */
@ -237,13 +239,52 @@ std::string GetFontCacheFontName(FontSize fs)
*/
void InitFontCache(FontSizes fontsizes)
{
FontCache::InitializeFontCaches();
static constexpr std::string_view DEFAULT_FONT = "default";
for (FontSize fs : fontsizes) {
FontCache *fc = FontCache::Get(fs);
if (fc->HasParent()) delete fc;
Layouter::ResetFontCache(fs);
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()
{
for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
FontCache *fc = FontCache::Get(fs);
if (fc->HasParent()) delete fc;
}
FontCache::caches.clear();
}

View File

@ -16,17 +16,31 @@
/** Glyphs are characters from a font. */
typedef uint32_t GlyphID;
static const GlyphID SPRITE_GLYPH = 1U << 30;
using FontIndex = uint8_t;
static const FontIndex INVALID_FONT_INDEX = std::numeric_limits<FontIndex>::max();
/** 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. */
class FontCache {
protected:
static FontCache *caches[FS_END]; ///< All the font caches.
FontCache *parent; ///< The parent of this font cache.
using FontCaches = std::vector<std::unique_ptr<FontCache>>;
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.
int height; ///< The height of the font.
int ascender; ///< The ascender value of the font.
int descender; ///< The descender value of the font.
FontIndex font_index; ///< The index of the font.
int height = 0; ///< The height of the font.
int ascender = 0; ///< The ascender value of the font.
int descender = 0; ///< The descender value of the font.
friend void UninitFontCache();
friend void InitFontCache(FontSizes fontsizes);
public:
FontCache(FontSize fs);
@ -42,6 +56,8 @@ public:
*/
inline FontSize GetSize() const { return this->fs; }
inline FontIndex GetIndex() const { return this->font_index; }
/**
* Get the height of the font.
* @return The height of the font.
@ -92,10 +108,9 @@ public:
/**
* Map a character into a glyph.
* @param key The character.
* @param fallback Allow fallback to the parent font.
* @return The glyph ID used to draw the character.
*/
virtual GlyphID MapCharToGlyph(char32_t key, bool fallback = true) = 0;
virtual GlyphID MapCharToGlyph(char32_t key) = 0;
/**
* Get the native OS font handle, if there is one.
@ -112,25 +127,57 @@ public:
*/
virtual std::string GetFontName() = 0;
virtual int GetGlyphYOffset();
/**
* Get span of all FontCaches.
* @return Span of all FontCaches.
*/
static inline std::span<const std::unique_ptr<FontCache>> Get()
{
return FontCache::caches;
}
/**
* Get the font cache of a given font size.
* @param fs The font size to look up.
* @return The font cache.
*/
static inline FontCache *Get(FontSize fs)
static inline FontCache *Get(FontIndex font_index)
{
assert(fs < FS_END);
return FontCache::caches[fs];
assert(font_index < FontCache::caches.size());
return FontCache::caches[font_index].get();
}
static std::string GetName(FontSize fs);
/**
* Check whether the font cache has a parent.
*/
inline bool HasParent()
static inline int GetCharacterHeight(FontSize fs)
{
return this->parent != nullptr;
return FontCache::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)
{
for (FontSize fs : fontsizes) {
FontCache::Get(fs)->ClearFontCache();
for (const auto &fc : FontCache::Get()) {
if (fc == nullptr) continue;
if (!fontsizes.Test(fc->GetSize())) continue;
fc->ClearFontCache();
}
}
/** Get the Sprite for a glyph */
inline const Sprite *GetGlyph(FontSize size, char32_t key)
{
FontCache *fc = FontCache::Get(size);
FontIndex font_index = FontCache::GetFontIndexForCharacter(size, key);
FontCache *fc = font_index != INVALID_FONT_INDEX ? FontCache::Get(font_index) : FontCache::GetDefaultFontCache(size);
if (fc == nullptr) return nullptr;
return fc->GetGlyph(fc->MapCharToGlyph(key));
}
/** Get the width of a glyph */
inline uint GetGlyphWidth(FontSize size, char32_t key)
{
FontCache *fc = FontCache::Get(size);
FontIndex font_index = FontCache::GetFontIndexForCharacter(size, key);
FontCache *fc = font_index != INVALID_FONT_INDEX ? FontCache::Get(font_index) : FontCache::GetDefaultFontCache(size);
if (fc == nullptr) return 0;
return fc->GetGlyphWidth(fc->MapCharToGlyph(key));
}
inline bool GetDrawGlyphShadow(FontSize size)
{
return FontCache::Get(size)->GetDrawGlyphShadow();
}
/** Settings for a single font. */
struct FontCacheSubSetting {
std::string font; ///< The name of the font, or path to the font.
uint size; ///< The (requested) size of the font.
const void *os_handle = nullptr; ///< Optional native OS font info. Only valid during font search.
struct FontCacheFallback {
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. */
@ -228,14 +294,14 @@ public:
ProviderManager<FontCacheFactory>::Unregister(*this);
}
virtual void LoadFont(FontSize fs, FontType fonttype) = 0;
virtual bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) = 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(const std::string &language_isocode, FontSizes bad_mask, class MissingGlyphSearcher *callback) = 0;
};
class FontProviderManager : ProviderManager<FontCacheFactory> {
public:
static void LoadFont(FontSize fs, FontType fonttype);
static bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback);
static void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span<const std::byte> os_handle);
static bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, class MissingGlyphSearcher *callback);
};
/* Implemented in spritefontcache.cpp */

View File

@ -40,7 +40,7 @@ public:
FreeTypeFontCache(FontSize fs, FT_Face face, int pixels);
~FreeTypeFontCache();
void ClearFontCache() override;
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override;
GlyphID MapCharToGlyph(char32_t key) override;
std::string GetFontName() override { return fmt::format("{}, {}", face->family_name, face->style_name); }
bool IsBuiltInFont() override { return false; }
const void *GetOSHandle() override { return &face; }
@ -104,6 +104,7 @@ void FreeTypeFontCache::SetFontSize(int pixels)
this->ascender = this->face->size->metrics.ascender >> 6;
this->descender = this->face->size->metrics.descender >> 6;
this->height = this->ascender - this->descender;
FontCache::UpdateCharacterHeight(this->fs);
} else {
/* Both FT_Set_Pixel_Sizes and FT_Select_Size failed. */
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));
FT_UInt glyph = FT_Get_Char_Index(this->face, key);
if (glyph == 0 && allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
return this->parent->MapCharToGlyph(key);
}
return glyph;
return FT_Get_Char_Index(this->face, key);
}
FT_Library _ft_library = nullptr;
@ -225,15 +220,10 @@ public:
* format is 'font family name' or 'font family name, font style'.
* @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;
FontCacheSubSetting *settings = GetFontCacheSubSetting(fs);
std::string font = GetFontCacheFontName(fs);
if (font.empty()) return;
if (_ft_library == nullptr) {
if (FT_Init_FreeType(&_ft_library) != FT_Err_Ok) {
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. */
int32_t index = 0;
if (settings->os_handle != nullptr) index = *static_cast<const int32_t *>(settings->os_handle);
if (os_handle.size() == sizeof(index)) {
index = *reinterpret_cast<const int32_t *>(os_handle.data());
}
FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face);
if (error != FT_Err_Ok) {
@ -259,24 +251,24 @@ public:
}
#ifdef WITH_FONTCONFIG
/* Try loading based on font face name (OS-wide fonts). */
if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face);
/* If allowed to search, try loading based on font face name (OS-wide fonts). */
if (error != FT_Err_Ok && search) error = GetFontByFaceName(font, &face);
#endif /* WITH_FONTCONFIG */
if (error == FT_Err_Ok) {
error = LoadFont(fs, face, font, GetFontCacheFontSize(fs));
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 {
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
if (FontConfigSetFallbackFont(settings, language_isocode, callback)) return true;
if (FontConfigSetFallbackFont(language_isocode, bad_mask, callback)) return true;
#endif /* WITH_FONTCONFIG */
return false;

View File

@ -38,8 +38,15 @@ static int ScaleFontTrad(int value)
*/
SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs)
{
this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs));
this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2;
this->UpdateMetrics();
}
void SpriteFontCache::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()
{
Layouter::ResetFontCache(this->fs);
this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs));
this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2;
this->UpdateMetrics();
}
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('?');
return GetSprite(sprite, SpriteType::Font);
}
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('?');
return SpriteExists(sprite) ? GetSprite(sprite, SpriteType::Font)->width + ScaleFontTrad(this->fs != FS_NORMAL ? 1 : 0) : 0;
}
GlyphID SpriteFontCache::MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback)
GlyphID SpriteFontCache::MapCharToGlyph(char32_t key)
{
assert(IsPrintable(key));
SpriteID sprite = this->GetSpriteIDForChar(key);
if (sprite == 0) return 0;
return SPRITE_GLYPH | key;
return static_cast<GlyphID>(key);
}
bool SpriteFontCache::GetDrawGlyphShadow()
@ -95,14 +101,14 @@ class SpriteFontCacheFactory : public FontCacheFactory {
public:
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;
new SpriteFontCache(fs);
}
bool SetFallbackFont(struct FontCacheSettings *, const std::string &, class MissingGlyphSearcher *) override
bool SetFallbackFont(const std::string &, FontSizes, class MissingGlyphSearcher *) override
{
return false;
}

View File

@ -21,11 +21,12 @@ public:
const Sprite *GetGlyph(GlyphID key) override;
uint GetGlyphWidth(GlyphID key) override;
bool GetDrawGlyphShadow() override;
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override;
GlyphID MapCharToGlyph(char32_t key) override;
std::string GetFontName() override { return "sprite"; }
bool IsBuiltInFont() override { return true; }
private:
void UpdateMetrics();
SpriteID GetSpriteIDForChar(char32_t key);
};

View File

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

View File

@ -481,6 +481,12 @@ static void SetColourRemap(TextColour colour)
_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.
* @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;
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;
int height = GetCharacterHeight(fc->GetSize());
auto render = RenderGlyph;
DrawPixelInfo *dpi = _cur_dpi;
int dpi_left = dpi->left;
@ -612,14 +621,16 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
/* Truncated away. */
if (truncation && (begin_x < min_x || end_x > max_x)) continue;
/* Outside the clipping area. */
if (begin_x > dpi_right || end_x < dpi_left) continue;
const Sprite *sprite = fc->GetGlyph(glyph);
/* Check clipping (the "+ 1" is for the shadow). */
if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue;
if (do_shadow) {
begin_x += shadow_offset;
end_x += shadow_offset;
top += shadow_offset;
}
if (do_shadow && (glyph & SPRITE_GLYPH) != 0) continue;
GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap);
render(fc, glyph, begin_x, end_x, top, top + height - 1);
}
}

View File

@ -38,18 +38,17 @@
std::unique_ptr<Layouter::LineCache> Layouter::linecache;
/** Cache of Font instances. */
Layouter::FontColourMap Layouter::fonts[FS_END];
std::unordered_map<FontIndex, Layouter::FontColourMap> Layouter::fonts;
/**
* 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.
*/
Font::Font(FontSize size, TextColour colour) :
fc(FontCache::Get(size)), colour(colour)
Font::Font(FontIndex font_index, TextColour 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;
typename T::CharType *buff = buff_begin;
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();
@ -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
* usable by ParagraphLayout.
*/
for (char32_t c : Utf8View(str)) {
Utf8View view(str);
for (auto it = view.begin(); it != view.end(); /* nothing */) {
auto cur = it;
uint32_t c = *it++;
if (c == '\0' || c == '\n') {
/* Caller should already have filtered out these characters. */
NOT_REACHED();
@ -95,19 +97,40 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s
} else {
/* Filter out non printable characters */
if (!IsPrintable(c)) continue;
if (IsTextDirectionChar(c)) {
/* Filter out text direction characters that shouldn't be drawn, and
* will not be handled in the fallback case because they are mostly
* needed for RTL languages which need more proper shaping support. */
if (!T::SUPPORTS_RTL && IsTextDirectionChar(c)) continue;
if constexpr (!T::SUPPORTS_RTL) continue;
buff += T::AppendToBuffer(buff, buffer_last, c);
if (buff >= buffer_last) break;
continue;
}
if (font_mapping.empty() || font_mapping.back().first != buff - buff_begin) {
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 (buff - buff_begin > 0 && (font_mapping.empty() || font_mapping.back().first != buff - buff_begin)) {
font_mapping.emplace_back(buff - buff_begin, f);
}
f = Layouter::GetFont(state.fontsize, state.cur_colour);
f = Layouter::GetFont(state.font_index, state.cur_colour);
}
/* 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) {
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.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)
{
FontState state(TC_INVALID, fontsize);
FontState state(TC_INVALID, fontsize, FontCache::GetDefaultFontIndex(fontsize));
while (true) {
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.
*/
Font *Layouter::GetFont(FontSize size, TextColour colour)
Font *Layouter::GetFont(FontIndex font_index, TextColour colour)
{
FontColourMap::iterator it = fonts[size].find(colour);
if (it != fonts[size].end()) return it->second.get();
if (font_index == INVALID_FONT_INDEX) return nullptr;
assert(font_index < FontCache::Get().size());
fonts[size][colour] = std::make_unique<Font>(size, colour);
return fonts[size][colour].get();
FontColourMap &fcm = Layouter::fonts[font_index];
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.
* @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 */
ResetLineCache();

View File

@ -23,11 +23,12 @@
*/
struct FontState {
FontSize fontsize; ///< Current font size.
FontIndex font_index; ///< Current font index.
TextColour cur_colour; ///< Current text colour.
std::vector<TextColour> colour_stack; ///< Stack of colours to assist with colour switching.
FontState() : fontsize(FS_END), cur_colour(TC_INVALID) {}
FontState(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour) {}
FontState() : fontsize(FS_END), font_index(INVALID_FONT_INDEX), cur_colour(TC_INVALID) {}
FontState(TextColour colour, FontSize fontsize, FontIndex font_index) : fontsize(fontsize), font_index(font_index), cur_colour(colour) {}
auto operator<=>(const FontState &) const = default;
@ -67,6 +68,7 @@ struct FontState {
inline void SetFontSize(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
{
size_t h1 = std::hash<FontSize>{}(state.fontsize);
size_t h2 = std::hash<TextColour>{}(state.cur_colour);
size_t h3 = std::hash<std::vector<TextColour>>{}(state.colour_stack);
return h1 ^ (h2 << 1) ^ (h3 << 2);
size_t h2 = std::hash<FontIndex>{}(state.font_index);
size_t h3 = std::hash<TextColour>{}(state.cur_colour);
size_t h4 = std::hash<std::vector<TextColour>>{}(state.colour_stack);
return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3);
}
};
@ -99,7 +102,7 @@ public:
FontCache *fc; ///< The font we are using.
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. */
@ -206,9 +209,9 @@ private:
static LineCacheItem &GetCachedParagraphLayout(std::string_view str, const FontState &state);
using FontColourMap = std::map<TextColour, std::unique_ptr<Font>>;
static FontColourMap fonts[FS_END];
static std::unordered_map<FontIndex, FontColourMap> fonts;
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);
Dimension GetBounds();
@ -216,7 +219,7 @@ public:
ptrdiff_t GetCharAtPosition(int x, size_t line_index) const;
static void Initialize();
static void ResetFontCache(FontSize size);
static void ResetFontCache(FontSize fs);
static void ResetLineCache();
};

View File

@ -51,7 +51,7 @@ public:
int GetGlyphCount() const override { return static_cast<int>(this->glyphs.size()); }
std::span<const GlyphID> GetGlyphs() const override { return this->glyphs; }
std::span<const Position> GetPositions() const override { return this->positions; }
int GetLeading() const override { return this->GetFont()->fc->GetHeight(); }
int GetLeading() const override { return GetCharacterHeight(this->GetFont()->fc->GetSize()); }
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) :
font(font)
{
const bool isbuiltin = font->fc->IsBuiltInFont();
this->glyphs.reserve(char_count);
this->glyph_to_char.reserve(char_count);
this->positions.reserve(char_count);
FontCache &fc = *this->font->fc;
int y_offset = fc.GetGlyphYOffset();;
int advance = x;
for (int i = 0; i < char_count; i++) {
const GlyphID &glyph_id = this->glyphs.emplace_back(font->fc->MapCharToGlyph(chars[i]));
int x_advance = font->fc->GetGlyphWidth(glyph_id);
if (isbuiltin) {
this->positions.emplace_back(advance, advance + x_advance - 1, font->fc->GetAscender()); // Apply sprite font's ascender.
} else if (chars[i] >= SCC_SPRITE_START && chars[i] <= SCC_SPRITE_END) {
this->positions.emplace_back(advance, advance + x_advance - 1, (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2); // Align sprite font to centre
} else {
this->positions.emplace_back(advance, advance + x_advance - 1, 0); // No ascender adjustment.
}
const GlyphID &glyph_id = this->glyphs.emplace_back(fc.MapCharToGlyph(chars[i]));
int x_advance = fc.GetGlyphWidth(glyph_id);
this->positions.emplace_back(advance, advance + x_advance - 1, y_offset); // No ascender adjustment.
advance += x_advance;
this->glyph_to_char.push_back(char_offset + i);
}
@ -234,6 +228,7 @@ std::unique_ptr<const ParagraphLayouter::Line> FallbackParagraphLayout::NextLine
}
const FontCache *fc = iter->second->fc;
assert(fc != nullptr);
const char32_t *next_run = this->buffer_begin + iter->first;
const char32_t *begin = this->buffer;
@ -251,6 +246,7 @@ std::unique_ptr<const ParagraphLayouter::Line> FallbackParagraphLayout::NextLine
if (this->buffer == next_run) {
int w = l->GetWidth();
assert(iter->second->fc != nullptr);
l->emplace_back(iter->second, begin, this->buffer - begin, begin - this->buffer_begin, w);
++iter;
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);
};
/**
* 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 */

View File

@ -9,8 +9,10 @@
#include "stdafx.h"
#include "gfx_layout_icu.h"
#include "gfx_layout_fallback.h"
#include "debug.h"
#include "string_func.h"
#include "strings_func.h"
#include "language.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) {}
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; }
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 GetAdvance() const { return this->total_advance; }
};
@ -135,12 +138,52 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) :
assert(!run.positions.empty());
this->positions.reserve(run.positions.size());
int y_offset = this->font->fc->GetGlyphYOffset();
/* Copy positions, moving x coordinate by x offset. */
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.
*
@ -149,6 +192,17 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) :
*/
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())));
/* Match the flags with how we render the glyphs. */
hb_ft_font_set_load_flags(hbfont, GetFontAAState() ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO);
@ -170,12 +224,6 @@ void ICURun::Shape(UChar *buff, size_t buff_length)
auto glyph_info = hb_buffer_get_glyph_infos(hbbuf, &glyph_count);
auto glyph_pos = hb_buffer_get_glyph_positions(hbbuf, &glyph_count);
/* Make sure any former run is lost. */
this->glyphs.clear();
this->glyph_to_char.clear();
this->positions.clear();
this->advance.clear();
/* Reserve space, as we already know the size. */
this->glyphs.reserve(glyph_count);
this->glyph_to_char.reserve(glyph_count);
@ -183,20 +231,12 @@ void ICURun::Shape(UChar *buff, size_t buff_length)
this->advance.reserve(glyph_count);
/* Prepare the glyphs/position. ICUVisualRun will give the position an offset if needed. */
int y_offset = (GetCharacterHeight(this->font->fc->GetSize()) - this->font->fc->GetHeight()) / 2;
hb_position_t advance = 0;
for (unsigned int i = 0; i < glyph_count; i++) {
int x_advance;
if (buff[glyph_info[i].cluster] >= SCC_SPRITE_START && buff[glyph_info[i].cluster] <= SCC_SPRITE_END && glyph_info[i].codepoint == 0) {
auto glyph = this->font->fc->MapCharToGlyph(buff[glyph_info[i].cluster]);
x_advance = this->font->fc->GetGlyphWidth(glyph);
this->glyphs.push_back(glyph);
this->positions.emplace_back(advance, advance + x_advance - 1, (this->font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(this->font->fc->GetSize()))) / 2); // Align sprite font to centre
} else {
x_advance = glyph_pos[i].x_advance / FONT_SCALE;
int x_advance = glyph_pos[i].x_advance / FONT_SCALE;
this->glyphs.push_back(glyph_info[i].codepoint);
this->positions.emplace_back(glyph_pos[i].x_offset / FONT_SCALE + advance, glyph_pos[i].x_offset / FONT_SCALE + advance + x_advance - 1, glyph_pos[i].y_offset / FONT_SCALE);
}
this->positions.emplace_back(glyph_pos[i].x_offset / FONT_SCALE + advance, glyph_pos[i].x_offset / FONT_SCALE + advance + x_advance - 1, glyph_pos[i].y_offset / FONT_SCALE + y_offset);
this->glyph_to_char.push_back(glyph_info[i].cluster);
this->advance.push_back(x_advance);
@ -359,11 +399,6 @@ std::vector<ICURun> ItemizeStyle(std::vector<ICURun> &runs_current, FontMap &fon
/* Can't layout an empty string. */
if (length == 0) return nullptr;
/* Can't layout our in-built sprite fonts. */
for (auto const &[position, font] : font_mapping) {
if (font->fc->IsBuiltInFont()) return nullptr;
}
auto runs = ItemizeBidi(buff, length);
runs = ItemizeScript(buff, length, runs);
runs = ItemizeStyle(runs, font_mapping);

View File

@ -26,6 +26,7 @@
CoreTextFontCache::CoreTextFontCache(FontSize fs, CFAutoRelease<CTFontDescriptorRef> &&font, int pixels) : TrueTypeFontCache(fs, pixels), font_desc(std::move(font))
{
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);
}
GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key)
{
assert(IsPrintable(key));
@ -112,10 +113,6 @@ GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
return glyph[0];
}
if (allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
return this->parent->MapCharToGlyph(key);
}
return 0;
}
@ -211,7 +208,7 @@ public:
* fallback search, use it. Otherwise, try to resolve it by font name.
* @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;
@ -259,7 +256,7 @@ public:
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
* 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). */
if (symbolic_traits & kCTFontBoldTrait) continue;
/* Select monospaced fonts if asked for. */
if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue;
if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != fontsizes.Test(FS_MONO)) continue;
/* Get font name. */
char buffer[128];
@ -323,7 +320,13 @@ public:
if (name.starts_with(".") || name.starts_with("LastResort")) continue;
/* 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()) {
Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name);
result = true;
@ -335,7 +338,9 @@ public:
if (!result) {
/* For some OS versions, the font 'Arial Unicode MS' does not report all languages it
* supports. If we didn't find any other font, just try it, maybe we get lucky. */
callback->SetFontNames(settings, "Arial Unicode MS");
for (FontSize fs : bad_mask) {
GetFontCacheSubSetting(fs)->AddFallback("Arial Unicode MS", std::nullopt);
}
result = !callback->FindMissingGlyphs();
}

View File

@ -28,7 +28,7 @@ public:
~CoreTextFontCache() {}
void ClearFontCache() override;
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override;
GlyphID MapCharToGlyph(char32_t key) override;
std::string GetFontName() override { return font_name; }
bool IsBuiltInFont() override { return false; }
const void *GetOSHandle() override { return font.get(); }

View File

@ -52,7 +52,7 @@ extern "C" {
/** Cached current locale. */
static CFAutoRelease<CFLocaleRef> _osx_locale;
/** CoreText cache for font information, cleared when OTTD changes fonts. */
static CFAutoRelease<CTFontRef> _font_cache[FS_END];
static std::unordered_map<FontIndex, CFAutoRelease<CTFontRef>> _font_cache;
/**
@ -88,7 +88,7 @@ public:
std::span<const int> GetGlyphToCharMap() const override { return this->glyph_to_char; }
const Font *GetFont() const override { return this->font; }
int GetLeading() const override { return this->font->fc->GetHeight(); }
int GetLeading() const override { return GetCharacterHeight(this->font->fc->GetSize()); }
int GetGlyphCount() const override { return (int)this->glyphs.size(); }
int GetAdvance() const { return this->total_advance; }
};
@ -137,17 +137,17 @@ public:
/** Get the width of an encoded sprite font character. */
static CGFloat SpriteFontGetWidth(void *ref_con)
static CGFloat CustomFontGetWidth(void *ref_con)
{
FontSize fs = (FontSize)((size_t)ref_con >> 24);
char32_t c = (char32_t)((size_t)ref_con & 0xFFFFFF);
FontIndex fi = static_cast<FontIndex>(reinterpret_cast<uintptr_t>(ref_con) >> 24);
char32_t c = static_cast<char32_t>(reinterpret_cast<uintptr_t>(ref_con) & 0xFFFFFF);
return GetGlyphWidth(fs, c);
return FontCache::Get(fi)->GetGlyphWidth(c);
}
static const CTRunDelegateCallbacks _sprite_font_callback = {
kCTRunDelegateCurrentVersion, nullptr, nullptr, nullptr,
&SpriteFontGetWidth
&CustomFontGetWidth
};
/* static */ std::unique_ptr<ParagraphLayouter> CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &font_mapping)
@ -158,11 +158,6 @@ static const CTRunDelegateCallbacks _sprite_font_callback = {
ptrdiff_t length = buff_end - buff;
if (length == 0) return nullptr;
/* Can't layout our in-built sprite fonts. */
for (const auto &[position, font] : font_mapping) {
if (font->fc->IsBuiltInFont()) return nullptr;
}
/* Make attributed string with embedded font information. */
CFAutoRelease<CFMutableAttributedStringRef> str(CFAttributedStringCreateMutable(kCFAllocatorDefault, 0));
CFAttributedStringBeginEditing(str.get());
@ -181,12 +176,12 @@ static const CTRunDelegateCallbacks _sprite_font_callback = {
CTFontRef font_handle = static_cast<CTFontRef>(font->fc->GetOSHandle());
if (font_handle == nullptr) {
if (!_font_cache[font->fc->GetSize()]) {
if (!_font_cache[font->fc->GetIndex()]) {
/* Cache font information. */
CFAutoRelease<CFStringRef> font_name(CFStringCreateWithCString(kCFAllocatorDefault, font->fc->GetFontName().c_str(), kCFStringEncodingUTF8));
_font_cache[font->fc->GetSize()].reset(CTFontCreateWithName(font_name.get(), font->fc->GetFontSize(), nullptr));
_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);
@ -194,10 +189,10 @@ static const CTRunDelegateCallbacks _sprite_font_callback = {
CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, color);
CGColorRelease(color);
/* Install a size callback for our special private-use sprite glyphs in case the font does not provide them. */
for (ssize_t c = last; c < position; c++) {
if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END && font->fc->MapCharToGlyph(buff[c], false) == 0) {
CFAutoRelease<CTRunDelegateRef> del(CTRunDelegateCreate(&_sprite_font_callback, (void *)(size_t)(buff[c] | (font->fc->GetSize() << 24))));
/* Install a size callback for our custom fonts. */
if (font->fc->IsBuiltInFont()) {
for (ssize_t c = last; c < i.first; c++) {
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. */
CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacment_str.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);
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
* the proper glyphs for our private sprite glyphs. */
CGGlyph gl[this->glyphs.size()];
CTRunGetGlyphs(run, CFRangeMake(0, 0), gl);
for (size_t i = 0; i < this->glyphs.size(); i++) {
if (buff[this->glyph_to_char[i]] >= SCC_SPRITE_START && buff[this->glyph_to_char[i]] <= SCC_SPRITE_END && (gl[i] == 0 || gl[i] == 3)) {
/* A glyph of 0 indidicates not found, while apparently 3 is what char 0xFFFC maps to. */
this->glyphs[i] = font->fc->MapCharToGlyph(buff[this->glyph_to_char[i]]);
this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2); // Align sprite font to centre
} else {
this->glyphs[i] = gl[i];
this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y);
}
this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y + y_offset);
}
this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nullptr, nullptr, nullptr));
}

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

View File

@ -19,7 +19,7 @@
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 */

View File

@ -31,8 +31,8 @@
#include "../../safeguards.h"
struct EFCParam {
FontCacheSettings *settings;
LOCALESIGNATURE locale;
FontSizes fontsizes;
MissingGlyphSearcher *callback;
std::vector<std::wstring> fonts;
@ -59,7 +59,7 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
/* Don't use SYMBOL fonts */
if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1;
/* Use monospaced fonts when asked for it. */
if (info->callback->Monospace() && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1;
if (info->fontsizes.Test(FS_MONO) && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1;
/* The font has to have at least one of the supported locales to be usable. */
auto check_bitfields = [&]() {
@ -78,8 +78,15 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
char font_name[MAX_PATH];
convert_from_fs(logfont->elfFullName, font_name);
info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont);
if (info->callback->FindMissingGlyphs()) return 1;
Debug(misc, 0, "Trying font {}", font_name);
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);
return 0; // stop enumerating
}
@ -156,10 +163,11 @@ void Win32FontCache::SetFontSize(int pixels)
this->height = this->ascender + this->descender;
this->glyph_size.cx = otm->otmTextMetrics.tmMaxCharWidth;
this->glyph_size.cy = otm->otmTextMetrics.tmHeight;
FontCache::UpdateCharacterHeight(this->fs);
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;
}
@ -246,7 +254,7 @@ void Win32FontCache::ClearFontCache()
return this->SetGlyphPtr(key, std::move(new_glyph)).GetSprite();
}
/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(char32_t key)
{
assert(IsPrintable(key));
@ -263,7 +271,7 @@ void Win32FontCache::ClearFontCache()
GetGlyphIndicesW(this->dc, chars, key >= 0x010000U ? 2 : 1, glyphs, GGI_MARK_NONEXISTING_GLYPHS);
if (glyphs[0] != 0xFFFF) return glyphs[0];
return allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END ? this->parent->MapCharToGlyph(key) : 0;
return 0;
}
class Win32FontCacheFactory : FontCacheFactory {
@ -276,27 +284,23 @@ public:
* fallback search, use it. Otherwise, try to resolve it by font name.
* @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;
FontCacheSubSetting *settings = GetFontCacheSubSetting(fs);
std::string font = GetFontCacheFontName(fs);
if (font.empty()) return;
LOGFONT logfont{};
logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH;
logfont.lfCharSet = DEFAULT_CHARSET;
logfont.lfOutPrecision = OUT_OUTLINE_PRECIS;
logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
if (settings->os_handle != nullptr) {
logfont = *(const LOGFONT *)settings->os_handle;
if (!os_handle.empty()) {
logfont = *reinterpret_cast<const LOGFONT *>(os_handle.data());
} else if (font.find('.') != std::string::npos) {
/* Might be a font file name, try load it. */
if (!TryLoadFontFromFile(font, logfont)) {
ShowInfo("Unable to load file '{}' for {} font, using default windows font selection instead", font, FontSizeToName(fs));
if (!search) return;
}
}
@ -308,7 +312,7 @@ public:
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");
EFCParam langInfo;
@ -318,7 +322,7 @@ public:
Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode);
return false;
}
langInfo.settings = settings;
langInfo.fontsizes = bad_mask;
langInfo.callback = callback;
LOGFONT font;

View File

@ -37,11 +37,11 @@ public:
Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels);
~Win32FontCache();
void ClearFontCache() override;
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override;
GlyphID MapCharToGlyph(char32_t key) override;
std::string GetFontName() override { return this->fontname; }
const void *GetOSHandle() override { return &this->logfont; }
};
void LoadWin32Font(FontSize fs);
void LoadWin32Font(FontSize fs, bool search, const std::string &font_name, std::span<const std::byte> os_handle);
#endif /* FONT_WIN32_H */

View File

@ -10,6 +10,7 @@
#include "../../stdafx.h"
#include "../../debug.h"
#include "string_uniscribe.h"
#include "../../gfx_layout_fallback.h"
#include "../../language.h"
#include "../../strings_func.h"
#include "../../string_func.h"
@ -29,7 +30,7 @@
/** Uniscribe cache for internal font information, cleared when OTTD changes fonts. */
static SCRIPT_CACHE _script_cache[FS_END];
static std::map<FontIndex, SCRIPT_CACHE> _script_cache;
/**
* Contains all information about a run of characters. A run are consecutive
@ -52,6 +53,8 @@ struct UniscribeRun {
int total_advance;
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. */
@ -94,7 +97,7 @@ public:
std::span<const int> GetGlyphToCharMap() const override;
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 GetAdvance() const { return this->total_advance; }
};
@ -130,12 +133,12 @@ public:
std::unique_ptr<const Line> NextLine(int max_width) override;
};
void UniscribeResetScriptCache(FontSize size)
void UniscribeResetScriptCache(FontSize)
{
if (_script_cache[size] != nullptr) {
ScriptFreeCache(&_script_cache[size]);
_script_cache[size] = nullptr;
for (auto &sc : _script_cache) {
ScriptFreeCache(&sc.second);
}
_script_cache.clear();
}
/** Load the matching native Windows font. */
@ -152,6 +155,41 @@ static HFONT HFontFromFont(Font *font)
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. */
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 cur_font = nullptr;
if (range.font->fc->IsBuiltInFont()) {
range.FallbackShape(buff);
return true;
}
while (true) {
/* Shape the text run by determining the glyphs needed for display. */
int glyphs_used = 0;
HRESULT hr = ScriptShape(temp_dc, &_script_cache[range.font->fc->GetSize()], buff + range.pos, range.len, (int)range.glyphs.size(), &range.sa, &range.glyphs[0], &range.char_to_glyph[0], &range.vis_attribs[0], &glyphs_used);
HRESULT hr = ScriptShape(temp_dc, &_script_cache[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)) {
range.glyphs.resize(glyphs_used);
@ -179,7 +222,7 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b
ABC abc;
range.advances.resize(range.glyphs.size());
range.offsets.resize(range.glyphs.size());
hr = ScriptPlace(temp_dc, &_script_cache[range.font->fc->GetSize()], &range.glyphs[0], (int)range.glyphs.size(), &range.vis_attribs[0], &range.sa, &range.advances[0], &range.offsets[0], &abc);
hr = ScriptPlace(temp_dc, &_script_cache[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)) {
/* We map our special sprite chars to values that don't fit into a WORD. Copy the glyphs
* into a new vector and query the real glyph to use for these special chars. */
@ -187,22 +230,12 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b
for (size_t g_id = 0; g_id < range.glyphs.size(); g_id++) {
range.ft_glyphs[g_id] = range.glyphs[g_id];
}
for (int i = 0; i < range.len; i++) {
if (buff[range.pos + i] >= SCC_SPRITE_START && buff[range.pos + i] <= SCC_SPRITE_END) {
auto pos = range.char_to_glyph[i];
if (range.ft_glyphs[pos] == 0) { // Font doesn't have our special glyph, so remap.
range.ft_glyphs[pos] = range.font->fc->MapCharToGlyph(buff[range.pos + i]);
range.offsets[pos].dv = (range.font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(range.font->fc->GetSize()))) / 2; // Align sprite font to centre
range.advances[pos] = range.font->fc->GetGlyphWidth(range.ft_glyphs[pos]);
}
}
}
range.total_advance = 0;
for (size_t i = 0; i < range.advances.size(); i++) {
#ifdef WITH_FREETYPE
/* FreeType and GDI/Uniscribe seems to occasionally disagree over the width of a glyph. */
if (range.advances[i] > 0 && range.ft_glyphs[i] != 0xFFFF) range.advances[i] = range.font->fc->GetGlyphWidth(range.ft_glyphs[i]);
if (range.advances[i] > 0 && range.glyphs[i] != 0xFFFF) range.advances[i] = range.font->fc->GetGlyphWidth(range.glyphs[i]);
#endif
range.total_advance += range.advances[i];
}
@ -280,11 +313,6 @@ static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutF
/* Can't layout an empty string. */
if (length == 0) return nullptr;
/* Can't layout our in-built sprite fonts. */
for (auto const &[position, font] : font_mapping) {
if (font->fc->IsBuiltInFont()) return nullptr;
}
/* Itemize text. */
std::vector<SCRIPT_ITEM> items = UniscribeItemizeString(buff, length);
if (items.empty()) return nullptr;

View File

@ -697,7 +697,7 @@ public:
* break point, but we only want word starts. Move to the next location in
* case the new position points to whitespace. */
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();
/* 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. */
@ -729,7 +729,7 @@ public:
* break point, but we only want word starts. Move to the previous location in
* case the new position points to whitespace. */
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();
/* 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. */

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.
* @return Decoded Unicode character.
*/
inline char32_t Utf16DecodeChar(const uint16_t *c)
inline char32_t Utf16DecodeChar(const char16_t *c)
{
if (Utf16IsLeadSurrogate(c[0])) {
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.
* @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();
for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
FontSize size = this->DefaultSize();
FontCache *fc = FontCache::Get(size);
for (char32_t c : Utf8View(*text)) {
if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
size = (FontSize)(c - SCC_FIRST_FONT);
fc = FontCache::Get(size);
} else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) {
/* The character is printable, but not in the normal font. This is the case we were testing for. */
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();
continue;
}
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. */
class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
class LanguagePackGlyphSearcher : public BaseStringMissingGlyphSearcher {
public:
LanguagePackGlyphSearcher() : BaseStringMissingGlyphSearcher({FS_NORMAL, FS_SMALL, FS_LARGE}) {}
private:
uint i; ///< Iterator for the primary language tables.
uint j; ///< Iterator for the secondary language tables.
@ -2316,26 +2334,11 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
return ret;
}
bool Monospace() override
{
return false;
}
void SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data) override
{
#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
settings->small.font = font_name;
settings->medium.font = font_name;
settings->large.font = font_name;
settings->small.os_handle = os_data;
settings->medium.os_handle = os_data;
settings->large.os_handle = os_data;
#endif
}
};
static LanguagePackGlyphSearcher _language_pack_searcher;
/**
* Check whether the currently loaded language pack
* uses characters that the currently loaded font
@ -2351,22 +2354,17 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
*/
void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
{
static LanguagePackGlyphSearcher pack_searcher;
if (searcher == nullptr) searcher = &pack_searcher;
bool bad_font = !base_font || searcher->FindMissingGlyphs();
if (searcher == nullptr) searcher = &_language_pack_searcher;
FontSizes bad_mask = searcher->FindMissingGlyphs();
bool bad_font = bad_mask.Any();
#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
* a fallback font that can print the characters in the current language. */
bool any_font_configured = !_fcsettings.medium.font.empty();
FontCacheSettings backup = _fcsettings;
_fcsettings.mono.os_handle = nullptr;
_fcsettings.medium.os_handle = nullptr;
bad_font = !FontProviderManager::SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher);
_fcsettings = std::move(backup);
bad_font = !FontProviderManager::SetFallbackFont(_langpack.langpack->isocode, bad_mask, searcher);
if (!bad_font && any_font_configured) {
/* 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
* user chosen font as that is more likely to be any good than
* the wild guess we made */
InitFontCache(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
InitFontCache(bad_mask);
}
}
#endif
@ -2400,12 +2398,12 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING);
/* Reset the font width */
LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED);
LoadStringWidthTable(bad_mask);
return;
}
/* 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)
/*

View File

@ -157,9 +157,31 @@ EncodedString GetEncodedString(StringID string, const Args&... args)
*/
class MissingGlyphSearcher {
public:
FontSizes font_sizes; ///< Font sizes to search for.
MissingGlyphSearcher(FontSizes font_sizes) : font_sizes(font_sizes) {}
/** Make sure everything gets destructed right. */
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.
* @return The next string or nullopt if there is none.
@ -177,23 +199,9 @@ public:
*/
virtual void Reset() = 0;
/**
* 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();
std::set<char32_t> GetRequiredGlyphs(FontSizes fontsizes) override;
};
void CheckForMissingGlyphs(bool base_font = true, MissingGlyphSearcher *search = nullptr);
void CheckForMissingGlyphs(bool base_font = true, MissingGlyphSearcher *searcher = nullptr);
#endif /* STRINGS_FUNC_H */

View File

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

View File

@ -19,20 +19,22 @@ public:
MockFontCache(FontSize fs) : FontCache(fs)
{
this->height = FontCache::GetDefaultFontHeight(this->fs);
FontCache::UpdateCharacterHeight(this->fs);
}
void ClearFontCache() override {}
const Sprite *GetGlyph(GlyphID) override { return nullptr; }
uint GetGlyphWidth(GlyphID) override { return this->height / 2; }
bool GetDrawGlyphShadow() override { return false; }
GlyphID MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback = true) override { return key; }
GlyphID MapCharToGlyph(char32_t key) override { return key; }
std::string GetFontName() override { return "mock"; }
bool IsBuiltInFont() override { return true; }
static void InitializeFontCaches()
{
FontCache::caches.clear();
for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) {
if (FontCache::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
);
TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), file_type(file_type)
TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), BaseStringMissingGlyphSearcher(FS_MONO), file_type(file_type)
{
/* Init of nested tree is deferred.
* TextfileWindow::ConstructWindow must be called by the inheriting window. */
@ -754,19 +754,6 @@ bool TextfileWindow::IsTextWrapped() const
return this->lines[this->search_iterator++].text;
}
/* virtual */ bool TextfileWindow::Monospace()
{
return true;
}
/* virtual */ void TextfileWindow::SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data)
{
#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
settings->mono.font = font_name;
settings->mono.os_handle = os_data;
#endif
}
#if defined(WITH_ZLIB)
/**

View File

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