/* * 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 . */ /** @file freetypefontcache.cpp FreeType font cache implementation. */ #include "../stdafx.h" #include "../debug.h" #include "../fontcache.h" #include "../fontdetection.h" #include "../blitter/factory.hpp" #include "../core/math_func.hpp" #include "../zoom_func.h" #include "../fileio_func.h" #include "truetypefontcache.h" #include "../table/control_codes.h" #include "../safeguards.h" #ifdef WITH_FREETYPE #include #include FT_FREETYPE_H #include FT_GLYPH_H #include FT_TRUETYPE_TABLES_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(FontSize fs, FT_Face face, int pixels); virtual const void *InternalGetFontTable(uint32 tag, size_t &length); virtual const Sprite *InternalGetGlyph(GlyphID key, bool aa); public: FreeTypeFontCache(FontSize fs, FT_Face face, int pixels); ~FreeTypeFontCache(); virtual void ClearFontCache(); virtual GlyphID MapCharToGlyph(WChar key); virtual const char *GetFontName() { return face->family_name; } virtual bool IsBuiltInFont() { return false; } }; FT_Library _library = nullptr; /** * 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(fs, face, pixels); } void FreeTypeFontCache::SetFontSize(FontSize fs, FT_Face face, int pixels) { if (pixels == 0) { /* Try to determine a good height based on the minimal height recommended by the font. */ int scaled_height = ScaleGUITrad(this->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(this->GetDefaultFontHeight(FS_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(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->units_per_em = this->face->units_per_EM; 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."); } } /** * Loads the freetype font. * First type to load the fontname as if it were a path. If that fails, * try to resolve the filename of the font using fontconfig, where the * format is 'font family name' or 'font family name, font style'. * @param fs The font size to load. */ void LoadFreeTypeFont(FontSize fs) { FontCacheSubSetting *settings = nullptr; switch (fs) { default: NOT_REACHED(); case FS_SMALL: settings = &_fcsettings.small; break; case FS_NORMAL: settings = &_fcsettings.medium; break; case FS_LARGE: settings = &_fcsettings.large; break; case FS_MONO: settings = &_fcsettings.mono; break; } if (settings->font.empty()) return; if (_library == nullptr) { if (FT_Init_FreeType(&_library) != FT_Err_Ok) { ShowInfoF("Unable to initialize FreeType, using sprite fonts instead"); return; } Debug(fontcache, 2, "Initialized"); } const char *font_name = settings->font.c_str(); FT_Face face = nullptr; /* If font is an absolute path to a ttf, try loading that first. */ FT_Error error = FT_New_Face(_library, font_name, 0, &face); #if defined(WITH_COCOA) extern void MacOSRegisterExternalFont(const char *file_path); if (error == FT_Err_Ok) MacOSRegisterExternalFont(font_name); #endif if (error != FT_Err_Ok) { /* Check if font is a relative filename in one of our search-paths. */ std::string full_font = FioFindFullPath(BASE_DIR, font_name); if (!full_font.empty()) { error = FT_New_Face(_library, full_font.c_str(), 0, &face); #if defined(WITH_COCOA) if (error == FT_Err_Ok) MacOSRegisterExternalFont(full_font.c_str()); #endif } } /* Try loading based on font face name (OS-wide fonts). */ if (error != FT_Err_Ok) error = GetFontByFaceName(font_name, &face); if (error == FT_Err_Ok) { Debug(fontcache, 2, "Requested '{}', using '{} {}'", font_name, face->family_name, face->style_name); /* Attempt to select the unicode character map */ error = FT_Select_Charmap(face, ft_encoding_unicode); if (error == FT_Err_Ok) goto found_face; // Success 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]; int i; for (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) goto found_face; } } } FT_Done_Face(face); static const char *SIZE_TO_NAME[] = { "medium", "small", "large", "mono" }; ShowInfoF("Unable to use '%s' for %s font, FreeType reported error 0x%X, using sprite font instead", font_name, SIZE_TO_NAME[fs], error); return; found_face: new FreeTypeFontCache(fs, face, settings->size); } /** * 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->fs, this->face, 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 == FS_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::Sprite sprite; sprite.AllocateData(ZOOM_LVL_NORMAL, width * height); sprite.type = ST_FONT; sprite.colours = (aa ? SCC_PAL | SCC_ALPHA : SCC_PAL); 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 == FS_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; } } } GlyphEntry new_glyph; new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(&sprite, SimpleSpriteAlloc); new_glyph.width = slot->advance.x >> 6; this->SetGlyphPtr(key, &new_glyph); return new_glyph.sprite; } GlyphID FreeTypeFontCache::MapCharToGlyph(WChar key) { assert(IsPrintable(key)); if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { return this->parent->MapCharToGlyph(key); } return FT_Get_Char_Index(this->face, key); } const void *FreeTypeFontCache::InternalGetFontTable(uint32 tag, size_t &length) { FT_ULong len = 0; FT_Byte *result = nullptr; FT_Load_Sfnt_Table(this->face, tag, 0, nullptr, &len); if (len > 0) { result = MallocT(len); FT_Load_Sfnt_Table(this->face, tag, 0, result, &len); } length = len; return result; } /** * Free everything allocated w.r.t. freetype. */ void UninitFreeType() { FT_Done_FreeType(_library); _library = nullptr; } #if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) FT_Error GetFontByFaceName(const char *font_name, FT_Face *face) { return FT_Err_Cannot_Open_Resource; } #endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) */ #endif /* WITH_FREETYPE */