[Windows] Fix driver version detection of AMD RDNA+ GPU on Windows 10
[xbmc.git] / xbmc / guilib / GUIFontManager.cpp
blob93f88666a09448793b69a5203098afd768c82104
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "GUIFontManager.h"
11 #include "FileItemList.h"
12 #include "GUIComponent.h"
13 #include "GUIFontTTF.h"
14 #include "GUIWindowManager.h"
15 #include "addons/AddonManager.h"
16 #include "addons/FontResource.h"
17 #include "addons/Skin.h"
18 #include "addons/addoninfo/AddonType.h"
19 #include "filesystem/SpecialProtocol.h"
20 #include "windowing/GraphicContext.h"
22 #include <mutex>
23 #if defined(HAS_GL)
24 #include "GUIFontTTFGL.h"
25 #endif
26 #if defined(HAS_GLES)
27 #include "GUIFontTTFGLES.h"
28 #endif
29 #include "FileItem.h"
30 #include "GUIControlFactory.h"
31 #include "GUIFont.h"
32 #include "ServiceBroker.h"
33 #include "URL.h"
34 #include "filesystem/Directory.h"
35 #include "settings/lib/Setting.h"
36 #include "settings/lib/SettingDefinitions.h"
37 #include "utils/FileUtils.h"
38 #include "utils/FontUtils.h"
39 #include "utils/StringUtils.h"
40 #include "utils/URIUtils.h"
41 #include "utils/XMLUtils.h"
42 #include "utils/log.h"
44 #include <algorithm>
45 #include <set>
47 using namespace XFILE;
48 using namespace ADDON;
50 namespace
52 constexpr const char* XML_FONTCACHE_FILENAME = "fontcache.xml";
54 bool LoadXMLData(const std::string& filepath, CXBMCTinyXML& xmlDoc)
56 if (!CFileUtils::Exists(filepath))
58 CLog::LogF(LOGDEBUG, "Couldn't load '{}' the file don't exists", filepath);
59 return false;
61 if (!xmlDoc.LoadFile(filepath))
63 CLog::LogF(LOGERROR, "Couldn't load '{}'", filepath);
64 return false;
66 TiXmlElement* pRootElement = xmlDoc.RootElement();
67 if (!pRootElement || pRootElement->ValueStr() != "fonts")
69 CLog::LogF(LOGERROR, "Couldn't load '{}' XML content doesn't start with <fonts>", filepath);
70 return false;
72 return true;
74 } // unnamed namespace
77 GUIFontManager::GUIFontManager() = default;
79 GUIFontManager::~GUIFontManager()
81 Clear();
84 void GUIFontManager::RescaleFontSizeAndAspect(CGraphicContext& context,
85 float* size,
86 float* aspect,
87 const RESOLUTION_INFO& sourceRes,
88 bool preserveAspect)
90 // get the UI scaling constants so that we can scale our font sizes correctly
91 // as fonts aren't scaled at render time (due to aliasing) we must scale
92 // the size of the fonts before they are drawn to bitmaps
93 float scaleX, scaleY;
94 context.GetGUIScaling(sourceRes, scaleX, scaleY);
96 if (preserveAspect)
98 // font always displayed in the aspect specified by the aspect parameter
99 *aspect /= context.GetResInfo().fPixelRatio;
101 else
103 // font stretched like the rest of the UI, aspect parameter being the original aspect
105 // adjust aspect ratio
106 *aspect *= sourceRes.fPixelRatio;
108 *aspect *= scaleY / scaleX;
111 *size /= scaleY;
114 static bool CheckFont(std::string& strPath, const std::string& newPath, const std::string& filename)
116 if (!CFileUtils::Exists(strPath))
118 strPath = URIUtils::AddFileToFolder(newPath, filename);
119 #ifdef TARGET_POSIX
120 strPath = CSpecialProtocol::TranslatePathConvertCase(strPath);
121 #endif
122 return false;
125 return true;
128 CGUIFont* GUIFontManager::LoadTTF(const std::string& strFontName,
129 const std::string& strFilename,
130 UTILS::COLOR::Color textColor,
131 UTILS::COLOR::Color shadowColor,
132 const int iSize,
133 const int iStyle,
134 bool border,
135 float lineSpacing,
136 float aspect,
137 const RESOLUTION_INFO* sourceRes,
138 bool preserveAspect)
140 CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
141 if (!winSystem)
143 CLog::Log(LOGFATAL,
144 "GUIFontManager::{}: Something tries to call function without an available GUI "
145 "window system",
146 __func__);
147 return nullptr;
150 CGraphicContext& context = winSystem->GetGfxContext();
152 float originalAspect = aspect;
154 //check if font already exists
155 CGUIFont* pFont = GetFont(strFontName, false);
156 if (pFont)
157 return pFont;
159 if (!sourceRes) // no source res specified, so assume the skin res
160 sourceRes = &m_skinResolution;
162 float newSize = static_cast<float>(iSize);
163 RescaleFontSizeAndAspect(context, &newSize, &aspect, *sourceRes, preserveAspect);
165 // First try to load the font from the skin
166 std::string strPath;
167 if (!CURL::IsFullPath(strFilename))
169 strPath = URIUtils::AddFileToFolder(context.GetMediaDir(), "fonts", strFilename);
171 else
172 strPath = strFilename;
174 #ifdef TARGET_POSIX
175 strPath = CSpecialProtocol::TranslatePathConvertCase(strPath);
176 #endif
178 // Check if the file exists, otherwise try loading it from the global media dir
179 std::string file = URIUtils::GetFileName(strFilename);
180 if (!CheckFont(strPath, "special://home/media/Fonts", file) &&
181 !CheckFont(strPath, "special://xbmc/media/Fonts", file))
183 VECADDONS addons;
184 CServiceBroker::GetAddonMgr().GetAddons(addons, AddonType::RESOURCE_FONT);
185 for (auto& it : addons)
187 std::shared_ptr<CFontResource> font(std::static_pointer_cast<CFontResource>(it));
188 if (font->GetFont(file, strPath))
189 break;
193 // check if we already have this font file loaded (font object could differ only by color or style)
194 const std::string fontIdent =
195 StringUtils::Format("{}_{:f}_{:f}{}", strFilename, newSize, aspect, border ? "_border" : "");
197 CGUIFontTTF* pFontFile = GetFontFile(fontIdent);
198 if (!pFontFile)
200 pFontFile = CGUIFontTTF::CreateGUIFontTTF(fontIdent);
201 bool bFontLoaded = pFontFile->Load(strPath, newSize, aspect, 1.0f, border);
203 if (!bFontLoaded)
205 delete pFontFile;
207 // font could not be loaded - try Arial.ttf, which we distribute
208 if (strFilename != "arial.ttf")
210 CLog::Log(LOGERROR, "GUIFontManager::{}: Couldn't load font name: {}({}), trying arial.ttf",
211 __func__, strFontName, strFilename);
212 return LoadTTF(strFontName, "arial.ttf", textColor, shadowColor, iSize, iStyle, border,
213 lineSpacing, originalAspect);
215 CLog::Log(LOGERROR, "GUIFontManager::{}: Couldn't load font name:{} file:{}", __func__,
216 strFontName, strPath);
218 return nullptr;
221 m_vecFontFiles.emplace_back(pFontFile);
224 // font file is loaded, create our CGUIFont
225 CGUIFont* pNewFont = new CGUIFont(strFontName, iStyle, textColor, shadowColor, lineSpacing,
226 static_cast<float>(iSize), pFontFile);
227 m_vecFonts.emplace_back(pNewFont);
229 // Store the original TTF font info in case we need to reload it in a different resolution
230 OrigFontInfo fontInfo;
231 fontInfo.size = iSize;
232 fontInfo.aspect = originalAspect;
233 fontInfo.fontFilePath = strPath;
234 fontInfo.fileName = strFilename;
235 fontInfo.sourceRes = *sourceRes;
236 fontInfo.preserveAspect = preserveAspect;
237 fontInfo.border = border;
238 m_vecFontInfo.emplace_back(fontInfo);
240 return pNewFont;
243 bool GUIFontManager::OnMessage(CGUIMessage& message)
245 if (message.GetMessage() != GUI_MSG_NOTIFY_ALL)
246 return false;
248 if (message.GetParam1() == GUI_MSG_RENDERER_LOST)
250 m_canReload = false;
251 return true;
254 if (message.GetParam1() == GUI_MSG_RENDERER_RESET)
255 { // our device has been reset - we have to reload our ttf fonts, and send
256 // a message to controls that we have done so
257 ReloadTTFFonts();
258 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0,
259 GUI_MSG_WINDOW_RESIZE);
260 m_canReload = true;
261 return true;
264 if (message.GetParam1() == GUI_MSG_WINDOW_RESIZE)
265 { // we need to reload our fonts
266 if (m_canReload)
268 ReloadTTFFonts();
269 // no need to send a resize message, as this message will do the rounds
270 return true;
274 return false;
277 void GUIFontManager::ReloadTTFFonts(void)
279 CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
280 if (m_vecFonts.empty() || !winSystem)
281 return; // we haven't even loaded fonts in yet
283 for (size_t i = 0; i < m_vecFonts.size(); ++i)
285 const auto& font = m_vecFonts[i];
286 OrigFontInfo fontInfo = m_vecFontInfo[i];
288 float aspect = fontInfo.aspect;
289 float newSize = static_cast<float>(fontInfo.size);
290 std::string& strPath = fontInfo.fontFilePath;
291 std::string& strFilename = fontInfo.fileName;
293 RescaleFontSizeAndAspect(winSystem->GetGfxContext(), &newSize, &aspect, fontInfo.sourceRes,
294 fontInfo.preserveAspect);
296 const std::string fontIdent = StringUtils::Format("{}_{:f}_{:f}{}", strFilename, newSize,
297 aspect, fontInfo.border ? "_border" : "");
298 CGUIFontTTF* pFontFile = GetFontFile(fontIdent);
299 if (!pFontFile)
301 pFontFile = CGUIFontTTF::CreateGUIFontTTF(fontIdent);
302 if (!pFontFile || !pFontFile->Load(strPath, newSize, aspect, 1.0f, fontInfo.border))
304 delete pFontFile;
305 // font could not be loaded
306 CLog::Log(LOGERROR, "GUIFontManager::{}: Couldn't re-load font file: '{}'", __func__,
307 strPath);
308 return;
311 m_vecFontFiles.emplace_back(pFontFile);
314 font->SetFont(pFontFile);
318 void GUIFontManager::Unload(const std::string& strFontName)
320 for (auto iFont = m_vecFonts.begin(); iFont != m_vecFonts.end(); ++iFont)
322 if (StringUtils::EqualsNoCase((*iFont)->GetFontName(), strFontName))
324 m_vecFonts.erase(iFont);
325 return;
330 void GUIFontManager::FreeFontFile(CGUIFontTTF* pFont)
332 for (auto it = m_vecFontFiles.begin(); it != m_vecFontFiles.end(); ++it)
334 if (pFont == it->get())
336 m_vecFontFiles.erase(it);
337 return;
342 CGUIFontTTF* GUIFontManager::GetFontFile(const std::string& fontIdent)
344 for (const auto& it : m_vecFontFiles)
346 if (StringUtils::EqualsNoCase(it->GetFontIdent(), fontIdent))
347 return it.get();
350 return nullptr;
353 CGUIFont* GUIFontManager::GetFont(const std::string& strFontName, bool fallback /*= true*/)
355 for (const auto& it : m_vecFonts)
357 CGUIFont* pFont = it.get();
358 if (StringUtils::EqualsNoCase(pFont->GetFontName(), strFontName))
359 return pFont;
362 // fall back to "font13" if we have none
363 if (fallback && !strFontName.empty() && !StringUtils::EqualsNoCase(strFontName, "font13"))
364 return GetFont("font13");
366 return nullptr;
369 CGUIFont* GUIFontManager::GetDefaultFont(bool border)
371 // first find "font13" or "__defaultborder__"
372 size_t font13index = m_vecFonts.size();
373 CGUIFont* font13border = nullptr;
374 for (size_t i = 0; i < m_vecFonts.size(); i++)
376 CGUIFont* font = m_vecFonts[i].get();
377 if (font->GetFontName() == "font13")
378 font13index = i;
379 else if (font->GetFontName() == "__defaultborder__")
380 font13border = font;
382 // no "font13" means no default font is found - use the first font found.
383 if (font13index == m_vecFonts.size())
385 if (m_vecFonts.empty())
386 return nullptr;
388 font13index = 0;
391 if (border)
393 if (!font13border)
394 { // create it
395 const auto& font13 = m_vecFonts[font13index];
396 OrigFontInfo fontInfo = m_vecFontInfo[font13index];
397 font13border = LoadTTF("__defaultborder__", fontInfo.fileName, UTILS::COLOR::BLACK, 0,
398 fontInfo.size, font13->GetStyle(), true, 1.0f, fontInfo.aspect,
399 &fontInfo.sourceRes, fontInfo.preserveAspect);
401 return font13border;
404 return m_vecFonts[font13index].get();
407 void GUIFontManager::Clear()
409 m_vecFonts.clear();
410 m_vecFontFiles.clear();
411 m_vecFontInfo.clear();
413 #if defined(HAS_GL)
414 CGUIFontTTFGL::DestroyStaticVertexBuffers();
415 #endif
417 #if defined(HAS_GLES)
418 CGUIFontTTFGLES::DestroyStaticVertexBuffers();
419 #endif
422 bool GUIFontManager::LoadFontsFromFile(const std::string& fontsetFilePath,
423 const std::string& fontSet,
424 std::string& firstFontset)
426 CXBMCTinyXML xmlDoc;
427 if (LoadXMLData(fontsetFilePath, xmlDoc))
429 TiXmlElement* rootElement = xmlDoc.RootElement();
430 g_SkinInfo->ResolveIncludes(rootElement);
431 const TiXmlElement* fontsetElement = rootElement->FirstChildElement("fontset");
432 while (fontsetElement)
434 const char* idAttr = fontsetElement->Attribute("id");
435 if (idAttr)
437 // Take note of the first fontset available in case we can't load the fontset requested
438 if (firstFontset.empty())
439 firstFontset = idAttr;
441 if (StringUtils::EqualsNoCase(fontSet, idAttr))
443 // Found the requested fontset, so load the fonts and return
444 CLog::LogF(LOGINFO, "Loading <fontset> with name '{}' from '{}'", fontSet,
445 fontsetFilePath);
446 LoadFonts(fontsetElement->FirstChild("font"));
447 return true;
450 fontsetElement = fontsetElement->NextSiblingElement("fontset");
453 return false;
456 void GUIFontManager::LoadFonts(const std::string& fontSet)
458 std::string firstFontset;
459 // Try to load the fontset from Font.xml
460 const std::string fontsetFilePath = g_SkinInfo->GetSkinPath("Font.xml", &m_skinResolution);
461 if (LoadFontsFromFile(fontsetFilePath, fontSet, firstFontset))
462 return;
464 // If we got here, then the requested fontset was not found in the skin's Font.xml file
465 // Look at additional fontsets that are defined in .xml files in the skin's fonts directory
466 CFileItemList xmlFileItems;
467 CDirectory::GetDirectory(CSpecialProtocol::TranslatePath("special://skin/fonts"), xmlFileItems,
468 ".xml", DIR_FLAG_BYPASS_CACHE);
469 for (int i = 0; i < xmlFileItems.Size(); i++)
470 if (LoadFontsFromFile(xmlFileItems[i]->GetPath(), fontSet, firstFontset))
471 return;
473 // Requested fontset was not found, try the first
474 if (!firstFontset.empty())
476 CLog::LogF(LOGWARNING,
477 "Fontset with name '{}' was not found, "
478 "defaulting to first fontset '{}' ",
479 fontSet, firstFontset);
480 LoadFonts(firstFontset);
482 else
483 CLog::LogF(LOGERROR, "No valid <fontset> found in '{}' or in xml files in fonts directory",
484 fontsetFilePath);
487 void GUIFontManager::LoadFonts(const TiXmlNode* fontNode)
489 while (fontNode)
491 std::string fontName;
492 std::string fileName;
493 int iSize = 20;
494 float aspect = 1.0f;
495 float lineSpacing = 1.0f;
496 UTILS::COLOR::Color shadowColor = 0;
497 UTILS::COLOR::Color textColor = 0;
498 int iStyle = FONT_STYLE_NORMAL;
500 XMLUtils::GetString(fontNode, "name", fontName);
501 XMLUtils::GetInt(fontNode, "size", iSize);
502 XMLUtils::GetFloat(fontNode, "linespacing", lineSpacing);
503 XMLUtils::GetFloat(fontNode, "aspect", aspect);
504 CGUIControlFactory::GetColor(fontNode, "shadow", shadowColor);
505 CGUIControlFactory::GetColor(fontNode, "color", textColor);
506 XMLUtils::GetString(fontNode, "filename", fileName);
507 GetStyle(fontNode, iStyle);
509 if (!fontName.empty() && URIUtils::HasExtension(fileName, ".ttf"))
511 LoadTTF(fontName, fileName, textColor, shadowColor, iSize, iStyle, false, lineSpacing,
512 aspect);
514 fontNode = fontNode->NextSibling("font");
518 void GUIFontManager::GetStyle(const TiXmlNode* fontNode, int& iStyle)
520 std::string style;
521 iStyle = FONT_STYLE_NORMAL;
522 if (XMLUtils::GetString(fontNode, "style", style))
524 std::vector<std::string> styles = StringUtils::Tokenize(style, " ");
525 for (const std::string& i : styles)
527 if (i == "bold")
528 iStyle |= FONT_STYLE_BOLD;
529 else if (i == "italics")
530 iStyle |= FONT_STYLE_ITALICS;
531 else if (i == "bolditalics") // backward compatibility
532 iStyle |= (FONT_STYLE_BOLD | FONT_STYLE_ITALICS);
533 else if (i == "uppercase")
534 iStyle |= FONT_STYLE_UPPERCASE;
535 else if (i == "lowercase")
536 iStyle |= FONT_STYLE_LOWERCASE;
537 else if (i == "capitalize")
538 iStyle |= FONT_STYLE_CAPITALIZE;
539 else if (i == "lighten")
540 iStyle |= FONT_STYLE_LIGHT;
545 void GUIFontManager::SettingOptionsFontsFiller(const SettingConstPtr& setting,
546 std::vector<StringSettingOption>& list,
547 std::string& current,
548 void* data)
550 CFileItemList itemsRoot;
551 CFileItemList items;
553 // Find font files
554 XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::SYSTEM, itemsRoot,
555 UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
556 XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
557 XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::USER, items,
558 UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
559 XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
561 for (auto itItem = itemsRoot.rbegin(); itItem != itemsRoot.rend(); ++itItem)
562 items.AddFront(*itItem, 0);
564 for (const auto& item : items)
566 if (item->m_bIsFolder)
567 continue;
569 list.emplace_back(item->GetLabel(), item->GetLabel());
573 void GUIFontManager::Initialize()
575 std::unique_lock<CCriticalSection> lock(m_critSection);
576 LoadUserFonts();
579 void GUIFontManager::LoadUserFonts()
581 if (!XFILE::CDirectory::Exists(UTILS::FONT::FONTPATH::USER))
582 return;
584 CLog::LogF(LOGDEBUG, "Updating user fonts cache...");
585 CXBMCTinyXML xmlDoc;
586 std::string userFontCacheFilepath =
587 URIUtils::AddFileToFolder(UTILS::FONT::FONTPATH::USER, XML_FONTCACHE_FILENAME);
588 if (LoadXMLData(userFontCacheFilepath, xmlDoc))
590 // Load in cache the fonts metadata previously stored in the XML
591 TiXmlElement* pRootElement = xmlDoc.RootElement();
592 if (pRootElement)
594 const TiXmlNode* fontNode = pRootElement->FirstChild("font");
595 while (fontNode)
597 std::string filename;
598 XMLUtils::GetString(fontNode, "filename", filename);
600 std::set<std::string> familyNames;
601 for (const TiXmlElement* fnChildNode = fontNode->FirstChildElement("familyname");
602 fnChildNode; fnChildNode = fnChildNode->NextSiblingElement("familyname"))
604 familyNames.emplace(fnChildNode->GetText());
607 m_userFontsCache.emplace_back(filename, familyNames);
608 fontNode = fontNode->NextSibling("font");
613 bool isCacheChanged{false};
614 size_t previousCacheSize = m_userFontsCache.size();
615 CFileItemList dirItems;
616 // Get the current files list from user fonts folder
617 XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::USER, dirItems,
618 UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
619 XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
620 dirItems.SetFastLookup(true);
622 // Remove files that no longer exist from cache
623 auto it = m_userFontsCache.begin();
624 while (it != m_userFontsCache.end())
626 const std::string filePath = UTILS::FONT::FONTPATH::USER + (*it).m_filename;
627 if (!dirItems.Contains(filePath))
629 it = m_userFontsCache.erase(it);
631 else
633 auto item = dirItems.Get(filePath);
634 dirItems.Remove(item.get());
635 ++it;
638 isCacheChanged = previousCacheSize != m_userFontsCache.size();
639 previousCacheSize = m_userFontsCache.size();
641 // Add new files in cache and generate the metadata
642 //!@todo FIXME: this "for" loop should be replaced with the more performant
643 //! parallel execution std::for_each(std::execution::par, ...
644 //! to halving loading times of fonts list, maybe with C++17 with appropriate
645 //! fix to include parallel execution or future C++20
646 for (auto& item : dirItems)
648 std::string filepath = item->GetPath();
649 if (item->m_bIsFolder)
650 continue;
652 std::set<std::string> familyNames;
653 if (UTILS::FONT::GetFontFamilyNames(filepath, familyNames))
655 m_userFontsCache.emplace_back(item->GetLabel(), familyNames);
658 isCacheChanged = isCacheChanged || previousCacheSize != m_userFontsCache.size();
660 // If the cache is changed save an updated XML cache file
661 if (isCacheChanged)
663 CXBMCTinyXML xmlDoc;
664 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
665 xmlDoc.InsertEndChild(decl);
666 TiXmlElement xmlMainElement("fonts");
668 TiXmlNode* fontsNode = xmlDoc.InsertEndChild(xmlMainElement);
669 if (fontsNode)
671 for (const FontMetadata& fontMetadata : m_userFontsCache)
673 TiXmlElement fontElement("font");
674 TiXmlNode* fontNode = fontsNode->InsertEndChild(fontElement);
675 XMLUtils::SetString(fontNode, "filename", fontMetadata.m_filename);
676 for (const std::string& familyName : fontMetadata.m_familyNames)
678 XMLUtils::SetString(fontNode, "familyname", familyName);
681 if (!xmlDoc.SaveFile(userFontCacheFilepath))
682 CLog::LogF(LOGERROR, "Failed to save fonts cache file \"{}\"", userFontCacheFilepath);
684 else
686 CLog::LogF(LOGERROR, "Failed to create XML \"fonts\" node");
689 CLog::LogF(LOGDEBUG, "Updating user fonts cache... DONE");
692 std::vector<std::string> GUIFontManager::GetUserFontsFamilyNames()
694 // We ensure to have unique font family names and sorted alphabetically
695 // Duplicated family names can happens for example when a font have each style
696 // on different files
697 std::set<std::string, sortstringbyname> familyNames;
698 for (const FontMetadata& fontMetadata : m_userFontsCache)
700 for (const std::string& familyName : fontMetadata.m_familyNames)
702 familyNames.insert(familyName);
705 return std::vector<std::string>(familyNames.begin(), familyNames.end());