diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 6409e647c1..7d6d587d36 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -620,7 +620,7 @@ height.smoothness = 0.5 height.min = 16 [fonts] -default = "LinBiolinum_Rah.ttf" ; Default font to use for all text +default = "LinBiolinum_Rah.ttf", "FreeSans.ttf" ; Default font to use for all text ;;;;;;;;;;;;;;;;;;;;;;;; @@ -637,15 +637,19 @@ default = "LinBiolinum_Rah.ttf" ; Default font to use for all text ; Support for locale fonts ; [fonts..] ; Example: fonts.jp.sans = "font.ttf" +; +; Each value is a comma separated list of font files +; Example: fonts.sans.regular = "font.ttf", "font2.ttf" +; Example: fonts.sans.bold = "font.ttf" ; ************************************************************** [fonts.sans] -regular = "LinBiolinum_Rah.ttf" -bold = "LinBiolinum_RBah.ttf" -italic = "LinBiolinum_RIah.ttf" +regular = "LinBiolinum_Rah.ttf", "FreeSans.ttf" +bold = "LinBiolinum_RBah.ttf", "FreeSansBold.ttf" +italic = "LinBiolinum_RIah.ttf", "FreeSansOblique.ttf" [fonts.mono] -regular = "DejaVuSansMono.ttf" +regular = "DejaVuSansMono.ttf", "FreeMono.ttf" [fonts.ja.sans] regular = "SourceHanSansJP-Regular.otf" diff --git a/binaries/data/tools/fontbuilder/fonts/FreeFont-LICENSE.txt b/binaries/data/mods/mod/fonts/FreeFont-LICENSE.txt similarity index 100% rename from binaries/data/tools/fontbuilder/fonts/FreeFont-LICENSE.txt rename to binaries/data/mods/mod/fonts/FreeFont-LICENSE.txt diff --git a/binaries/data/tools/fontbuilder/fonts/FreeMono.ttf b/binaries/data/mods/mod/fonts/FreeMono.ttf similarity index 100% rename from binaries/data/tools/fontbuilder/fonts/FreeMono.ttf rename to binaries/data/mods/mod/fonts/FreeMono.ttf diff --git a/binaries/data/tools/fontbuilder/fonts/FreeSans.ttf b/binaries/data/mods/mod/fonts/FreeSans.ttf similarity index 100% rename from binaries/data/tools/fontbuilder/fonts/FreeSans.ttf rename to binaries/data/mods/mod/fonts/FreeSans.ttf diff --git a/binaries/data/tools/fontbuilder/fonts/FreeSansBold.ttf b/binaries/data/mods/mod/fonts/FreeSansBold.ttf similarity index 100% rename from binaries/data/tools/fontbuilder/fonts/FreeSansBold.ttf rename to binaries/data/mods/mod/fonts/FreeSansBold.ttf diff --git a/binaries/data/tools/fontbuilder/fonts/FreeSansOblique.ttf b/binaries/data/mods/mod/fonts/FreeSansOblique.ttf similarity index 100% rename from binaries/data/tools/fontbuilder/fonts/FreeSansOblique.ttf rename to binaries/data/mods/mod/fonts/FreeSansOblique.ttf diff --git a/source/graphics/Font.cpp b/source/graphics/Font.cpp index 42ade45ec4..9f39bbfee1 100644 --- a/source/graphics/Font.cpp +++ b/source/graphics/Font.cpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace { @@ -107,10 +108,10 @@ void CFont::CalculateStringSize(const wchar_t* string, float& width, float& heig if (!g) return sum; - if (!FT_HAS_KERNING(m_Font)) + if (!FT_HAS_KERNING(g->face)) return sum + g->xadvance; - const FT_UInt glyphIndex{FT_Get_Char_Index(m_Font.get(), c)}; + const FT_UInt glyphIndex{FT_Get_Char_Index(g->face, c)}; if (!glyphIndex) return sum + g->xadvance; @@ -120,7 +121,7 @@ void CFont::CalculateStringSize(const wchar_t* string, float& width, float& heig // Get the kerning value between the previous and current glyph. FT_Vector kerning; - FT_Get_Kerning(m_Font.get(), prevGlyph, glyphIndex, FT_KERNING_DEFAULT, &kerning); + FT_Get_Kerning(g->face, prevGlyph, glyphIndex, FT_KERNING_DEFAULT, &kerning); // Add the kerning distance. return sum + g->xadvance + FPosF26Dot6ToFloat(kerning.x); }) @@ -131,34 +132,16 @@ void CFont::CalculateStringSize(const wchar_t* string, float& width, float& heig } } -bool CFont::SetFontFromPath(const std::string& fontPath, const std::string& fontName, float size, float strokeWidth, float scale) +bool CFont::SetFontParams(const std::string& fontName, float size, float strokeWidth, float scale) { + ENSURE(m_FontSize == 0 && size > 0); + // TODO: expose the Stroke Width outside class. m_Scale = scale; m_StrokeWidth = strokeWidth * scale; m_FontSize = size * scale; m_FontName = fontName; - FT_Face face; - if (FT_Error error{FT_New_Face(m_FreeType, fontPath.c_str(), 0, &face)}) - { - if (error == FT_Err_Unknown_File_Format) - { - LOGERROR("Font file format is not supported: %s", fontPath.c_str()); - return false; - } - LOGERROR("Failed to load font %s: %d", fontPath.c_str(), error); - return false; - } - m_Font.reset(face); - - // Set the font size. - if (FT_Error error{FT_Set_Char_Size(m_Font.get(), 0, FloatToF26Dot6(m_FontSize), 0 , 0)}) - { - LOGERROR("Failed to set font size %d: %d", size, error); - return false; - } - if (m_StrokeWidth) { FT_Stroker stroker; @@ -177,13 +160,43 @@ bool CFont::SetFontFromPath(const std::string& fontPath, const std::string& font return false; } - // Get the height of the font. - m_Height = FPosF26Dot6ToFloat(m_Font->size->metrics.height); + return true; +} - // Get the line spacing of the font. - m_LineSpacing = FPosF26Dot6ToFloat(m_Font->size->metrics.ascender - m_Font->size->metrics.descender); +bool CFont::AddFontFromPath(const OsPath& fontPath) +{ + ENSURE(m_FontSize > 0); - m_IsLoading = true; + FT_Face face; + if (FT_Error error{FT_New_Face(m_FreeType, fontPath.string8().c_str(), 0, &face)}; error == FT_Err_Unknown_File_Format) + { + LOGERROR("Font file format is not supported: %s", fontPath.string8()); + return false; + } + else if (error) + { + LOGERROR("Failed to load font %s: %d", fontPath.string8(), error); + return false; + } + + // Set the font size. + if (FT_Error error{FT_Set_Char_Size(face, 0, FloatToF26Dot6(m_FontSize), 0 , 0)}) + { + LOGERROR("Failed to set font size %d: %d", m_FontSize, error); + return false; + } + + if(m_Faces.empty()) + { + // Get the height of the font. + m_Height = FPosF26Dot6ToFloat(face->size->metrics.height); + + // Get the line spacing of the font. + m_LineSpacing = FPosF26Dot6ToFloat(face->size->metrics.ascender - face->size->metrics.descender); + } + + // Add the fallback font to the list. + m_Faces.push_back({face, &ftFaceDeleter}); return true; } @@ -247,6 +260,8 @@ bool CFont::ConstructTextureAtlas() return false; } + m_IsLoadingTextureToGPU = true; + // Initialise texture with transparency, for the areas we don't // overwrite with uploading later. m_TexData = std::make_unique(m_AtlasSize); @@ -267,18 +282,31 @@ const CFont::GlyphData* CFont::GetGlyph(u16 codepoint) const CFont::GlyphData* CFont::ExtractAndGenerateGlyph(u16 codepoint) { + ENSURE(!m_Faces.empty()); PROFILE2("Glyph font texture generate"); - const FT_UInt glyphIndex{FT_Get_Char_Index(m_Font.get(), codepoint)}; + const auto [faceToUse, glyphIndex]{[&]()->std::pair + { + FT_UInt index{0}; + std::vector::iterator it{std::find_if(m_Faces.begin(), m_Faces.end(), [&](const UniqueFTFace& face) + { + index = FT_Get_Char_Index(face.get(), codepoint); + return index != 0; + } + )}; + + return {it != m_Faces.end() ? it->get() : m_Faces.front().get(), index}; + }() + }; const FT_Int32 loadFlags{FT_LOAD_DEFAULT | (m_FontSize <= MINIMAL_FONT_SIZE_ANTIALIASING ? FT_LOAD_TARGET_MONO : 0)}; - if (FT_Error error{FT_Load_Glyph(m_Font.get(), glyphIndex, loadFlags)}) + if (FT_Error error{FT_Load_Glyph(faceToUse, glyphIndex, loadFlags)}) { LOGERROR("Failed to load glyph %u: %d", codepoint, error); return nullptr; } - const FT_GlyphSlot slot{m_Font->glyph}; + const FT_GlyphSlot slot{faceToUse->glyph}; FT_Glyph glyph; if (FT_Error error{FT_Get_Glyph(slot, &glyph)}) { @@ -287,7 +315,7 @@ const CFont::GlyphData* CFont::ExtractAndGenerateGlyph(u16 codepoint) } UniqueFTGlyph glyphPtr(glyph); - const float baselineInAtlas{FPosF26Dot6ToFloat(m_Font->size->metrics.ascender)}; + const float baselineInAtlas{FPosF26Dot6ToFloat(faceToUse->size->metrics.ascender)}; const float glyphW{FPosF26Dot6ToFloat(slot->advance.x)}; if (m_AtlasX + glyphW + m_StrokeWidth + m_AtlasPadding > m_AtlasWidth) @@ -340,6 +368,7 @@ const CFont::GlyphData* CFont::ExtractAndGenerateGlyph(u16 codepoint) gd.xadvance = glyphW / m_Scale; gd.defined = 1; + gd.face = faceToUse; m_Glyphs.set(codepoint, gd); @@ -426,7 +455,7 @@ std::optional CFont::GenerateGlyphBitmap(FT_Glyph& glyph, u16 codepoi void CFont::UploadTextureAtlasToGPU() { - if (std::exchange(m_IsLoading, false)) + if (std::exchange(m_IsLoadingTextureToGPU, false)) return; if (!m_IsDirty) diff --git a/source/graphics/Font.h b/source/graphics/Font.h index 65cd5861c1..c2e5234441 100644 --- a/source/graphics/Font.h +++ b/source/graphics/Font.h @@ -21,6 +21,7 @@ #include "graphics/Texture.h" #include "maths/Rect.h" #include "renderer/Renderer.h" +#include "ps/Filesystem.h" #include #include FT_FREETYPE_H @@ -45,6 +46,7 @@ public: float x0, y0, x1, y1; float xadvance; u8 defined{0}; + FT_Face face; }; /** @@ -104,7 +106,8 @@ private: friend class CFontManager; - bool SetFontFromPath(const std::string& fontPath, const std::string& fontName, float size, float strokeWidth, float scale); + bool AddFontFromPath(const OsPath& fontPath); + bool SetFontParams(const std::string& fontName, float size, float strokeWidth, float scale); void BlendGlyphBitmapToTexture(const FT_Bitmap& bitmap, int targetX, int targetY, u8 r, u8 g, u8 b); void BlendGlyphBitmapToTextureRGBA(const FT_Bitmap& bitmap, int targetX, int targetY, u8 r, u8 g, u8 b); @@ -142,7 +145,7 @@ private: int m_AtlasPadding; bool m_IsDirty{false}; - bool m_IsLoading{false}; + bool m_IsLoadingTextureToGPU{false}; float m_StrokeWidth{0.0f}; float m_Scale{1.0f}; @@ -156,7 +159,7 @@ private: std::shared_ptr> m_GammaCorrectionLUT{nullptr}; FT_Library m_FreeType; - UniqueFTFace m_Font{nullptr, &ftFaceDeleter}; + std::vector m_Faces; UniqueFTStroker m_Stroker{nullptr, &ftStrokerDeleter}; /** diff --git a/source/graphics/FontManager.cpp b/source/graphics/FontManager.cpp index 6a13399050..3aa65c0bbe 100644 --- a/source/graphics/FontManager.cpp +++ b/source/graphics/FontManager.cpp @@ -24,14 +24,12 @@ #include "i18n/L10n.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" -#include "ps/CStr.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "renderer/Renderer.h" #include #include -#include namespace { struct FontSpec { @@ -130,7 +128,7 @@ std::shared_ptr CFontManager::LoadFont(CStrIntern fontName) } // Check for font configuration or fallback. - const std::string fontToSearch{[&] + const std::map fontToSearch{[&] { std::vector candidateFonts; // 3 types * 2 (bold, italic). @@ -156,39 +154,52 @@ std::shared_ptr CFontManager::LoadFont(CStrIntern fontName) for (const std::string& key : candidateFonts) { - std::string value = g_ConfigDB.Get(key, std::string{}); - if (!value.empty()) + std::map value{g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, key)}; + std::map::iterator item{value.find(key)}; + + if (item != value.end() && !item->second.empty()) return value; } // Fallback to default. - return defaultFont; + return g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, defaultFont); }() }; std::shared_ptr font{std::make_shared(this->m_FreeType.get(), m_GammaCorrectionLUT)}; + if (!font->SetFontParams(localeFontName.string(), fontSpec.size, fontSpec.stroke ? 1.0f : 0.0f, guiScale)) + { + LOGERROR("Failed to set font params for %s", localeFontName.string().c_str()); + return nullptr; + } + const VfsPath path(L"fonts/"); - const VfsPath fntName(fontToSearch); - OsPath realPath; - - if (!VfsFileExists(path / fntName)) + for (const std::pair& configPair : fontToSearch) { - LOGERROR("Font file %s not found", fontToSearch.c_str()); - return nullptr; - } + for (const CStr& fontPath : configPair.second) + { + const VfsPath fntPath{path / fontPath}; + if (!VfsFileExists(fntPath)) + { + LOGERROR("Font file %s not found", fontPath.c_str()); + return nullptr; + } - g_VFS->GetOriginalPath(path / fntName, realPath); - if (realPath.empty()) - { - LOGERROR("Font file %s not found", fontToSearch.c_str()); - return nullptr; - } + OsPath realPath; + g_VFS->GetOriginalPath(fntPath, realPath); + if (realPath.empty()) + { + LOGERROR("Font file %s not found", fontPath.c_str()); + return nullptr; + } - // TODO: For now set strokeWith = 1, later we can expose it to the GUI. - if (!font->SetFontFromPath(realPath.string8(), localeFontName.string(), fontSpec.size, fontSpec.stroke ? 1.0f : 0.0f, guiScale)) - { - return nullptr; + if (!font->AddFontFromPath(realPath)) + { + LOGERROR("Failed to load font %s", realPath.string8()); + return nullptr; + } + } } m_Fonts[localeFontName] = font; diff --git a/source/tools/entity/checkrefs.py b/source/tools/entity/checkrefs.py index 74aa2df66b..5d3e56e79d 100755 --- a/source/tools/entity/checkrefs.py +++ b/source/tools/entity/checkrefs.py @@ -1003,7 +1003,7 @@ class CheckRefs: for key, value in config[section].items(): if key.split(".")[-1] in ["regular", "bold", "italic"]: for font_name in value.split(","): - referenced_fonts.add(font_name.strip('"')) + referenced_fonts.add(font_name.strip().strip('"')) for font in referenced_fonts: if not (font_dir / font).is_file():