Refactor fonts reloading

* Remove hardcoded default font name.
  This means the `fonts/fonts.ini` file is now mandatory
  and must contain definition of all 9 font types.
  Old mods relying on an incomplete `fonts.ini` file might break.
  A separate PR creating the required `fonts/fonts.ini` file
  should be merged before this pull request.
* Simplify `CFontLoader`.
    * Return `std::optional` instead of returning a default.
    * Remove the now unnecessary `std::map`s.
    * Remove the now unnecessary `GetFontType` method.
* Improve Gfx::FontType.
    * Provide `ToString` function for the enum, which is now
      used for logs and by `CFontLoader`.
    * Provide `ToBoldFontType` and `ToItalicFontType` functions.
    * Replace hex literals with binary literals for readability.
* Move font caching related code to a new private class `FontsCache`.
    * Add neccessary changes because of changes made in `CFontLoader`.
    * Add minor code improvements like renames and formatting.
    * Split the code into smaller functions for readability.
    * Simplify the `CText` class.
* Apply the rule of 5 to the `CachedFont` structure.
fix-squashed-planets
MrSimbax 2021-12-21 00:17:42 +01:00
parent 53053b901f
commit d3dd99c384
4 changed files with 246 additions and 148 deletions

View File

@ -33,36 +33,12 @@
#include <memory>
#include <utility>
#include <cstring>
#include <optional>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/regex.hpp>
namespace bp = boost::property_tree;
const std::map<Gfx::FontType, std::string> DEFAULT_FONT =
{
{ Gfx::FONT_COMMON, "dvu_sans.ttf" },
{ Gfx::FONT_COMMON_BOLD, "dvu_sans_bold.ttf" },
{ Gfx::FONT_COMMON_ITALIC, "dvu_sans_italic.ttf" },
{ Gfx::FONT_STUDIO, "dvu_sans_mono.ttf" },
{ Gfx::FONT_STUDIO_BOLD, "dvu_sans_mono_bold.ttf" },
{ Gfx::FONT_STUDIO_ITALIC, "dvu_sans_mono.ttf" }, //placeholder for future use, DejaVu Sans Mono doesn't have italic variant
{ Gfx::FONT_SATCOM, "dvu_sans.ttf" },
{ Gfx::FONT_SATCOM_BOLD, "dvu_sans_bold.ttf" },
{ Gfx::FONT_SATCOM_ITALIC, "dvu_sans_italic.ttf" },
};
const std::map<Gfx::FontType, std::string> FONT_TYPE =
{
{ Gfx::FONT_COMMON, "FontCommon" },
{ Gfx::FONT_COMMON_BOLD, "FontCommonBold" },
{ Gfx::FONT_COMMON_ITALIC, "FontCommonItalic" },
{ Gfx::FONT_STUDIO, "FontStudio" },
{ Gfx::FONT_STUDIO_BOLD, "FontStudioBold" },
{ Gfx::FONT_STUDIO_ITALIC, "FontStudioItalic" },
{ Gfx::FONT_SATCOM, "FontSatCom" },
{ Gfx::FONT_SATCOM_BOLD, "FontSatComBold" },
{ Gfx::FONT_SATCOM_ITALIC, "FontSatComItalic" },
};
CFontLoader::CFontLoader()
{
@ -99,17 +75,10 @@ bool CFontLoader::Init()
return true;
}
std::string CFontLoader::GetFont(Gfx::FontType type)
std::optional<std::string> CFontLoader::GetFont(Gfx::FontType type) const
{
return std::string("/fonts/") + m_propertyTree.get<std::string>(GetFontType(type), GetDefaultFont(type));
}
std::string CFontLoader::GetDefaultFont(Gfx::FontType type) const
{
return DEFAULT_FONT.at(type);
}
std::string CFontLoader::GetFontType(Gfx::FontType type) const
{
return FONT_TYPE.at(type);
auto font = m_propertyTree.get_optional<std::string>(ToString(type));
if (font)
return std::string("/fonts/") + *font;
return std::nullopt;
}

View File

@ -50,22 +50,10 @@ public:
*/
bool Init();
/** Reads given font from file
* \return return path to font file
/** Reads given font path from file
* \return return path to font file if font type is configured
*/
std::string GetFont(Gfx::FontType type);
/** Const type method to read filenames of fonts from defaultFont map
* used as a fallback if it wasn't possible to read font from fonts.ini
* \return return filename of default path
*/
std::string GetDefaultFont(Gfx::FontType type) const;
/** Const type method converting Gfx::FontType to string
* \return return id of font used in fonts.ini file
*/
std::string GetFontType(Gfx::FontType type) const;
std::optional<std::string> GetFont(Gfx::FontType type) const;
private:
boost::property_tree::ptree m_propertyTree;

View File

@ -82,6 +82,24 @@ struct CachedFont
font = TTF_OpenFontRW(this->fontFile->GetHandler(), 0, pointSize);
}
CachedFont(CachedFont&& other) noexcept
: fontFile{std::move(other.fontFile)},
font{std::exchange(other.font, nullptr)},
cache{std::move(other.cache)}
{
}
CachedFont& operator=(CachedFont&& other) noexcept
{
fontFile = std::move(other.fontFile);
std::swap(font, other.font);
cache = std::move(other.cache);
return *this;
}
CachedFont(const CachedFont& other) = delete;
CachedFont& operator=(const CachedFont& other) = delete;
~CachedFont()
{
if (font != nullptr)
@ -89,11 +107,38 @@ struct CachedFont
}
};
std::string ToString(FontType type)
{
switch (type)
{
case FontType::FONT_COMMON: return "FontCommon";
case FontType::FONT_COMMON_BOLD: return "FontCommonBold";
case FontType::FONT_COMMON_ITALIC: return "FontCommonItalic";
case FontType::FONT_STUDIO: return "FontStudio";
case FontType::FONT_STUDIO_BOLD: return "FontStudioBold";
case FontType::FONT_STUDIO_ITALIC: return "FontStudioItalic";
case FontType::FONT_SATCOM: return "FontSatCom";
case FontType::FONT_SATCOM_BOLD: return "FontSatComBold";
case FontType::FONT_SATCOM_ITALIC: return "FontSatComItalic";
case FontType::FONT_BUTTON: return "FontButton";
default: throw std::invalid_argument("Unsupported value for Gfx::FontType -> std::string conversion: " + std::to_string(type));
}
}
namespace
{
const Math::IntPoint REFERENCE_SIZE(800, 600);
const Math::IntPoint FONT_TEXTURE_SIZE(256, 256);
Gfx::FontType ToBoldFontType(Gfx::FontType type)
{
return static_cast<Gfx::FontType>(type | FONT_BOLD);
}
Gfx::FontType ToItalicFontType(Gfx::FontType type)
{
return static_cast<Gfx::FontType>(type | FONT_ITALIC);
}
} // anonymous namespace
/// The QuadBatch is responsible for collecting as many quad (aka rectangle) draws as possible and
@ -167,6 +212,166 @@ private:
EngineRenderState m_renderState{};
};
class FontsCache
{
public:
using Fonts = std::map<FontType, std::unique_ptr<MultisizeFont>>;
FontsCache()
{
ClearLastCachedFont();
}
bool Reload(const CFontLoader& fontLoader, int pointSize)
{
Flush();
if (!PrepareCache(fontLoader)) { Flush(); return false; }
if (!LoadDefaultFonts(fontLoader, pointSize)) { Flush(); return false; }
return true;
}
CachedFont* GetOrOpenFont(FontType type, int pointSize)
{
if (IsLastCachedFont(type, pointSize))
return m_lastCachedFont;
auto multisizeFontIt = m_fonts.find(type);
if (multisizeFontIt == m_fonts.end())
{
m_error = std::string("Font type not found in cache: ") + ToString(type);
return nullptr;
}
MultisizeFont* multisizeFont = multisizeFontIt->second.get();
auto cachedFontIt = multisizeFont->fonts.find(pointSize);
if (cachedFontIt != multisizeFont->fonts.end())
{
auto* cachedFont = cachedFontIt->second.get();
SaveLastCachedFont(cachedFont, type, pointSize);
return m_lastCachedFont;
}
auto newFont = LoadFont(multisizeFont, pointSize);
if (!newFont) return nullptr;
SaveLastCachedFont(newFont.get(), type, pointSize);
multisizeFont->fonts[pointSize] = std::move(newFont);
return m_lastCachedFont;
}
void Flush()
{
Clear();
ClearLastCachedFont();
}
~FontsCache()
{
Flush();
}
std::string GetError() const
{
return m_error;
}
private:
bool PrepareCache(const CFontLoader& fontLoader)
{
for (auto type : {FONT_COMMON, FONT_STUDIO, FONT_SATCOM})
{
if (!PrepareCacheForFontType(type, fontLoader)) return false;
if (!PrepareCacheForFontType(ToBoldFontType(type), fontLoader)) return false;
if (!PrepareCacheForFontType(ToItalicFontType(type), fontLoader)) return false;
}
return true;
}
bool PrepareCacheForFontType(Gfx::FontType type, const CFontLoader& fontLoader)
{
if (auto font = fontLoader.GetFont(type))
{
m_fonts[type] = MakeUnique<MultisizeFont>(std::move(*font));
return true;
}
m_error = "Error on loading fonts: font type " + ToString(type) + " is not configured";
return false;
}
bool LoadDefaultFonts(const CFontLoader& fontLoader, int pointSize)
{
for (auto& font : m_fonts)
{
auto type = font.first;
auto* cachedFont = GetOrOpenFont(type, pointSize);
if (cachedFont == nullptr || cachedFont->font == nullptr)
return false;
}
return true;
}
std::unique_ptr<CachedFont> LoadFont(MultisizeFont* multisizeFont, int pointSize)
{
auto file = CResourceManager::GetSDLMemoryHandler(multisizeFont->fileName);
if (!file->IsOpen())
{
m_error = "Unable to open file '" + multisizeFont->fileName + "' (font size = " + std::to_string(pointSize) + ")";
return nullptr;
}
GetLogger()->Debug("Loaded font file %s (font size = %d)\n", multisizeFont->fileName.c_str(), pointSize);
auto newFont = MakeUnique<CachedFont>(std::move(file), pointSize);
if (newFont->font == nullptr)
{
m_error = std::string("TTF_OpenFont error ") + std::string(TTF_GetError());
return nullptr;
}
return newFont;
}
void SaveLastCachedFont(CachedFont* font, FontType type, int pointSize)
{
m_lastCachedFont = font;
m_lastFontType = type;
m_lastFontSize = pointSize;
}
bool IsLastCachedFont(FontType font, int pointSize)
{
return
m_lastCachedFont != nullptr &&
m_lastFontType == font &&
m_lastFontSize == pointSize;
}
void Clear()
{
for (auto& [fontType, multisizeFont] : m_fonts)
{
for (auto& cachedFont : multisizeFont->fonts)
{
cachedFont.second->cache.clear();
}
}
m_fonts.clear();
}
void ClearLastCachedFont()
{
m_lastCachedFont = nullptr;
m_lastFontType = FONT_COMMON;
m_lastFontSize = 0;
}
private:
Fonts m_fonts;
CachedFont* m_lastCachedFont;
FontType m_lastFontType;
int m_lastFontSize;
std::string m_error;
};
CText::CText(CEngine* engine)
{
@ -176,9 +381,7 @@ CText::CText(CEngine* engine)
m_defaultSize = 12.0f;
m_tabSize = 4;
m_lastFontType = FONT_COMMON;
m_lastFontSize = 0;
m_lastCachedFont = nullptr;
m_fontsCache = std::make_unique<FontsCache>();
m_quadBatch = MakeUnique<CQuadBatch>(*engine);
}
@ -210,42 +413,24 @@ bool CText::ReloadFonts()
CFontLoader fontLoader;
if (!fontLoader.Init())
{
GetLogger()->Debug("Error on parsing fonts config file: failed to open file\n");
m_error = "Error on parsing fonts config file: failed to open file";
return false;
}
// Backup previous fonts
auto fonts = std::move(m_fonts);
m_fonts.clear();
for (auto type : {FONT_COMMON, FONT_STUDIO, FONT_SATCOM})
auto newCache = std::make_unique<FontsCache>();
if (!newCache->Reload(fontLoader, GetFontPointSize(m_defaultSize)))
{
m_fonts[static_cast<Gfx::FontType>(type)] = MakeUnique<MultisizeFont>(fontLoader.GetFont(type));
m_fonts[static_cast<Gfx::FontType>(type|FONT_BOLD)] = MakeUnique<MultisizeFont>(fontLoader.GetFont(static_cast<Gfx::FontType>(type|FONT_BOLD)));
m_fonts[static_cast<Gfx::FontType>(type|FONT_ITALIC)] = MakeUnique<MultisizeFont>(fontLoader.GetFont(static_cast<Gfx::FontType>(type|FONT_ITALIC)));
}
for (auto it = m_fonts.begin(); it != m_fonts.end(); ++it)
{
FontType type = (*it).first;
CachedFont* cf = GetOrOpenFont(type, m_defaultSize);
if (cf == nullptr || cf->font == nullptr)
{
m_fonts = std::move(fonts);
return false;
}
m_error = newCache->GetError();
return false;
}
m_fontsCache = std::move(newCache);
return true;
}
void CText::Destroy()
{
m_fonts.clear();
m_lastCachedFont = nullptr;
m_lastFontType = FONT_COMMON;
m_lastFontSize = 0;
m_fontsCache->Flush();
TTF_Quit();
}
@ -269,17 +454,7 @@ void CText::FlushCache()
}
m_fontTextures.clear();
for (auto& multisizeFont : m_fonts)
{
for (auto& cachedFont : multisizeFont.second->fonts)
{
cachedFont.second->cache.clear();
}
}
m_lastCachedFont = nullptr;
m_lastFontType = FONT_COMMON;
m_lastFontSize = 0;
m_fontsCache->Flush();
}
int CText::GetTabSize()
@ -1099,54 +1274,21 @@ void CText::DrawCharAndAdjustPos(UTF8Char ch, FontType font, float size, Math::I
}
}
CachedFont* CText::GetOrOpenFont(FontType font, float size)
int CText::GetFontPointSize(float size) const
{
Math::IntPoint windowSize = m_engine->GetWindowSize();
int pointSize = static_cast<int>(size * (windowSize.Length() / REFERENCE_SIZE.Length()));
return static_cast<int>(size * (windowSize.Length() / REFERENCE_SIZE.Length()));
}
if (m_lastCachedFont != nullptr &&
m_lastFontType == font &&
m_lastFontSize == pointSize)
CachedFont* CText::GetOrOpenFont(FontType type, float size)
{
auto* cachedFont = m_fontsCache->GetOrOpenFont(type, GetFontPointSize(size));
if (!cachedFont)
{
return m_lastCachedFont;
}
auto it = m_fonts.find(font);
if (it == m_fonts.end())
{
m_error = std::string("Invalid font type ") + StrUtils::ToString<int>(static_cast<int>(font));
m_error = m_fontsCache->GetError();
return nullptr;
}
MultisizeFont* mf = it->second.get();
auto jt = mf->fonts.find(pointSize);
if (jt != mf->fonts.end())
{
m_lastCachedFont = jt->second.get();
m_lastFontType = font;
m_lastFontSize = pointSize;
return m_lastCachedFont;
}
auto file = CResourceManager::GetSDLMemoryHandler(mf->fileName);
if (!file->IsOpen())
{
m_error = std::string("Unable to open file '") + mf->fileName + "' (font size = " + StrUtils::ToString<float>(size) + ")";
return nullptr;
}
GetLogger()->Debug("Loaded font file %s (font size = %.1f)\n", mf->fileName.c_str(), size);
auto newFont = MakeUnique<CachedFont>(std::move(file), pointSize);
if (newFont->font == nullptr)
{
m_error = std::string("TTF_OpenFont error ") + std::string(TTF_GetError());
return nullptr;
}
m_lastCachedFont = newFont.get();
mf->fonts[pointSize] = std::move(newFont);
return m_lastCachedFont;
return cachedFont;
}
CharTexture CText::GetCharTexture(UTF8Char ch, FontType font, float size)

View File

@ -34,7 +34,6 @@
#include <memory>
#include <vector>
// Graphics module namespace
namespace Gfx
{
@ -69,38 +68,40 @@ typedef short FontMetaChar;
*
* Bitmask in lower 4 bits (mask 0x00f)
*/
enum FontType
enum FontType : unsigned char
{
//! Flag for bold font subtype
FONT_BOLD = 0x04,
FONT_BOLD = 0b0000'01'00,
//! Flag for italic font subtype
FONT_ITALIC = 0x08,
FONT_ITALIC = 0b0000'10'00,
//! Default colobot font used for interface
FONT_COMMON = 0x00,
FONT_COMMON = 0b0000'00'00,
//! Alias for bold colobot font
FONT_COMMON_BOLD = FONT_COMMON | FONT_BOLD,
//! Alias for italic colobot font
FONT_COMMON_ITALIC = FONT_COMMON | FONT_ITALIC,
//! Studio font used mainly in code editor
FONT_STUDIO = 0x01,
FONT_STUDIO = 0b0000'00'01,
//! Alias for bold studio font
FONT_STUDIO_BOLD = FONT_STUDIO | FONT_BOLD,
//! Alias for italic studio font (at this point not used anywhere)
FONT_STUDIO_ITALIC = FONT_STUDIO | FONT_ITALIC,
//! SatCom font used for interface (currently bold and italic wariants aren't used anywhere)
FONT_SATCOM = 0x02,
FONT_SATCOM = 0b0000'00'10,
//! Alias for bold satcom font
FONT_SATCOM_BOLD = FONT_SATCOM | FONT_BOLD,
//! Alias for italic satcom font
FONT_SATCOM_ITALIC = FONT_SATCOM | FONT_ITALIC,
//! Pseudo-font loaded from textures for buttons, icons, etc.
FONT_BUTTON = 0x03,
FONT_BUTTON = 0b0000'00'11,
};
std::string ToString(FontType);
/**
* \enum FontTitle
* \brief Size of font title
@ -204,6 +205,7 @@ struct CharTexture
};
// Definition is private - in text.cpp
class FontsCache;
struct CachedFont;
struct MultisizeFont;
struct FontTexture;
@ -323,7 +325,8 @@ public:
Math::IntPoint GetFontTextureSize();
protected:
CachedFont* GetOrOpenFont(FontType font, float size);
int GetFontPointSize(float size) const;
CachedFont* GetOrOpenFont(FontType type, float size);
CharTexture CreateCharTexture(UTF8Char ch, CachedFont* font);
FontTexture* GetOrCreateFontTexture(Math::IntPoint tileSize);
FontTexture CreateFontTexture(Math::IntPoint tileSize);
@ -349,13 +352,9 @@ protected:
float m_defaultSize;
int m_tabSize;
std::map<FontType, std::unique_ptr<MultisizeFont>> m_fonts;
std::unique_ptr<FontsCache> m_fontsCache;
std::vector<FontTexture> m_fontTextures;
FontType m_lastFontType;
int m_lastFontSize;
CachedFont* m_lastCachedFont;
class CQuadBatch;
std::unique_ptr<CQuadBatch> m_quadBatch;
};