diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index 1337d3293..86eb6f891 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -615,37 +615,65 @@ public: void AddFace(FT_Face Face) { m_vFtFaces.push_back(Face); - if(!m_DefaultFace) - m_DefaultFace = Face; } - void SetDefaultFaceByName(const char *pFamilyName) + bool SetDefaultFaceByName(const char *pFamilyName) { m_DefaultFace = GetFaceByName(pFamilyName); + if(!m_DefaultFace) + { + if(!m_vFtFaces.empty()) + { + m_DefaultFace = m_vFtFaces.front(); + } + log_error("textrender", "The default font face '%s' could not be found", pFamilyName); + return false; + } + return true; } - void SetIconFaceByName(const char *pFamilyName) + bool SetIconFaceByName(const char *pFamilyName) { m_IconFace = GetFaceByName(pFamilyName); + if(!m_IconFace) + { + log_error("textrender", "The icon font face '%s' could not be found", pFamilyName); + return false; + } + return true; } - void AddFallbackFaceByName(const char *pFamilyName) + bool AddFallbackFaceByName(const char *pFamilyName) { FT_Face Face = GetFaceByName(pFamilyName); - if(Face != nullptr && std::find(m_vFallbackFaces.begin(), m_vFallbackFaces.end(), Face) == m_vFallbackFaces.end()) + if(!Face) { - m_vFallbackFaces.push_back(Face); + log_error("textrender", "The fallback font face '%s' could not be found", pFamilyName); + return false; } + if(std::find(m_vFallbackFaces.begin(), m_vFallbackFaces.end(), Face) != m_vFallbackFaces.end()) + { + log_warn("textrender", "The fallback font face '%s' was specified multiple times", pFamilyName); + return true; + } + m_vFallbackFaces.push_back(Face); + return true; } - void SetVariantFaceByName(const char *pFamilyName) + bool SetVariantFaceByName(const char *pFamilyName) { FT_Face Face = GetFaceByName(pFamilyName); if(m_VariantFace != Face) { m_VariantFace = Face; Clear(); // rebuild atlas after changing variant font + if(!Face && pFamilyName != nullptr) + { + log_error("textrender", "The variant font face '%s' could not be found", pFamilyName); + return false; + } } + return true; } void SetFontPreset(EFontPreset FontPreset) @@ -1002,9 +1030,7 @@ class CTextRender : public IEngineTextRender FT_Error CollectionLoadError = FT_New_Memory_Face(m_FTLibrary, pFontData, FontDataSize, -1, &FtFace); if(CollectionLoadError) { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Failed to load font file '%s': %s", pFontName, FT_Error_String(CollectionLoadError)); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf); + log_error("textrender", "Failed to load font file '%s': %s", pFontName, FT_Error_String(CollectionLoadError)); return false; } @@ -1017,26 +1043,20 @@ class CTextRender : public IEngineTextRender FT_Error FaceLoadError = FT_New_Memory_Face(m_FTLibrary, pFontData, FontDataSize, FaceIndex, &FtFace); if(FaceLoadError) { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Failed to load font face %ld from font file '%s': %s", FaceIndex, pFontName, FT_Error_String(FaceLoadError)); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf); + log_error("textrender", "Failed to load font face %ld from font file '%s': %s", FaceIndex, pFontName, FT_Error_String(FaceLoadError)); FT_Done_Face(FtFace); continue; } m_pGlyphMap->AddFace(FtFace); - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Loaded font face %ld '%s %s' from font file '%s'", FaceIndex, FtFace->family_name, FtFace->style_name, pFontName); - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "textrender", aBuf); + log_debug("textrender", "Loaded font face %ld '%s %s' from font file '%s'", FaceIndex, FtFace->family_name, FtFace->style_name, pFontName); LoadedAny = true; } if(!LoadedAny) { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Failed to load font file '%s': no font faces could be loaded", pFontName); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf); + log_error("textrender", "Failed to load font file '%s': no font faces could be loaded", pFontName); return false; } @@ -1083,9 +1103,7 @@ public: { int LMajor, LMinor, LPatch; FT_Library_Version(m_FTLibrary, &LMajor, &LMinor, &LPatch); - char aFreetypeVersion[128]; - str_format(aFreetypeVersion, sizeof(aFreetypeVersion), "Freetype version %d.%d.%d (compiled = %d.%d.%d)", LMajor, LMinor, LPatch, FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aFreetypeVersion); + log_info("textrender", "Freetype version %d.%d.%d (compiled = %d.%d.%d)", LMajor, LMinor, LPatch, FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); } m_FirstFreeTextContainerIndex = -1; @@ -1142,7 +1160,7 @@ public: m_pStorage = nullptr; } - void LoadFonts() override + bool LoadFonts() override { // read file data into buffer const char *pFilename = "fonts/index.json"; @@ -1150,10 +1168,8 @@ public: unsigned JsonFileSize; if(!Storage()->ReadFile(pFilename, IStorage::TYPE_ALL, &pFileData, &JsonFileSize)) { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Failed to open/read font index file '%s'", pFilename); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf); - return; + log_error("textrender", "Failed to open/read font index file '%s'", pFilename); + return false; } // parse json data @@ -1163,11 +1179,16 @@ public: free(pFileData); if(pJsonData == nullptr) { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Failed to parse font index file '%s': %s", pFilename, aError); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf); - return; + log_error("textrender", "Failed to parse font index file '%s': %s", pFilename, aError); + return false; } + if(pJsonData->type != json_object) + { + log_error("textrender", "Font index malformed: root must be an object", pFilename, aError); + return false; + } + + bool Success = true; // extract font file definitions const json_value &FontFiles = (*pJsonData)["font files"]; @@ -1176,7 +1197,11 @@ public: for(unsigned FontFileIndex = 0; FontFileIndex < FontFiles.u.array.length; ++FontFileIndex) { if(FontFiles[FontFileIndex].type != json_string) + { + log_error("textrender", "Font index malformed: 'font files' must be an array of strings (error at index %d)", FontFileIndex); + Success = false; continue; + } char aFontName[IO_MAX_PATH_LENGTH]; str_format(aFontName, sizeof(aFontName), "fonts/%s", FontFiles[FontFileIndex].u.string.ptr); @@ -1193,32 +1218,60 @@ public: free(pFontData); } } + else + { + log_error("textrender", "Failed to open/read font file '%s'", aFontName); + Success = false; + } } } + else + { + log_error("textrender", "Font index malformed: 'font files' must be an array"); + Success = false; + } // extract default family name const json_value &DefaultFace = (*pJsonData)["default"]; if(DefaultFace.type == json_string) { - m_pGlyphMap->SetDefaultFaceByName(DefaultFace.u.string.ptr); + if(!m_pGlyphMap->SetDefaultFaceByName(DefaultFace.u.string.ptr)) + { + Success = false; + } + } + else + { + log_error("textrender", "Font index malformed: 'default' must be a string"); + Success = false; } // extract language variant family names const json_value &Variants = (*pJsonData)["language variants"]; if(Variants.type == json_object) { - m_vVariants.resize(Variants.u.object.length); + m_vVariants.reserve(Variants.u.object.length); for(size_t i = 0; i < Variants.u.object.length; ++i) { - str_format(m_vVariants[i].m_aLanguageFile, sizeof(m_vVariants[i].m_aLanguageFile), "languages/%s.txt", Variants.u.object.values[i].name); - const json_value *pFamilyName = Variants.u.object.values[i].value; - if(pFamilyName->type == json_string) - str_copy(m_vVariants[i].m_aFamilyName, pFamilyName->u.string.ptr); - else - m_vVariants[i].m_aFamilyName[0] = '\0'; + if(pFamilyName->type != json_string) + { + log_error("textrender", "Font index malformed: 'language variants' entries must have string values (error on entry '%s')", Variants.u.object.values[i].name); + Success = false; + continue; + } + + SFontLanguageVariant Variant; + str_format(Variant.m_aLanguageFile, sizeof(Variant.m_aLanguageFile), "languages/%s.txt", Variants.u.object.values[i].name); + str_copy(Variant.m_aFamilyName, pFamilyName->u.string.ptr); + m_vVariants.emplace_back(Variant); } } + else + { + log_error("textrender", "Font index malformed: 'language variants' must be an array"); + Success = false; + } // extract fallback family names const json_value &FallbackFaces = (*pJsonData)["fallbacks"]; @@ -1226,21 +1279,41 @@ public: { for(unsigned i = 0; i < FallbackFaces.u.array.length; ++i) { - if(FallbackFaces[i].type == json_string) + if(FallbackFaces[i].type != json_string) { - m_pGlyphMap->AddFallbackFaceByName(FallbackFaces[i].u.string.ptr); + log_error("textrender", "Font index malformed: 'fallbacks' must be an array of strings (error at index %d)", i); + Success = false; + continue; + } + if(!m_pGlyphMap->AddFallbackFaceByName(FallbackFaces[i].u.string.ptr)) + { + Success = false; } } } + else + { + log_error("textrender", "Font index malformed: 'fallbacks' must be an array"); + Success = false; + } // extract icon font family name const json_value &IconFace = (*pJsonData)["icon"]; if(IconFace.type == json_string) { - m_pGlyphMap->SetIconFaceByName(IconFace.u.string.ptr); + if(!m_pGlyphMap->SetIconFaceByName(IconFace.u.string.ptr)) + { + Success = false; + } + } + else + { + log_error("textrender", "Font index malformed: 'icon' must be a string"); + Success = false; } json_value_free(pJsonData); + return Success; } void SetFontPreset(EFontPreset FontPreset) override diff --git a/src/engine/textrender.h b/src/engine/textrender.h index 3b26e0aaa..2c7e6561c 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -316,7 +316,7 @@ public: virtual void MoveCursor(CTextCursor *pCursor, float x, float y) const = 0; virtual void SetCursorPosition(CTextCursor *pCursor, float x, float y) const = 0; - virtual void LoadFonts() = 0; + virtual bool LoadFonts() = 0; virtual void SetFontPreset(EFontPreset FontPreset) = 0; virtual void SetFontLanguageVariant(const char *pLanguageFile) = 0; diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index de1295ada..dd3111bc3 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -328,7 +328,10 @@ void CGameClient::OnInit() for(int i = 0; i < OLD_NUM_NETOBJTYPES; i++) Client()->SnapSetStaticsize7(i, m_NetObjHandler7.GetObjSize(i)); - TextRender()->LoadFonts(); + if(!TextRender()->LoadFonts()) + { + Client()->AddWarning(SWarning(Localize("Some fonts could not be loaded. Check the local console for details."))); + } TextRender()->SetFontLanguageVariant(g_Config.m_ClLanguagefile); // update and swap after font loading, they are quite huge