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.
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"
24 #include "GUIFontTTFGL.h"
27 #include "GUIFontTTFGLES.h"
30 #include "GUIControlFactory.h"
32 #include "ServiceBroker.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"
47 using namespace XFILE
;
48 using namespace ADDON
;
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
);
61 if (!xmlDoc
.LoadFile(filepath
))
63 CLog::LogF(LOGERROR
, "Couldn't load '{}'", filepath
);
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
);
74 } // unnamed namespace
77 GUIFontManager::GUIFontManager() = default;
79 GUIFontManager::~GUIFontManager()
84 void GUIFontManager::RescaleFontSizeAndAspect(CGraphicContext
& context
,
87 const RESOLUTION_INFO
& sourceRes
,
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
94 context
.GetGUIScaling(sourceRes
, scaleX
, scaleY
);
98 // font always displayed in the aspect specified by the aspect parameter
99 *aspect
/= context
.GetResInfo().fPixelRatio
;
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
;
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
);
120 strPath
= CSpecialProtocol::TranslatePathConvertCase(strPath
);
128 CGUIFont
* GUIFontManager::LoadTTF(const std::string
& strFontName
,
129 const std::string
& strFilename
,
130 UTILS::COLOR::Color textColor
,
131 UTILS::COLOR::Color shadowColor
,
137 const RESOLUTION_INFO
* sourceRes
,
140 CWinSystemBase
* const winSystem
= CServiceBroker::GetWinSystem();
144 "GUIFontManager::{}: Something tries to call function without an available GUI "
150 CGraphicContext
& context
= winSystem
->GetGfxContext();
152 float originalAspect
= aspect
;
154 //check if font already exists
155 CGUIFont
* pFont
= GetFont(strFontName
, false);
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
167 if (!CURL::IsFullPath(strFilename
))
169 strPath
= URIUtils::AddFileToFolder(context
.GetMediaDir(), "fonts", strFilename
);
172 strPath
= strFilename
;
175 strPath
= CSpecialProtocol::TranslatePathConvertCase(strPath
);
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
))
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
))
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
);
200 pFontFile
= CGUIFontTTF::CreateGUIFontTTF(fontIdent
);
201 bool bFontLoaded
= pFontFile
->Load(strPath
, newSize
, aspect
, 1.0f
, border
);
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
);
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
);
243 bool GUIFontManager::OnMessage(CGUIMessage
& message
)
245 if (message
.GetMessage() != GUI_MSG_NOTIFY_ALL
)
248 if (message
.GetParam1() == GUI_MSG_RENDERER_LOST
)
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
258 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL
, 0, 0,
259 GUI_MSG_WINDOW_RESIZE
);
264 if (message
.GetParam1() == GUI_MSG_WINDOW_RESIZE
)
265 { // we need to reload our fonts
269 // no need to send a resize message, as this message will do the rounds
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
);
301 pFontFile
= CGUIFontTTF::CreateGUIFontTTF(fontIdent
);
302 if (!pFontFile
|| !pFontFile
->Load(strPath
, newSize
, aspect
, 1.0f
, fontInfo
.border
))
305 // font could not be loaded
306 CLog::Log(LOGERROR
, "GUIFontManager::{}: Couldn't re-load font file: '{}'", __func__
,
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
);
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
);
342 CGUIFontTTF
* GUIFontManager::GetFontFile(const std::string
& fontIdent
)
344 for (const auto& it
: m_vecFontFiles
)
346 if (StringUtils::EqualsNoCase(it
->GetFontIdent(), fontIdent
))
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
))
362 // fall back to "font13" if we have none
363 if (fallback
&& !strFontName
.empty() && !StringUtils::EqualsNoCase(strFontName
, "font13"))
364 return GetFont("font13");
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")
379 else if (font
->GetFontName() == "__defaultborder__")
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())
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
);
404 return m_vecFonts
[font13index
].get();
407 void GUIFontManager::Clear()
410 m_vecFontFiles
.clear();
411 m_vecFontInfo
.clear();
414 CGUIFontTTFGL::DestroyStaticVertexBuffers();
417 #if defined(HAS_GLES)
418 CGUIFontTTFGLES::DestroyStaticVertexBuffers();
422 bool GUIFontManager::LoadFontsFromFile(const std::string
& fontsetFilePath
,
423 const std::string
& fontSet
,
424 std::string
& firstFontset
)
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");
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
,
446 LoadFonts(fontsetElement
->FirstChild("font"));
450 fontsetElement
= fontsetElement
->NextSiblingElement("fontset");
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
))
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
))
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
);
483 CLog::LogF(LOGERROR
, "No valid <fontset> found in '{}' or in xml files in fonts directory",
487 void GUIFontManager::LoadFonts(const TiXmlNode
* fontNode
)
491 std::string fontName
;
492 std::string fileName
;
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
,
514 fontNode
= fontNode
->NextSibling("font");
518 void GUIFontManager::GetStyle(const TiXmlNode
* fontNode
, int& iStyle
)
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
)
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
,
550 CFileItemList itemsRoot
;
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
)
569 list
.emplace_back(item
->GetLabel(), item
->GetLabel());
573 void GUIFontManager::Initialize()
575 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
579 void GUIFontManager::LoadUserFonts()
581 if (!XFILE::CDirectory::Exists(UTILS::FONT::FONTPATH::USER
))
584 CLog::LogF(LOGDEBUG
, "Updating user fonts cache...");
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();
594 const TiXmlNode
* fontNode
= pRootElement
->FirstChild("font");
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
);
633 auto item
= dirItems
.Get(filePath
);
634 dirItems
.Remove(item
.get());
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
)
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
664 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
665 xmlDoc
.InsertEndChild(decl
);
666 TiXmlElement
xmlMainElement("fonts");
668 TiXmlNode
* fontsNode
= xmlDoc
.InsertEndChild(xmlMainElement
);
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
);
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());