summaryrefslogtreecommitdiffstats
path: root/xbmc/guilib/GUIFontManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/guilib/GUIFontManager.cpp')
-rw-r--r--xbmc/guilib/GUIFontManager.cpp671
1 files changed, 671 insertions, 0 deletions
diff --git a/xbmc/guilib/GUIFontManager.cpp b/xbmc/guilib/GUIFontManager.cpp
new file mode 100644
index 0000000..605b9c8
--- /dev/null
+++ b/xbmc/guilib/GUIFontManager.cpp
@@ -0,0 +1,671 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIFontManager.h"
+
+#include "GUIComponent.h"
+#include "GUIFontTTF.h"
+#include "GUIWindowManager.h"
+#include "addons/AddonManager.h"
+#include "addons/FontResource.h"
+#include "addons/Skin.h"
+#include "addons/addoninfo/AddonType.h"
+#include "windowing/GraphicContext.h"
+
+#include <mutex>
+#if defined(HAS_GLES) || defined(HAS_GL)
+#include "GUIFontTTFGL.h"
+#endif
+#include "FileItem.h"
+#include "GUIControlFactory.h"
+#include "GUIFont.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "filesystem/Directory.h"
+#include "settings/lib/Setting.h"
+#include "settings/lib/SettingDefinitions.h"
+#include "utils/FileUtils.h"
+#include "utils/FontUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <set>
+
+#ifdef TARGET_POSIX
+#include "filesystem/SpecialProtocol.h"
+#endif
+
+using namespace ADDON;
+
+namespace
+{
+constexpr const char* XML_FONTCACHE_FILENAME = "fontcache.xml";
+
+bool LoadXMLData(const std::string& filepath, CXBMCTinyXML& xmlDoc)
+{
+ if (!CFileUtils::Exists(filepath))
+ {
+ CLog::LogF(LOGDEBUG, "Couldn't load '{}' the file don't exists", filepath);
+ return false;
+ }
+ if (!xmlDoc.LoadFile(filepath))
+ {
+ CLog::LogF(LOGERROR, "Couldn't load '{}'", filepath);
+ return false;
+ }
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+ if (!pRootElement || pRootElement->ValueStr() != "fonts")
+ {
+ CLog::LogF(LOGERROR, "Couldn't load '{}' XML content doesn't start with <fonts>", filepath);
+ return false;
+ }
+ return true;
+}
+} // unnamed namespace
+
+
+GUIFontManager::GUIFontManager() = default;
+
+GUIFontManager::~GUIFontManager()
+{
+ Clear();
+}
+
+void GUIFontManager::RescaleFontSizeAndAspect(CGraphicContext& context,
+ float* size,
+ float* aspect,
+ const RESOLUTION_INFO& sourceRes,
+ bool preserveAspect)
+{
+ // get the UI scaling constants so that we can scale our font sizes correctly
+ // as fonts aren't scaled at render time (due to aliasing) we must scale
+ // the size of the fonts before they are drawn to bitmaps
+ float scaleX, scaleY;
+ context.GetGUIScaling(sourceRes, scaleX, scaleY);
+
+ if (preserveAspect)
+ {
+ // font always displayed in the aspect specified by the aspect parameter
+ *aspect /= context.GetResInfo().fPixelRatio;
+ }
+ else
+ {
+ // font stretched like the rest of the UI, aspect parameter being the original aspect
+
+ // adjust aspect ratio
+ *aspect *= sourceRes.fPixelRatio;
+
+ *aspect *= scaleY / scaleX;
+ }
+
+ *size /= scaleY;
+}
+
+static bool CheckFont(std::string& strPath, const std::string& newPath, const std::string& filename)
+{
+ if (!CFileUtils::Exists(strPath))
+ {
+ strPath = URIUtils::AddFileToFolder(newPath, filename);
+#ifdef TARGET_POSIX
+ strPath = CSpecialProtocol::TranslatePathConvertCase(strPath);
+#endif
+ return false;
+ }
+
+ return true;
+}
+
+CGUIFont* GUIFontManager::LoadTTF(const std::string& strFontName,
+ const std::string& strFilename,
+ UTILS::COLOR::Color textColor,
+ UTILS::COLOR::Color shadowColor,
+ const int iSize,
+ const int iStyle,
+ bool border,
+ float lineSpacing,
+ float aspect,
+ const RESOLUTION_INFO* sourceRes,
+ bool preserveAspect)
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!winSystem)
+ {
+ CLog::Log(LOGFATAL,
+ "GUIFontManager::{}: Something tries to call function without an available GUI "
+ "window system",
+ __func__);
+ return nullptr;
+ }
+
+ CGraphicContext& context = winSystem->GetGfxContext();
+
+ float originalAspect = aspect;
+
+ //check if font already exists
+ CGUIFont* pFont = GetFont(strFontName, false);
+ if (pFont)
+ return pFont;
+
+ if (!sourceRes) // no source res specified, so assume the skin res
+ sourceRes = &m_skinResolution;
+
+ float newSize = static_cast<float>(iSize);
+ RescaleFontSizeAndAspect(context, &newSize, &aspect, *sourceRes, preserveAspect);
+
+ // First try to load the font from the skin
+ std::string strPath;
+ if (!CURL::IsFullPath(strFilename))
+ {
+ strPath = URIUtils::AddFileToFolder(context.GetMediaDir(), "fonts", strFilename);
+ }
+ else
+ strPath = strFilename;
+
+#ifdef TARGET_POSIX
+ strPath = CSpecialProtocol::TranslatePathConvertCase(strPath);
+#endif
+
+ // Check if the file exists, otherwise try loading it from the global media dir
+ std::string file = URIUtils::GetFileName(strFilename);
+ if (!CheckFont(strPath, "special://home/media/Fonts", file) &&
+ !CheckFont(strPath, "special://xbmc/media/Fonts", file))
+ {
+ VECADDONS addons;
+ CServiceBroker::GetAddonMgr().GetAddons(addons, AddonType::RESOURCE_FONT);
+ for (auto& it : addons)
+ {
+ std::shared_ptr<CFontResource> font(std::static_pointer_cast<CFontResource>(it));
+ if (font->GetFont(file, strPath))
+ break;
+ }
+ }
+
+ // check if we already have this font file loaded (font object could differ only by color or style)
+ const std::string fontIdent =
+ StringUtils::Format("{}_{:f}_{:f}{}", strFilename, newSize, aspect, border ? "_border" : "");
+
+ CGUIFontTTF* pFontFile = GetFontFile(fontIdent);
+ if (!pFontFile)
+ {
+ pFontFile = CGUIFontTTF::CreateGUIFontTTF(fontIdent);
+ bool bFontLoaded = pFontFile->Load(strPath, newSize, aspect, 1.0f, border);
+
+ if (!bFontLoaded)
+ {
+ delete pFontFile;
+
+ // font could not be loaded - try Arial.ttf, which we distribute
+ if (strFilename != "arial.ttf")
+ {
+ CLog::Log(LOGERROR, "GUIFontManager::{}: Couldn't load font name: {}({}), trying arial.ttf",
+ __func__, strFontName, strFilename);
+ return LoadTTF(strFontName, "arial.ttf", textColor, shadowColor, iSize, iStyle, border,
+ lineSpacing, originalAspect);
+ }
+ CLog::Log(LOGERROR, "GUIFontManager::{}: Couldn't load font name:{} file:{}", __func__,
+ strFontName, strPath);
+
+ return nullptr;
+ }
+
+ m_vecFontFiles.emplace_back(pFontFile);
+ }
+
+ // font file is loaded, create our CGUIFont
+ CGUIFont* pNewFont = new CGUIFont(strFontName, iStyle, textColor, shadowColor, lineSpacing,
+ static_cast<float>(iSize), pFontFile);
+ m_vecFonts.emplace_back(pNewFont);
+
+ // Store the original TTF font info in case we need to reload it in a different resolution
+ OrigFontInfo fontInfo;
+ fontInfo.size = iSize;
+ fontInfo.aspect = originalAspect;
+ fontInfo.fontFilePath = strPath;
+ fontInfo.fileName = strFilename;
+ fontInfo.sourceRes = *sourceRes;
+ fontInfo.preserveAspect = preserveAspect;
+ fontInfo.border = border;
+ m_vecFontInfo.emplace_back(fontInfo);
+
+ return pNewFont;
+}
+
+bool GUIFontManager::OnMessage(CGUIMessage& message)
+{
+ if (message.GetMessage() != GUI_MSG_NOTIFY_ALL)
+ return false;
+
+ if (message.GetParam1() == GUI_MSG_RENDERER_LOST)
+ {
+ m_canReload = false;
+ return true;
+ }
+
+ if (message.GetParam1() == GUI_MSG_RENDERER_RESET)
+ { // our device has been reset - we have to reload our ttf fonts, and send
+ // a message to controls that we have done so
+ ReloadTTFFonts();
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0,
+ GUI_MSG_WINDOW_RESIZE);
+ m_canReload = true;
+ return true;
+ }
+
+ if (message.GetParam1() == GUI_MSG_WINDOW_RESIZE)
+ { // we need to reload our fonts
+ if (m_canReload)
+ {
+ ReloadTTFFonts();
+ // no need to send a resize message, as this message will do the rounds
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void GUIFontManager::ReloadTTFFonts(void)
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (m_vecFonts.empty() || !winSystem)
+ return; // we haven't even loaded fonts in yet
+
+ for (size_t i = 0; i < m_vecFonts.size(); ++i)
+ {
+ const auto& font = m_vecFonts[i];
+ OrigFontInfo fontInfo = m_vecFontInfo[i];
+
+ float aspect = fontInfo.aspect;
+ float newSize = static_cast<float>(fontInfo.size);
+ std::string& strPath = fontInfo.fontFilePath;
+ std::string& strFilename = fontInfo.fileName;
+
+ RescaleFontSizeAndAspect(winSystem->GetGfxContext(), &newSize, &aspect, fontInfo.sourceRes,
+ fontInfo.preserveAspect);
+
+ const std::string fontIdent = StringUtils::Format("{}_{:f}_{:f}{}", strFilename, newSize,
+ aspect, fontInfo.border ? "_border" : "");
+ CGUIFontTTF* pFontFile = GetFontFile(fontIdent);
+ if (!pFontFile)
+ {
+ pFontFile = CGUIFontTTF::CreateGUIFontTTF(fontIdent);
+ if (!pFontFile || !pFontFile->Load(strPath, newSize, aspect, 1.0f, fontInfo.border))
+ {
+ delete pFontFile;
+ // font could not be loaded
+ CLog::Log(LOGERROR, "GUIFontManager::{}: Couldn't re-load font file: '{}'", __func__,
+ strPath);
+ return;
+ }
+
+ m_vecFontFiles.emplace_back(pFontFile);
+ }
+
+ font->SetFont(pFontFile);
+ }
+}
+
+void GUIFontManager::Unload(const std::string& strFontName)
+{
+ for (auto iFont = m_vecFonts.begin(); iFont != m_vecFonts.end(); ++iFont)
+ {
+ if (StringUtils::EqualsNoCase((*iFont)->GetFontName(), strFontName))
+ {
+ m_vecFonts.erase(iFont);
+ return;
+ }
+ }
+}
+
+void GUIFontManager::FreeFontFile(CGUIFontTTF* pFont)
+{
+ for (auto it = m_vecFontFiles.begin(); it != m_vecFontFiles.end(); ++it)
+ {
+ if (pFont == it->get())
+ {
+ m_vecFontFiles.erase(it);
+ return;
+ }
+ }
+}
+
+CGUIFontTTF* GUIFontManager::GetFontFile(const std::string& fontIdent)
+{
+ for (const auto& it : m_vecFontFiles)
+ {
+ if (StringUtils::EqualsNoCase(it->GetFontIdent(), fontIdent))
+ return it.get();
+ }
+
+ return nullptr;
+}
+
+CGUIFont* GUIFontManager::GetFont(const std::string& strFontName, bool fallback /*= true*/)
+{
+ for (const auto& it : m_vecFonts)
+ {
+ CGUIFont* pFont = it.get();
+ if (StringUtils::EqualsNoCase(pFont->GetFontName(), strFontName))
+ return pFont;
+ }
+
+ // fall back to "font13" if we have none
+ if (fallback && !strFontName.empty() && !StringUtils::EqualsNoCase(strFontName, "font13"))
+ return GetFont("font13");
+
+ return nullptr;
+}
+
+CGUIFont* GUIFontManager::GetDefaultFont(bool border)
+{
+ // first find "font13" or "__defaultborder__"
+ size_t font13index = m_vecFonts.size();
+ CGUIFont* font13border = nullptr;
+ for (size_t i = 0; i < m_vecFonts.size(); i++)
+ {
+ CGUIFont* font = m_vecFonts[i].get();
+ if (font->GetFontName() == "font13")
+ font13index = i;
+ else if (font->GetFontName() == "__defaultborder__")
+ font13border = font;
+ }
+ // no "font13" means no default font is found - use the first font found.
+ if (font13index == m_vecFonts.size())
+ {
+ if (m_vecFonts.empty())
+ return nullptr;
+
+ font13index = 0;
+ }
+
+ if (border)
+ {
+ if (!font13border)
+ { // create it
+ const auto& font13 = m_vecFonts[font13index];
+ OrigFontInfo fontInfo = m_vecFontInfo[font13index];
+ font13border = LoadTTF("__defaultborder__", fontInfo.fileName, UTILS::COLOR::BLACK, 0,
+ fontInfo.size, font13->GetStyle(), true, 1.0f, fontInfo.aspect,
+ &fontInfo.sourceRes, fontInfo.preserveAspect);
+ }
+ return font13border;
+ }
+
+ return m_vecFonts[font13index].get();
+}
+
+void GUIFontManager::Clear()
+{
+ m_vecFonts.clear();
+ m_vecFontFiles.clear();
+ m_vecFontInfo.clear();
+
+#if defined(HAS_GLES) || defined(HAS_GL)
+ CGUIFontTTFGL::DestroyStaticVertexBuffers();
+#endif
+}
+
+void GUIFontManager::LoadFonts(const std::string& fontSet)
+{
+ // Get the file to load fonts from:
+ const std::string filePath = g_SkinInfo->GetSkinPath("Font.xml", &m_skinResolution);
+ CLog::LogF(LOGINFO, "Loading fonts from '{}'", filePath);
+
+ CXBMCTinyXML xmlDoc;
+ if (!LoadXMLData(filePath, xmlDoc))
+ return;
+
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+ // Resolve includes in Font.xml
+ g_SkinInfo->ResolveIncludes(pRootElement);
+ // take note of the first font available in case we can't load the one specified
+ std::string firstFont;
+ const TiXmlElement* pChild = pRootElement->FirstChildElement("fontset");
+ while (pChild)
+ {
+ const char* idAttr = pChild->Attribute("id");
+ if (idAttr)
+ {
+ if (firstFont.empty())
+ firstFont = idAttr;
+
+ if (StringUtils::EqualsNoCase(fontSet, idAttr))
+ {
+ LoadFonts(pChild->FirstChild("font"));
+ return;
+ }
+ }
+ pChild = pChild->NextSiblingElement("fontset");
+ }
+
+ // no fontset was loaded, try the first
+ if (!firstFont.empty())
+ {
+ CLog::Log(LOGWARNING,
+ "GUIFontManager::{}: File doesn't have <fontset> with name '{}', defaulting to first "
+ "fontset",
+ __func__, fontSet);
+ LoadFonts(firstFont);
+ }
+ else
+ CLog::LogF(LOGERROR, "File '{}' doesn't have a valid <fontset>", filePath);
+}
+
+void GUIFontManager::LoadFonts(const TiXmlNode* fontNode)
+{
+ while (fontNode)
+ {
+ std::string fontName;
+ std::string fileName;
+ int iSize = 20;
+ float aspect = 1.0f;
+ float lineSpacing = 1.0f;
+ UTILS::COLOR::Color shadowColor = 0;
+ UTILS::COLOR::Color textColor = 0;
+ int iStyle = FONT_STYLE_NORMAL;
+
+ XMLUtils::GetString(fontNode, "name", fontName);
+ XMLUtils::GetInt(fontNode, "size", iSize);
+ XMLUtils::GetFloat(fontNode, "linespacing", lineSpacing);
+ XMLUtils::GetFloat(fontNode, "aspect", aspect);
+ CGUIControlFactory::GetColor(fontNode, "shadow", shadowColor);
+ CGUIControlFactory::GetColor(fontNode, "color", textColor);
+ XMLUtils::GetString(fontNode, "filename", fileName);
+ GetStyle(fontNode, iStyle);
+
+ if (!fontName.empty() && URIUtils::HasExtension(fileName, ".ttf"))
+ {
+ //! @todo Why do we tolower() this shit?
+ std::string strFontFileName = fileName;
+ StringUtils::ToLower(strFontFileName);
+ LoadTTF(fontName, strFontFileName, textColor, shadowColor, iSize, iStyle, false, lineSpacing,
+ aspect);
+ }
+ fontNode = fontNode->NextSibling("font");
+ }
+}
+
+void GUIFontManager::GetStyle(const TiXmlNode* fontNode, int& iStyle)
+{
+ std::string style;
+ iStyle = FONT_STYLE_NORMAL;
+ if (XMLUtils::GetString(fontNode, "style", style))
+ {
+ std::vector<std::string> styles = StringUtils::Tokenize(style, " ");
+ for (const std::string& i : styles)
+ {
+ if (i == "bold")
+ iStyle |= FONT_STYLE_BOLD;
+ else if (i == "italics")
+ iStyle |= FONT_STYLE_ITALICS;
+ else if (i == "bolditalics") // backward compatibility
+ iStyle |= (FONT_STYLE_BOLD | FONT_STYLE_ITALICS);
+ else if (i == "uppercase")
+ iStyle |= FONT_STYLE_UPPERCASE;
+ else if (i == "lowercase")
+ iStyle |= FONT_STYLE_LOWERCASE;
+ else if (i == "capitalize")
+ iStyle |= FONT_STYLE_CAPITALIZE;
+ else if (i == "lighten")
+ iStyle |= FONT_STYLE_LIGHT;
+ }
+ }
+}
+
+void GUIFontManager::SettingOptionsFontsFiller(const SettingConstPtr& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data)
+{
+ CFileItemList itemsRoot;
+ CFileItemList items;
+
+ // Find font files
+ XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::SYSTEM, itemsRoot,
+ UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
+ XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
+ XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::USER, items,
+ UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
+ XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
+
+ for (auto itItem = itemsRoot.rbegin(); itItem != itemsRoot.rend(); ++itItem)
+ items.AddFront(*itItem, 0);
+
+ for (const auto& item : items)
+ {
+ if (item->m_bIsFolder)
+ continue;
+
+ list.emplace_back(item->GetLabel(), item->GetLabel());
+ }
+}
+
+void GUIFontManager::Initialize()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ LoadUserFonts();
+}
+
+void GUIFontManager::LoadUserFonts()
+{
+ if (!XFILE::CDirectory::Exists(UTILS::FONT::FONTPATH::USER))
+ return;
+
+ CLog::LogF(LOGDEBUG, "Updating user fonts cache...");
+ CXBMCTinyXML xmlDoc;
+ std::string userFontCacheFilepath =
+ URIUtils::AddFileToFolder(UTILS::FONT::FONTPATH::USER, XML_FONTCACHE_FILENAME);
+ if (LoadXMLData(userFontCacheFilepath, xmlDoc))
+ {
+ // Load in cache the fonts metadata previously stored in the XML
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+ if (pRootElement)
+ {
+ const TiXmlNode* fontNode = pRootElement->FirstChild("font");
+ while (fontNode)
+ {
+ std::string filename;
+ std::string familyName;
+ XMLUtils::GetString(fontNode, "filename", filename);
+ XMLUtils::GetString(fontNode, "familyname", familyName);
+ m_userFontsCache.emplace_back(filename, familyName);
+ fontNode = fontNode->NextSibling("font");
+ }
+ }
+ }
+
+ bool isCacheChanged{false};
+ size_t previousCacheSize = m_userFontsCache.size();
+ CFileItemList dirItems;
+ // Get the current files list from user fonts folder
+ XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::USER, dirItems,
+ UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
+ XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
+ dirItems.SetFastLookup(true);
+
+ // Remove files that no longer exist from cache
+ auto it = m_userFontsCache.begin();
+ while (it != m_userFontsCache.end())
+ {
+ const std::string filePath = UTILS::FONT::FONTPATH::USER + (*it).m_filename;
+ if (!dirItems.Contains(filePath))
+ {
+ it = m_userFontsCache.erase(it);
+ }
+ else
+ {
+ auto item = dirItems.Get(filePath);
+ dirItems.Remove(item.get());
+ ++it;
+ }
+ }
+ isCacheChanged = previousCacheSize != m_userFontsCache.size();
+ previousCacheSize = m_userFontsCache.size();
+
+ // Add new files in cache and generate the metadata
+ //!@todo FIXME: this "for" loop should be replaced with the more performant
+ //! parallel execution std::for_each(std::execution::par, ...
+ //! to halving loading times of fonts list, maybe with C++17 with appropriate
+ //! fix to include parallel execution or future C++20
+ for (auto& item : dirItems)
+ {
+ std::string filepath = item->GetPath();
+ if (item->m_bIsFolder)
+ continue;
+
+ std::string familyName = UTILS::FONT::GetFontFamily(filepath);
+ if (!familyName.empty())
+ {
+ m_userFontsCache.emplace_back(item->GetLabel(), familyName);
+ }
+ }
+ isCacheChanged = isCacheChanged || previousCacheSize != m_userFontsCache.size();
+
+ // If the cache is changed save an updated XML cache file
+ if (isCacheChanged)
+ {
+ CXBMCTinyXML xmlDoc;
+ TiXmlDeclaration decl("1.0", "UTF-8", "yes");
+ xmlDoc.InsertEndChild(decl);
+ TiXmlElement xmlMainElement("fonts");
+ TiXmlNode* fontsNode = xmlDoc.InsertEndChild(xmlMainElement);
+ if (fontsNode)
+ {
+ for (auto& fontMetadata : m_userFontsCache)
+ {
+ TiXmlElement fontElement("font");
+ TiXmlNode* fontNode = fontsNode->InsertEndChild(fontElement);
+ XMLUtils::SetString(fontNode, "filename", fontMetadata.m_filename);
+ XMLUtils::SetString(fontNode, "familyname", fontMetadata.m_familyName);
+ }
+ if (!xmlDoc.SaveFile(userFontCacheFilepath))
+ CLog::LogF(LOGERROR, "Failed to save fonts cache file '{}'", userFontCacheFilepath);
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Failed to create XML 'fonts' node");
+ }
+ }
+ CLog::LogF(LOGDEBUG, "Updating user fonts cache... DONE");
+}
+
+std::vector<std::string> GUIFontManager::GetUserFontsFamilyNames()
+{
+ // We ensure to have unique font family names and sorted alphabetically
+ // Duplicated family names can happens for example when a font have each style
+ // on different files
+ std::set<std::string, sortstringbyname> familyNames;
+ for (auto& fontMetadata : m_userFontsCache)
+ {
+ familyNames.insert(fontMetadata.m_familyName);
+ }
+ return std::vector<std::string>(familyNames.begin(), familyNames.end());
+}