Files
OpenTTD/src/fontcache/freetypefontcache.cpp
T
2026-04-12 18:10:34 +02:00

317 lines
11 KiB
C++

/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
*/
/** @file freetypefontcache.cpp FreeType font cache implementation. */
#ifdef WITH_FREETYPE
#include "../stdafx.h"
#include "../debug.h"
#include "../fontcache.h"
#include "../blitter/factory.hpp"
#include "../zoom_func.h"
#include "../fileio_func.h"
#include "../error_func.h"
#include "../../os/unix/font_unix.h"
#include "truetypefontcache.h"
#include "../table/control_codes.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_TRUETYPE_TABLES_H
#include "../safeguards.h"
/** Font cache for fonts that are based on a freetype font. */
class FreeTypeFontCache : public TrueTypeFontCache {
private:
FT_Face face; ///< The font face associated with this font.
void SetFontSize(int pixels);
const Sprite *InternalGetGlyph(GlyphID key, bool aa) override;
public:
FreeTypeFontCache(FontSize fs, FT_Face face, int pixels);
~FreeTypeFontCache() override;
void ClearFontCache() override;
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) 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; }
};
/**
* Create a new FreeTypeFontCache.
* @param fs The font size that is going to be cached.
* @param face The font that has to be loaded.
* @param pixels The number of pixels this font should be high.
*/
FreeTypeFontCache::FreeTypeFontCache(FontSize fs, FT_Face face, int pixels) : TrueTypeFontCache(fs, pixels), face(face)
{
assert(face != nullptr);
this->SetFontSize(pixels);
}
void FreeTypeFontCache::SetFontSize(int pixels)
{
if (pixels == 0) {
/* Try to determine a good height based on the minimal height recommended by the font. */
int scaled_height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs));
pixels = scaled_height;
TT_Header *head = (TT_Header *)FT_Get_Sfnt_Table(this->face, ft_sfnt_head);
if (head != nullptr) {
/* Font height is minimum height plus the difference between the default
* height for this font size and the small size. */
int diff = scaled_height - ScaleGUITrad(FontCache::GetDefaultFontHeight(FontSize::Small));
/* Clamp() is not used as scaled_height could be greater than MAX_FONT_SIZE, which is not permitted in Clamp(). */
pixels = std::min(std::max(std::min<int>(head->Lowest_Rec_PPEM, MAX_FONT_MIN_REC_SIZE) + diff, scaled_height), MAX_FONT_SIZE);
}
} else {
pixels = ScaleGUITrad(pixels);
}
this->used_size = pixels;
FT_Error err = FT_Set_Pixel_Sizes(this->face, 0, pixels);
if (err != FT_Err_Ok) {
/* Find nearest size to that requested */
FT_Bitmap_Size *bs = this->face->available_sizes;
int i = this->face->num_fixed_sizes;
if (i > 0) { // In pathetic cases one might get no fixed sizes at all.
int n = bs->height;
FT_Int chosen = 0;
for (; --i; bs++) {
if (abs(pixels - bs->height) >= abs(pixels - n)) continue;
n = bs->height;
chosen = this->face->num_fixed_sizes - i;
}
/* Don't use FT_Set_Pixel_Sizes here - it might give us another
* error, even though the size is available (FS#5885). */
err = FT_Select_Size(this->face, chosen);
}
}
if (err == FT_Err_Ok) {
this->ascender = this->face->size->metrics.ascender >> 6;
this->descender = this->face->size->metrics.descender >> 6;
this->height = this->ascender - this->descender;
} else {
/* Both FT_Set_Pixel_Sizes and FT_Select_Size failed. */
Debug(fontcache, 0, "Font size selection failed. Using FontCache defaults.");
}
}
/**
* Free everything that was allocated for this font cache.
*/
FreeTypeFontCache::~FreeTypeFontCache()
{
FT_Done_Face(this->face);
this->face = nullptr;
this->ClearFontCache();
}
/**
* Reset cached glyphs.
*/
void FreeTypeFontCache::ClearFontCache()
{
/* Font scaling might have changed, determine font size anew if it was automatically selected. */
if (this->face != nullptr) this->SetFontSize(this->req_size);
this->TrueTypeFontCache::ClearFontCache();
}
const Sprite *FreeTypeFontCache::InternalGetGlyph(GlyphID key, bool aa)
{
FT_GlyphSlot slot = this->face->glyph;
FT_Load_Glyph(this->face, key, aa ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO);
FT_Render_Glyph(this->face->glyph, aa ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
/* Despite requesting a normal glyph, FreeType may have returned a bitmap */
aa = (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY);
/* Add 1 scaled pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel */
uint shadow = (this->fs == FontSize::Normal) ? ScaleGUITrad(1) : 0;
uint width = std::max(1U, (uint)slot->bitmap.width + shadow);
uint height = std::max(1U, (uint)slot->bitmap.rows + shadow);
/* Limit glyph size to prevent overflows later on. */
if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) UserError("Font glyph is too large");
/* FreeType has rendered the glyph, now we allocate a sprite and copy the image into it */
SpriteLoader::SpriteCollection spritecollection;
SpriteLoader::Sprite &sprite = spritecollection[ZoomLevel::Min];
sprite.AllocateData(ZoomLevel::Min, static_cast<size_t>(width) * height);
sprite.colours = SpriteComponent::Palette;
if (aa) sprite.colours.Set(SpriteComponent::Alpha);
sprite.width = width;
sprite.height = height;
sprite.x_offs = slot->bitmap_left;
sprite.y_offs = this->ascender - slot->bitmap_top;
/* Draw shadow for medium size */
if (this->fs == FontSize::Normal && !aa) {
for (uint y = 0; y < (uint)slot->bitmap.rows; y++) {
for (uint x = 0; x < (uint)slot->bitmap.width; x++) {
if (HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) {
sprite.data[shadow + x + (shadow + y) * sprite.width].m = SHADOW_COLOUR;
sprite.data[shadow + x + (shadow + y) * sprite.width].a = 0xFF;
}
}
}
}
for (uint y = 0; y < (uint)slot->bitmap.rows; y++) {
for (uint x = 0; x < (uint)slot->bitmap.width; x++) {
if (aa ? (slot->bitmap.buffer[x + y * slot->bitmap.pitch] > 0) : HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) {
sprite.data[x + y * sprite.width].m = FACE_COLOUR;
sprite.data[x + y * sprite.width].a = aa ? slot->bitmap.buffer[x + y * slot->bitmap.pitch] : 0xFF;
}
}
}
UniquePtrSpriteAllocator allocator;
BlitterFactory::GetCurrentBlitter()->Encode(SpriteType::Font, spritecollection, allocator);
GlyphEntry new_glyph;
new_glyph.data = std::move(allocator.data);
new_glyph.width = slot->advance.x >> 6;
return this->SetGlyphPtr(key, std::move(new_glyph)).GetSprite();
}
GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
{
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;
}
FT_Library _ft_library = nullptr;
class FreeTypeFontCacheFactory : public FontCacheFactory {
public:
FreeTypeFontCacheFactory() : FontCacheFactory("freetype", "FreeType font provider") {}
/** Close the freetype library. */
~FreeTypeFontCacheFactory() override
{
FT_Done_FreeType(_ft_library);
_ft_library = nullptr;
}
std::unique_ptr<FontCache> LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, const std::any &os_handle) const override
{
if (fonttype != FontType::TrueType) return nullptr;
if (_ft_library == nullptr) {
if (FT_Init_FreeType(&_ft_library) != FT_Err_Ok) {
ShowInfo("Unable to initialize FreeType, using sprite fonts instead");
return nullptr;
}
Debug(fontcache, 2, "Initialized");
}
FT_Face face = nullptr;
/* If font is an absolute path to a ttf, try loading that first. */
int32_t index = 0;
if (auto ptr = std::any_cast<int32_t>(&os_handle)) {
index = *ptr;
}
FT_Error error = FT_New_Face(_ft_library, font_name.c_str(), index, &face);
if (error != FT_Err_Ok) {
/* Check if font is a relative filename in one of our search-paths. */
std::string full_font = FioFindFullPath(Subdirectory::Base, font_name);
if (!full_font.empty()) {
error = FT_New_Face(_ft_library, full_font.c_str(), 0, &face);
}
}
#ifdef WITH_FONTCONFIG
/* If allowed to search, try loading based on font face name (OS-wide fonts). */
if (error != FT_Err_Ok && search) error = GetFontByFaceName(font_name, &face);
#endif /* WITH_FONTCONFIG */
if (error != FT_Err_Ok) {
FT_Done_Face(face);
return nullptr;
}
return LoadFont(fs, face, font_name, GetFontCacheFontSize(fs));
}
bool FindFallbackFont(const std::string &language_isocode, class MissingGlyphSearcher *callback) const override
{
#ifdef WITH_FONTCONFIG
if (FontConfigFindFallbackFont(language_isocode, callback)) return true;
#endif /* WITH_FONTCONFIG */
return false;
}
private:
static std::unique_ptr<FontCache> LoadFont(FontSize fs, FT_Face face, std::string_view font_name, uint size)
{
Debug(fontcache, 2, "Requested '{}', using '{} {}'", font_name, face->family_name, face->style_name);
/* Attempt to select the unicode character map */
FT_Error error = FT_Select_Charmap(face, ft_encoding_unicode);
if (error == FT_Err_Invalid_CharMap_Handle) {
/* Try to pick a different character map instead. We default to
* the first map, but platform_id 0 encoding_id 0 should also
* be unicode (strange system...) */
FT_CharMap found = face->charmaps[0];
for (int i = 0; i < face->num_charmaps; ++i) {
FT_CharMap charmap = face->charmaps[i];
if (charmap->platform_id == 0 && charmap->encoding_id == 0) {
found = charmap;
}
}
if (found != nullptr) {
error = FT_Set_Charmap(face, found);
}
}
if (error != FT_Err_Ok) {
FT_Done_Face(face);
ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), error);
return nullptr;
}
return std::make_unique<FreeTypeFontCache>(fs, face, size);
}
private:
static FreeTypeFontCacheFactory instance;
};
/* static */ FreeTypeFontCacheFactory FreeTypeFontCacheFactory::instance;
#endif /* WITH_FREETYPE */