diff options
24 files changed, 663 insertions, 265 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 40c0773c28..cc523d9101 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -2695,6 +2695,7 @@ msgid "Date added" msgstr "" #: xbmc/playlists/SmartPlaylist.cpp +#: xbmc/settings/SubtitlesSettings.cpp msgctxt "#571" msgid "Default" msgstr "" @@ -22983,3 +22984,9 @@ msgstr "" msgctxt "#39179" msgid "The time in seconds for the video OSD to be automatically closed" msgstr "" + +#. Progress text on splash screen, to build the font cache +#: xbmc/Application.cpp +msgctxt "#39180" +msgid "Building font cache in progress - please wait" +msgstr "" diff --git a/system/settings/settings.xml b/system/settings/settings.xml index 47940fca91..36b2f7fa47 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -517,26 +517,23 @@ </constraints> <control type="spinner" format="integer" delayed="true"/> </setting> - <setting id="subtitles.font" type="string" label="14089" help="36185"> + <setting id="subtitles.fontname" type="string" label="14089" help="36185"> <level>1</level> - <default>arial.ttf</default> + <default>DEFAULT</default> <constraints> - <options>fonts</options> + <options>subtitlesfonts</options> </constraints> <control type="list" format="string" /> </setting> - <setting id="subtitles.charset" type="string" parent="subtitles.font" label="735" help="36189"> + <setting id="subtitles.charset" type="string" parent="subtitles.fontname" label="735" help="36189"> <level>1</level> <default>DEFAULT</default> <constraints> <options>charsets</options> </constraints> - <dependencies> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> - </dependencies> <control type="list" format="string" /> </setting> - <setting id="subtitles.fontsize" type="integer" parent="subtitles.font" label="289" help="36186"> + <setting id="subtitles.fontsize" type="integer" parent="subtitles.fontname" label="289" help="36186"> <level>3</level> <default>42</default> <constraints> @@ -545,11 +542,11 @@ <maximum>74</maximum> </constraints> <dependencies> - <dependency type="update" setting="subtitles.font" /> + <dependency type="update" setting="subtitles.fontname" /> </dependencies> <control type="list" format="string" /> </setting> - <setting id="subtitles.style" type="integer" parent="subtitles.font" label="736" help="36187"> + <setting id="subtitles.style" type="integer" parent="subtitles.fontname" label="736" help="36187"> <level>3</level> <default>0</default> <!-- FONT_STYLE_NORMAL --> <constraints> @@ -560,62 +557,40 @@ <option label="741">3</option> <!-- FONT_STYLE_BOLD | FONT_STYLE_ITALICS --> </options> </constraints> - <dependencies> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> - </dependencies> <control type="list" format="string" /> </setting> - <setting id="subtitles.colorpick" type="string" parent="subtitles.font" label="737" help="36188"> + <setting id="subtitles.colorpick" type="string" parent="subtitles.fontname" label="737" help="36188"> <level>3</level> <default>FFFFFFFF</default> <!-- White --> - <dependencies> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> - </dependencies> <control type="colorbutton" /> </setting> - <setting id="subtitles.opacity" type="integer" parent="subtitles.font" label="752" help="36295"> + <setting id="subtitles.opacity" type="integer" parent="subtitles.fontname" label="752" help="36295"> <level>3</level> <default>100</default> - <dependencies> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> - </dependencies> <control type="slider" format="percentage" range="0,100" /> </setting> - <setting id="subtitles.bordersize" type="integer" parent="subtitles.font" label="39159"> + <setting id="subtitles.bordersize" type="integer" parent="subtitles.fontname" label="39159"> <level>3</level> <default>30</default> <dependencies> - <dependency type="enable"> - <and> - <condition on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font"/> - <condition setting="subtitles.backgroundtype" operator="!is">2</condition> - </and> - </dependency> + <dependency type="enable" setting="subtitles.backgroundtype" operator="!is">2</dependency> </dependencies> <control type="slider" format="percentage" range="0,100" /> </setting> - <setting id="subtitles.bordercolorpick" type="string" parent="subtitles.font" label="39160"> + <setting id="subtitles.bordercolorpick" type="string" parent="subtitles.fontname" label="39160"> <level>3</level> <default>FF000000</default> <!-- Black --> <dependencies> - <dependency type="enable"> - <and> - <condition on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font"/> - <condition setting="subtitles.backgroundtype" operator="!is">2</condition> - </and> - </dependency> + <dependency type="enable" setting="subtitles.backgroundtype" operator="!is">2</dependency> </dependencies> <control type="colorbutton" /> </setting> - <setting id="subtitles.blur" type="integer" parent="subtitles.font" label="39173"> + <setting id="subtitles.blur" type="integer" parent="subtitles.fontname" label="39173"> <level>3</level> <default>0</default> - <dependencies> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> - </dependencies> <control type="slider" format="percentage" range="0,100" /> </setting> - <setting id="subtitles.backgroundtype" type="integer" parent="subtitles.font" label="39165" help="39169"> + <setting id="subtitles.backgroundtype" type="integer" parent="subtitles.fontname" label="39165" help="39169"> <level>3</level> <default>0</default> <constraints> @@ -626,9 +601,6 @@ <option label="39168">3</option> <!-- SUBTITLE_BACKGROUNDTYPE_SQUAREBOX --> </options> </constraints> - <dependencies> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> - </dependencies> <control type="list" format="integer" /> </setting> <setting id="subtitles.bgcolorpick" type="string" parent="subtitles.backgroundtype" label="745" help="36228"> @@ -641,7 +613,6 @@ <condition setting="subtitles.backgroundtype">3</condition> </or> </dependency> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> </dependencies> <control type="colorbutton" /> </setting> @@ -655,7 +626,6 @@ <condition setting="subtitles.backgroundtype">3</condition> </or> </dependency> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> </dependencies> <control type="slider" format="percentage" range="0,100" /> </setting> @@ -669,7 +639,6 @@ <condition setting="subtitles.backgroundtype">2</condition> </or> </dependency> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> </dependencies> <control type="colorbutton" /> </setting> @@ -683,7 +652,6 @@ <condition setting="subtitles.backgroundtype">2</condition> </or> </dependency> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> </dependencies> <control type="slider" format="percentage" range="0,100" /> </setting> @@ -697,7 +665,6 @@ <condition setting="subtitles.backgroundtype">2</condition> </or> </dependency> - <dependency type="enable" on="property" name="HasSubtitlesFontExtensions" setting="subtitles.font" /> </dependencies> <control type="slider" format="percentage" range="0,100" /> </setting> diff --git a/xbmc/Application.cpp b/xbmc/Application.cpp index 4430a8dafc..27e17cfa42 100644 --- a/xbmc/Application.cpp +++ b/xbmc/Application.cpp @@ -640,6 +640,29 @@ bool CApplication::Initialize() } CServiceBroker::GetRenderSystem()->ShowSplash(""); + // Initialize GUI font manager to build/update fonts cache + //! @todo Move GUIFontManager into service broker and drop the global reference + event.Reset(); + GUIFontManager& guiFontManager = g_fontManager; + CJobManager::GetInstance().Submit([&guiFontManager, &event]() { + guiFontManager.Initialize(); + event.Set(); + }); + localizedStr = g_localizeStrings.Get(39180); + iDots = 1; + while (!event.Wait(1000ms)) + { + if (g_fontManager.IsUpdating()) + CServiceBroker::GetRenderSystem()->ShowSplash(std::string(iDots, ' ') + localizedStr + + std::string(iDots, '.')); + + if (iDots == 3) + iDots = 1; + else + ++iDots; + } + CServiceBroker::GetRenderSystem()->ShowSplash(""); + // GUI depends on seek handler m_appPlayer.GetSeekHandler().Configure(); diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp index 23f4c0428a..86aa9878d5 100644 --- a/xbmc/Util.cpp +++ b/xbmc/Util.cpp @@ -70,6 +70,7 @@ #include "settings/SettingsComponent.h" #include "utils/Digest.h" #include "utils/FileExtensionProvider.h" +#include "utils/FontUtils.h" #include "utils/LangCodeExpander.h" #include "utils/StringUtils.h" #include "utils/TimeUtils.h" @@ -677,26 +678,6 @@ void CUtil::ClearSubtitles() } } -void CUtil::ClearTempFonts() -{ - const std::string searchPath = "special://home/media/Fonts/"; - - if (!CDirectory::Exists(searchPath)) - return; - - CFileItemList items; - CDirectory::GetDirectory(searchPath, items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_BYPASS_CACHE | DIR_FLAG_GET_HIDDEN); - - for (const auto &item : items) - { - if (item->m_bIsFolder) - continue; - - if (StringUtils::StartsWithNoCase(URIUtils::GetFileName(item->GetPath()), "tmp.font.")) - CFile::Delete(item->GetPath()); - } -} - int64_t CUtil::ToInt64(uint32_t high, uint32_t low) { int64_t n; @@ -1023,11 +1004,6 @@ std::string CUtil::ValidatePath(const std::string &path, bool bFixDoubleSlashes return result; } -bool CUtil::IsSupportedFontExtension(const std::string& fileName) -{ - return URIUtils::HasExtension(fileName, ".ttf|.otf"); -} - void CUtil::SplitExecFunction(const std::string &execString, std::string &function, std::vector<std::string> ¶meters) { std::string paramString; diff --git a/xbmc/Util.h b/xbmc/Util.h index 6c7166f733..07c238c58e 100644 --- a/xbmc/Util.h +++ b/xbmc/Util.h @@ -64,7 +64,6 @@ public: static bool GetDirectoryName(const std::string& strFileName, std::string& strDescription); static void GetDVDDriveIcon(const std::string& strPath, std::string& strIcon); static void RemoveTempFiles(); - static void ClearTempFonts(); static void ClearSubtitles(); static void ScanForExternalSubtitles(const std::string& strMovie, std::vector<std::string>& vecSubtitles ); diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index f957a9df76..ddbd3988ca 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -25,6 +25,7 @@ #include "settings/SettingsComponent.h" #include "threads/SingleLock.h" #include "threads/SystemClock.h" +#include "utils/FontUtils.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/XTimeUtils.h" @@ -1748,39 +1749,40 @@ CDemuxStream* CDVDDemuxFFmpeg::AddStream(int streamIdx) } case AVMEDIA_TYPE_ATTACHMENT: { - // mkv attachments. Only bothering with fonts for now. + // MKV attachments. Only bothering with fonts for now. AVDictionaryEntry* attachmentMimetype = av_dict_get(pStream->metadata, "mimetype", nullptr, 0); if (pStream->codecpar->codec_id == AV_CODEC_ID_TTF || pStream->codecpar->codec_id == AV_CODEC_ID_OTF || AttachmentIsFont(attachmentMimetype)) { - std::string fileName = "special://home/media/Fonts/"; - XFILE::CDirectory::Create(fileName); + // Temporary fonts are extracted to the temporary fonts path + //! @todo: temporary font file management should be completely + //! removed, by sending font data to the subtitle renderer and + //! using libass ass_add_font to add the fonts directly in memory. + std::string filePath{UTILS::FONT::FONTPATH::TEMP}; + XFILE::CDirectory::Create(filePath); + AVDictionaryEntry* nameTag = av_dict_get(pStream->metadata, "filename", NULL, 0); - if (!nameTag) - { - CLog::Log(LOGERROR, "{}: TTF attachment has no name", __FUNCTION__); - } - else + if (nameTag) { - // Note: libass only supports a single font directory to look for additional fonts - // (c.f. ass_set_fonts_dir). To support both user defined fonts (those placed in - // special://home/media/Fonts/) and fonts extracted by the demuxer, make it extract - // fonts to the user directory with a known, easy to identify, prefix (tmp.font.*). - fileName += "tmp.font." + CUtil::MakeLegalFileName(nameTag->value, LEGAL_WIN32_COMPAT); + filePath += CUtil::MakeLegalFileName(nameTag->value, LEGAL_WIN32_COMPAT); XFILE::CFile file; - if (pStream->codecpar->extradata && file.OpenForWrite(fileName)) + if (pStream->codecpar->extradata && file.OpenForWrite(filePath)) { if (file.Write(pStream->codecpar->extradata, pStream->codecpar->extradata_size) != pStream->codecpar->extradata_size) { file.Close(); - XFILE::CFile::Delete(fileName); - CLog::Log(LOGDEBUG, "{}: Error saving font file \"{}\"", __FUNCTION__, fileName); + XFILE::CFile::Delete(filePath); + CLog::LogF(LOGDEBUG, "Error saving font file \"{}\"", filePath); } } } + else + { + CLog::LogF(LOGERROR, "Attached font has no name"); + } } stream = new CDemuxStream(); stream->type = STREAM_NONE; diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp index 2ed41ffbdf..d96340bb5f 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.cpp @@ -8,13 +8,18 @@ #include "DVDSubtitlesLibass.h" +#include "FileItem.h" #include "ServiceBroker.h" +#include "Util.h" #include "cores/VideoPlayer/Interface/TimingConstants.h" +#include "filesystem/Directory.h" #include "filesystem/File.h" #include "filesystem/SpecialProtocol.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" +#include "settings/SubtitlesSettings.h" #include "threads/SingleLock.h" +#include "utils/FontUtils.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/log.h" @@ -32,27 +37,6 @@ constexpr int ASS_BORDER_STYLE_SQUARE_BOX = 4; // Square box + outline constexpr int ASS_FONT_ENCODING_AUTO = -1; -// Directory where user defined fonts are located (and where mkv fonts are extracted to) -constexpr const char* userFontPath = "special://home/media/Fonts/"; -// Directory where Kodi bundled fonts (default ones like Arial or Teletext) are located -constexpr const char* systemFontPath = "special://xbmc/media/Fonts/"; - -std::string GetDefaultFontPath(std::string& font) -{ - constexpr std::array<const char*, 2> fontSources{userFontPath, systemFontPath}; - - for (const auto& path : fontSources) - { - auto fontPath = URIUtils::AddFileToFolder(path, font); - if (XFILE::CFile::Exists(fontPath)) - { - return CSpecialProtocol::TranslatePath(fontPath).c_str(); - } - } - CLog::Log(LOGERROR, "CDVDSubtitlesLibass: Could not find font {} in font sources", font); - return ""; -} - // Convert RGB/ARGB to RGBA by also applying the opacity value COLOR::Color ConvColor(COLOR::Color argbColor, int opacity = 100) { @@ -109,9 +93,58 @@ void CDVDSubtitlesLibass::Configure() ass_set_margins(m_renderer, 0, 0, 0, 0); ass_set_use_margins(m_renderer, 0); - // libass uses fontconfig (system lib) by default in some platforms (e.g. linux/android) or as - // a fallback for all platforms. It is not wrapped so translate the path before calling into libass - ass_set_fonts_dir(m_library, CSpecialProtocol::TranslatePath(userFontPath).c_str()); + // Libass uses system font provider (like fontconfig) by default in some + // platforms (e.g. linux/windows), on some other systems like android the + // font provider is currenlty not supported, then an user can add his + // additionals fonts only by using the user fonts folder. + ass_set_fonts_dir(m_library, + CSpecialProtocol::TranslatePath(UTILS::FONT::FONTPATH::USER).c_str()); + + // Load additional fonts into Libass memory + CFileItemList items; + // Get fonts from system directory + XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::SYSTEM, items, + UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, + XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO); + // Get temporary fonts + XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::TEMP, items, + UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, + XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO); + for (const auto& item : items) + { + if (item->m_bIsFolder) + continue; + const std::string filepath = item->GetPath(); + const std::string fileName = item->GetLabel(); + std::vector<uint8_t> buffer; + if (XFILE::CFile().LoadFile(filepath, buffer) <= 0) + { + CLog::LogF(LOGERROR, "Failed to load file {}", filepath); + continue; + } +#if LIBASS_VERSION >= 0x01501000 + ass_add_font(m_library, fileName.c_str(), reinterpret_cast<const char*>(buffer.data()), + static_cast<int>(buffer.size())); +#else + ass_add_font(m_library, const_cast<char*>(fileName.c_str()), + reinterpret_cast<char*>(buffer.data()), static_cast<int>(buffer.size())); +#endif + if (StringUtils::CompareNoCase(fileName, FONT::FONT_DEFAULT_FILENAME) == 0) + { + m_defaultFontFamilyName = FONT::GetFontFamily(buffer); + } + } + if (m_defaultFontFamilyName.empty()) + { + CLog::LogF(LOGERROR, + "The application font {} is missing. The default subtitle font cannot be set.", + FONT::FONT_DEFAULT_FILENAME); + } + + ass_set_fonts(m_renderer, + UTILS::FONT::FONTPATH::GetSystemFontPath(FONT::FONT_DEFAULT_FILENAME).c_str(), + m_defaultFontFamilyName.c_str(), ASS_FONTPROVIDER_AUTODETECT, nullptr, 1); + ass_set_font_scale(m_renderer, 1); // Extract font must be set before loading ASS/SSA data, @@ -277,14 +310,13 @@ void CDVDSubtitlesLibass::ApplyStyle(const std::shared_ptr<struct KODI::SUBTITLE return; } - ConfigureFont((m_subtitleType == NATIVE && subStyle->assOverrideFont), subStyle->fontName); - // ASS_Style is a POD struct need to be initialized with {} ASS_Style defaultStyle{}; ASS_Style* style = nullptr; if (m_subtitleType == ADAPTED || - (m_subtitleType == NATIVE && subStyle->assOverrideStyles != OverrideStyles::DISABLED)) + (m_subtitleType == NATIVE && + (subStyle->assOverrideStyles != OverrideStyles::DISABLED || subStyle->assOverrideFont))) { m_currentDefaultStyleId = m_defaultKodiStyleId; @@ -297,6 +329,7 @@ void CDVDSubtitlesLibass::ApplyStyle(const std::shared_ptr<struct KODI::SUBTITLE style = &m_track->styles[m_currentDefaultStyleId]; } + free(style->Name); style->Name = strdup("KodiDefault"); // Calculate the scale (influence ASS style properties) @@ -304,7 +337,8 @@ void CDVDSubtitlesLibass::ApplyStyle(const std::shared_ptr<struct KODI::SUBTITLE int playResY; if (m_subtitleType == NATIVE && (subStyle->assOverrideStyles == OverrideStyles::STYLES || - subStyle->assOverrideStyles == OverrideStyles::STYLES_POSITIONS)) + subStyle->assOverrideStyles == OverrideStyles::STYLES_POSITIONS || + subStyle->assOverrideFont)) { // With styles overridden the PlayResY will be changed to 288 playResY = 288; @@ -316,105 +350,106 @@ void CDVDSubtitlesLibass::ApplyStyle(const std::shared_ptr<struct KODI::SUBTITLE } // It is mandatory set the FontName, the text is case sensitive - style->FontName = strdup(subStyle->fontName.c_str()); - - if (m_subtitleType != NATIVE || subStyle->assOverrideStyles != OverrideStyles::POSITIONS) + free(style->FontName); + if (subStyle->fontName == FONT_DEFAULT_FAMILYNAME) + style->FontName = strdup(m_defaultFontFamilyName.c_str()); + else + style->FontName = strdup(subStyle->fontName.c_str()); + + // Configure the font properties + // FIXME: The font size need to be scaled to be shown in right PT size + style->FontSize = (subStyle->fontSize / 720) * playResY; + // Modifies the width/height of the font (1 = 100%) + style->ScaleX = 1.0; + style->ScaleY = 1.0; + // Extra space between characters causes the underlined + // text line to become more discontinuous (test on LibAss 15.1) + style->Spacing = 0; + + // Set automatic paragraph direction (not VSFilter-compatible) + // to fix wrong RTL text direction when there are unicode chars + style->Encoding = ASS_FONT_ENCODING_AUTO; + + bool isFontBold = + (subStyle->fontStyle == FontStyle::BOLD || subStyle->fontStyle == FontStyle::BOLD_ITALIC); + bool isFontItalic = + (subStyle->fontStyle == FontStyle::ITALIC || subStyle->fontStyle == FontStyle::BOLD_ITALIC); + style->Bold = isFontBold * -1; + style->Italic = isFontItalic * -1; + + // Compute the font color, depending on the opacity + COLOR::Color subColor = ConvColor(subStyle->fontColor, subStyle->fontOpacity); + // Set default subtitles color + style->PrimaryColour = subColor; + // Set SecondaryColour may be used to prevent an onscreen collision + style->SecondaryColour = subColor; + + // Configure the effects + double lineSpacing = 0.0; + if (subStyle->borderStyle == BorderStyle::OUTLINE || + subStyle->borderStyle == BorderStyle::OUTLINE_NO_SHADOW) { - // Configure the font properties - // FIXME: The font size need to be scaled to be shown in right PT size - style->FontSize = (subStyle->fontSize / 720) * playResY; - // Modifies the width/height of the font (1 = 100%) - style->ScaleX = 1.0; - style->ScaleY = 1.0; - // Extra space between characters causes the underlined - // text line to become more discontinuous (test on LibAss 15.1) - style->Spacing = 0; - - // Set automatic paragraph direction (not VSFilter-compatible) - // to fix wrong RTL text direction when there are unicode chars - style->Encoding = ASS_FONT_ENCODING_AUTO; - - bool isFontBold = - (subStyle->fontStyle == FontStyle::BOLD || subStyle->fontStyle == FontStyle::BOLD_ITALIC); - bool isFontItalic = (subStyle->fontStyle == FontStyle::ITALIC || - subStyle->fontStyle == FontStyle::BOLD_ITALIC); - style->Bold = isFontBold * -1; - style->Italic = isFontItalic * -1; - - // Compute the font color, depending on the opacity - COLOR::Color subColor = ConvColor(subStyle->fontColor, subStyle->fontOpacity); - // Set default subtitles color - style->PrimaryColour = subColor; - // Set SecondaryColour may be used to prevent an onscreen collision - style->SecondaryColour = subColor; - - // Configure the effects - double lineSpacing = 0.0; - if (subStyle->borderStyle == BorderStyle::OUTLINE || - subStyle->borderStyle == BorderStyle::OUTLINE_NO_SHADOW) + style->BorderStyle = ASS_BORDER_STYLE_OUTLINE; + style->Outline = (10.00 / 100 * subStyle->fontBorderSize) * scale; + style->OutlineColour = ConvColor(subStyle->fontBorderColor, subStyle->fontOpacity); + if (subStyle->borderStyle == BorderStyle::OUTLINE_NO_SHADOW) { - style->BorderStyle = ASS_BORDER_STYLE_OUTLINE; - style->Outline = (10.00 / 100 * subStyle->fontBorderSize) * scale; - style->OutlineColour = ConvColor(subStyle->fontBorderColor, subStyle->fontOpacity); - if (subStyle->borderStyle == BorderStyle::OUTLINE_NO_SHADOW) - { - style->BackColour = ConvColor(COLOR::NONE, 0); // Set the shadow color - style->Shadow = 0; // Set the shadow size - } - else - { - style->BackColour = - ConvColor(subStyle->shadowColor, subStyle->shadowOpacity); // Set the shadow color - style->Shadow = (10.00 / 100 * subStyle->shadowSize) * scale; // Set the shadow size - } + style->BackColour = ConvColor(COLOR::NONE, 0); // Set the shadow color + style->Shadow = 0; // Set the shadow size } - else if (subStyle->borderStyle == BorderStyle::BOX) + else { - // This BorderStyle not support outline color/size - style->BorderStyle = ASS_BORDER_STYLE_BOX; - style->Outline = 4 * scale; // Space between the text and the box edges - style->OutlineColour = - ConvColor(subStyle->backgroundColor, - subStyle->backgroundOpacity); // Set the background border color style->BackColour = - ConvColor(subStyle->shadowColor, subStyle->shadowOpacity); // Set the box shadow color - style->Shadow = (10.00 / 100 * subStyle->shadowSize) * scale; // Set the box shadow size - // By default a box overlaps the other, then we increase a bit the line spacing - lineSpacing = 6.0; - } - else if (subStyle->borderStyle == BorderStyle::SQUARE_BOX) - { - // This BorderStyle not support shadow color/size - style->BorderStyle = ASS_BORDER_STYLE_SQUARE_BOX; - style->Outline = (10.00 / 100 * subStyle->fontBorderSize) * scale; - style->OutlineColour = ConvColor(subStyle->fontBorderColor, subStyle->fontOpacity); - style->BackColour = ConvColor(subStyle->backgroundColor, subStyle->backgroundOpacity); - style->Shadow = 4 * scale; // Space between the text and the box edges + ConvColor(subStyle->shadowColor, subStyle->shadowOpacity); // Set the shadow color + style->Shadow = (10.00 / 100 * subStyle->shadowSize) * scale; // Set the shadow size } + } + else if (subStyle->borderStyle == BorderStyle::BOX) + { + // This BorderStyle not support outline color/size + style->BorderStyle = ASS_BORDER_STYLE_BOX; + style->Outline = 4 * scale; // Space between the text and the box edges + style->OutlineColour = + ConvColor(subStyle->backgroundColor, + subStyle->backgroundOpacity); // Set the background border color + style->BackColour = + ConvColor(subStyle->shadowColor, subStyle->shadowOpacity); // Set the box shadow color + style->Shadow = (10.00 / 100 * subStyle->shadowSize) * scale; // Set the box shadow size + // By default a box overlaps the other, then we increase a bit the line spacing + lineSpacing = 6.0; + } + else if (subStyle->borderStyle == BorderStyle::SQUARE_BOX) + { + // This BorderStyle not support shadow color/size + style->BorderStyle = ASS_BORDER_STYLE_SQUARE_BOX; + style->Outline = (10.00 / 100 * subStyle->fontBorderSize) * scale; + style->OutlineColour = ConvColor(subStyle->fontBorderColor, subStyle->fontOpacity); + style->BackColour = ConvColor(subStyle->backgroundColor, subStyle->backgroundOpacity); + style->Shadow = 4 * scale; // Space between the text and the box edges + } - ass_set_line_spacing(m_renderer, lineSpacing); - - style->Blur = (10.00 / 100 * subStyle->blur); + ass_set_line_spacing(m_renderer, lineSpacing); - double marginLR = 20; - if (opts.horizontalAlignment != HorizontalAlignment::DISABLED) - { - // If the subtitle text is aligned on the left or right - // of the screen, we set an extra left/right margin - marginLR += static_cast<double>(opts.frameWidth) / 10; - } + style->Blur = (10.00 / 100 * subStyle->blur); - // Set the margins (in pixel) - style->MarginL = static_cast<int>(marginLR * scale); - style->MarginR = static_cast<int>(marginLR * scale); - // Vertical margin (direction depends on alignment) - // to be set only when the video calibration position setting is not used - if (opts.usePosition) - style->MarginV = 0; - else - style->MarginV = static_cast<int>(subStyle->marginVertical * scale); + double marginLR = 20; + if (opts.horizontalAlignment != HorizontalAlignment::DISABLED) + { + // If the subtitle text is aligned on the left or right + // of the screen, we set an extra left/right margin + marginLR += static_cast<double>(opts.frameWidth) / 10; } + // Set the margins (in pixel) + style->MarginL = static_cast<int>(marginLR * scale); + style->MarginR = static_cast<int>(marginLR * scale); + // Vertical margin (direction depends on alignment) + // to be set only when the video calibration position setting is not used + if (opts.usePosition) + style->MarginV = 0; + else + style->MarginV = static_cast<int>(subStyle->marginVertical * scale); + // Set the vertical alignment if (subStyle->alignment == FontAlignment::TOP_LEFT || subStyle->alignment == FontAlignment::TOP_CENTER || @@ -467,42 +502,32 @@ void CDVDSubtitlesLibass::ConfigureAssOverride( } // Default behaviour, disable ASS embedded styles override (if has been changed) - int stylesFlags = ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE; - + int stylesFlags{ASS_OVERRIDE_DEFAULT}; if (style) { // Manage override cases with ASS embedded styles if (subStyle->assOverrideStyles == OverrideStyles::STYLES) { - stylesFlags = ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS | ASS_OVERRIDE_BIT_FONT_NAME | - ASS_OVERRIDE_BIT_COLORS | ASS_OVERRIDE_BIT_ATTRIBUTES | + stylesFlags = ASS_OVERRIDE_BIT_COLORS | ASS_OVERRIDE_BIT_ATTRIBUTES | ASS_OVERRIDE_BIT_BORDER | ASS_OVERRIDE_BIT_MARGINS; } else if (subStyle->assOverrideStyles == OverrideStyles::STYLES_POSITIONS) { - stylesFlags = ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS | ASS_OVERRIDE_BIT_FONT_NAME | - ASS_OVERRIDE_BIT_COLORS | ASS_OVERRIDE_BIT_ATTRIBUTES | + stylesFlags = ASS_OVERRIDE_BIT_COLORS | ASS_OVERRIDE_BIT_ATTRIBUTES | ASS_OVERRIDE_BIT_BORDER | ASS_OVERRIDE_BIT_MARGINS | ASS_OVERRIDE_BIT_ALIGNMENT; } else if (subStyle->assOverrideStyles == OverrideStyles::POSITIONS) { stylesFlags = ASS_OVERRIDE_BIT_ALIGNMENT; } + if (subStyle->assOverrideFont) + { + stylesFlags |= ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS | ASS_OVERRIDE_BIT_FONT_NAME; + } ass_set_selective_style_override(m_renderer, style); } - ass_set_selective_style_override_enabled(m_renderer, stylesFlags); -} -void CDVDSubtitlesLibass::ConfigureFont(bool overrideFont, std::string fontName) -{ - int fontProvider = ASS_FONTPROVIDER_AUTODETECT; - std::string fontPath = GetDefaultFontPath(fontName); - if ((m_subtitleType == ADAPTED || overrideFont) && !fontPath.empty()) - fontProvider = ASS_FONTPROVIDER_NONE; - // Libass take in consideration of the default font specified only - // as last resort so when the builtin list of fallbacks fails, - // the be able to use our font we have to set ASS_FONTPROVIDER_NONE - ass_set_fonts(m_renderer, fontPath.c_str(), fontName.c_str(), fontProvider, nullptr, 1); + ass_set_selective_style_override_enabled(m_renderer, stylesFlags); } ASS_Event* CDVDSubtitlesLibass::GetEvents() diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h index 5df4a8cffd..82efffc3c7 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitlesLibass.h @@ -146,7 +146,6 @@ protected: private: void ConfigureAssOverride(const std::shared_ptr<struct KODI::SUBTITLES::style>& subStyle, ASS_Style* style); - void ConfigureFont(bool overrideFont, std::string fontName); void ApplyStyle(const std::shared_ptr<struct KODI::SUBTITLES::style>& subStyle, KODI::SUBTITLES::renderOpts opts); @@ -162,4 +161,5 @@ private: // default allocated style ID for the kodi user configured subtitle style int m_defaultKodiStyleId{ASS_NO_ID}; bool m_drawWithinBlackBars{false}; + std::string m_defaultFontFamilyName; }; diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h index eb249f1ce0..86c0170186 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/SubtitlesStyle.h @@ -21,7 +21,6 @@ constexpr double VIEWPORT_HEIGHT = 1080.0; constexpr double VIEWPORT_WIDTH = 1920.0; constexpr int MARGIN_VERTICAL = 30; - enum class HorizontalAlignment { DISABLED = 0, @@ -69,8 +68,8 @@ enum class OverrideStyles struct style { - std::string fontName; - double fontSize; + std::string fontName; // Font family name + double fontSize; // Font size in PT FontStyle fontStyle = FontStyle::NORMAL; UTILS::COLOR::Color fontColor = UTILS::COLOR::WHITE; int fontBorderSize = 15; // In % diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index 62e52089ad..9ea97e9da7 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -50,6 +50,7 @@ #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "storage/MediaManager.h" +#include "utils/FontUtils.h" #include "utils/JobManager.h" #include "utils/LangCodeExpander.h" #include "utils/StreamDetails.h" @@ -745,7 +746,7 @@ void CVideoPlayer::OnStartup() m_CurrentTeletext.Clear(); m_CurrentRadioRDS.Clear(); - CUtil::ClearTempFonts(); + UTILS::FONT::ClearTemporaryFonts(); } bool CVideoPlayer::OpenInputStream() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp index 6c9667097a..f83a26a019 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/OverlayRenderer.cpp @@ -263,7 +263,7 @@ void CRenderer::CreateSubtitlesStyle() m_overlayStyle = std::make_shared<KODI::SUBTITLES::style>(); const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); - m_overlayStyle->fontName = settings->GetString(CSettings::SETTING_SUBTITLES_FONT); + m_overlayStyle->fontName = settings->GetString(CSettings::SETTING_SUBTITLES_FONTNAME); m_overlayStyle->fontSize = (double)settings->GetInt(CSettings::SETTING_SUBTITLES_FONTSIZE); uint32_t fontStyleMask = settings->GetInt(CSettings::SETTING_SUBTITLES_STYLE) & FONT_STYLE_MASK; diff --git a/xbmc/guilib/GUIFontManager.cpp b/xbmc/guilib/GUIFontManager.cpp index e3e94bba40..3a22fcdf38 100644 --- a/xbmc/guilib/GUIFontManager.cpp +++ b/xbmc/guilib/GUIFontManager.cpp @@ -28,17 +28,48 @@ #include "filesystem/File.h" #include "settings/lib/Setting.h" #include "settings/lib/SettingDefinitions.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 (!XFILE::CFile::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() @@ -383,29 +414,18 @@ void GUIFontManager::Clear() void GUIFontManager::LoadFonts(const std::string& fontSet) { // Get the file to load fonts from: - const std::string strPath = g_SkinInfo->GetSkinPath("Font.xml", &m_skinResolution); - CLog::Log(LOGINFO, "GUIFontManager::{}: Loading fonts from '{}'", __func__, strPath); + const std::string filePath = g_SkinInfo->GetSkinPath("Font.xml", &m_skinResolution); + CLog::LogF(LOGINFO, "Loading fonts from '{}'", filePath); CXBMCTinyXML xmlDoc; - if (!xmlDoc.LoadFile(strPath)) - { - CLog::Log(LOGERROR, "GUIFontManager::{}: Couldn't load '{}'", __func__, strPath); + if (!LoadXMLData(filePath, xmlDoc)) return; - } TiXmlElement* pRootElement = xmlDoc.RootElement(); - if (!pRootElement || pRootElement->ValueStr() != "fonts") - { - CLog::Log(LOGERROR, "GUIFontManager::{}: File {} doesn't start with <fonts>", __func__, - strPath); - return; - } - // 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) { @@ -434,8 +454,7 @@ void GUIFontManager::LoadFonts(const std::string& fontSet) LoadFonts(firstFont); } else - CLog::Log(LOGERROR, "GUIFontManager::{}: File '{}' doesn't have a valid <fontset>", __func__, - strPath); + CLog::LogF(LOGERROR, "File '{}' doesn't have a valid <fontset>", filePath); } void GUIFontManager::LoadFonts(const TiXmlNode* fontNode) @@ -508,19 +527,143 @@ void GUIFontManager::SettingOptionsFontsFiller(const SettingConstPtr& setting, CFileItemList items; // Find font files - XFILE::CDirectory::GetDirectory("special://xbmc/media/Fonts/", itemsRoot, "", - XFILE::DIR_FLAG_DEFAULTS); - XFILE::CDirectory::GetDirectory("special://home/media/Fonts/", items, "", - XFILE::DIR_FLAG_DEFAULTS); + 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 && CUtil::IsSupportedFontExtension(item->GetLabel())) + if (item->m_bIsFolder) + continue; + + list.emplace_back(item->GetLabel(), item->GetLabel()); + } +} + +void GUIFontManager::Initialize() +{ + CSingleLock 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) { - list.emplace_back(item->GetLabel(), item->GetLabel()); + 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()); } diff --git a/xbmc/guilib/GUIFontManager.h b/xbmc/guilib/GUIFontManager.h index 88aed7bd62..5923517059 100644 --- a/xbmc/guilib/GUIFontManager.h +++ b/xbmc/guilib/GUIFontManager.h @@ -14,6 +14,8 @@ */ #include "IMsgTargetCallback.h" +#include "threads/CriticalSection.h" +#include "threads/SingleLock.h" #include "utils/ColorUtils.h" #include "utils/GlobalsHandling.h" #include "windowing/GraphicContext.h" @@ -40,6 +42,17 @@ struct OrigFontInfo bool border; }; +struct FontMetadata +{ + FontMetadata(const std::string& filename, const std::string& familyName) + : m_filename{filename}, m_familyName{familyName} + { + } + + std::string m_filename; + std::string m_familyName; +}; + /*! \ingroup textures \brief @@ -50,6 +63,14 @@ public: GUIFontManager(); ~GUIFontManager() override; + /*! + * \brief Initialize the font manager. + * Checks that fonts cache are up to date, otherwise update it. + */ + void Initialize(); + + bool IsUpdating() const { return m_critSection.IsLocked(); } + bool OnMessage(CGUIMessage& message) override; void Unload(const std::string& strFontName); @@ -81,6 +102,12 @@ public: std::string& current, void* data); + /*! + * \brief Get the list of user fonts as family names from cache + * \return The list of available fonts family names + */ + std::vector<std::string> GetUserFontsFamilyNames(); + protected: void ReloadTTFFonts(); static void RescaleFontSizeAndAspect(CGraphicContext& context, @@ -97,6 +124,12 @@ protected: std::vector<OrigFontInfo> m_vecFontInfo; RESOLUTION_INFO m_skinResolution; bool m_canReload{true}; + +private: + void LoadUserFonts(); + + mutable CCriticalSection m_critSection; + std::vector<FontMetadata> m_userFontsCache; }; /*! diff --git a/xbmc/settings/SettingConditions.cpp b/xbmc/settings/SettingConditions.cpp index 8f5ec6a824..fb4201f114 100644 --- a/xbmc/settings/SettingConditions.cpp +++ b/xbmc/settings/SettingConditions.cpp @@ -25,6 +25,7 @@ #include "profiles/ProfileManager.h" #include "settings/SettingAddon.h" #include "settings/SettingsComponent.h" +#include "utils/FontUtils.h" #include "utils/StringUtils.h" #include "windowing/WinSystem.h" @@ -134,7 +135,7 @@ bool HasSubtitlesFontExtensions(const std::string& condition, if (!settingStr) return false; - return CUtil::IsSupportedFontExtension(settingStr->GetValue()); + return UTILS::FONT::IsSupportedFontExtension(settingStr->GetValue()); } bool ProfileCanWriteDatabase(const std::string& condition, diff --git a/xbmc/settings/Settings.cpp b/xbmc/settings/Settings.cpp index 02420efb89..ea9b359011 100644 --- a/xbmc/settings/Settings.cpp +++ b/xbmc/settings/Settings.cpp @@ -164,7 +164,7 @@ constexpr const char* CSettings::SETTING_SUBTITLES_PARSECAPTIONS; constexpr const char* CSettings::SETTING_SUBTITLES_CAPTIONSALIGN; constexpr const char* CSettings::SETTING_SUBTITLES_ALIGN; constexpr const char* CSettings::SETTING_SUBTITLES_STEREOSCOPICDEPTH; -constexpr const char* CSettings::SETTING_SUBTITLES_FONT; +constexpr const char* CSettings::SETTING_SUBTITLES_FONTNAME; constexpr const char* CSettings::SETTING_SUBTITLES_FONTSIZE; constexpr const char* CSettings::SETTING_SUBTITLES_STYLE; constexpr const char* CSettings::SETTING_SUBTITLES_COLOR; @@ -788,6 +788,8 @@ void CSettings::InitializeOptionFillers() #endif GetSettingsManager()->RegisterSettingOptionsFiller("charsets", CCharsetConverter::SettingOptionsCharsetsFiller); GetSettingsManager()->RegisterSettingOptionsFiller("fonts", GUIFontManager::SettingOptionsFontsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller( + "subtitlesfonts", SUBTITLES::CSubtitlesSettings::SettingOptionsSubtitleFontsFiller); GetSettingsManager()->RegisterSettingOptionsFiller("languagenames", CLangInfo::SettingOptionsLanguageNamesFiller); GetSettingsManager()->RegisterSettingOptionsFiller("refreshchangedelays", CDisplaySettings::SettingOptionsRefreshChangeDelaysFiller); GetSettingsManager()->RegisterSettingOptionsFiller("refreshrates", CDisplaySettings::SettingOptionsRefreshRatesFiller); @@ -832,6 +834,7 @@ void CSettings::UninitializeOptionFillers() GetSettingsManager()->UnregisterSettingOptionsFiller("charsets"); GetSettingsManager()->UnregisterSettingOptionsFiller("fontheights"); GetSettingsManager()->UnregisterSettingOptionsFiller("fonts"); + GetSettingsManager()->UnregisterSettingOptionsFiller("subtitlesfonts"); GetSettingsManager()->UnregisterSettingOptionsFiller("languagenames"); GetSettingsManager()->UnregisterSettingOptionsFiller("refreshchangedelays"); GetSettingsManager()->UnregisterSettingOptionsFiller("refreshrates"); diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index 1227b306ee..c31fc60a82 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -143,7 +143,7 @@ public: static constexpr auto SETTING_SUBTITLES_CAPTIONSALIGN = "subtitles.captionsalign"; static constexpr auto SETTING_SUBTITLES_ALIGN = "subtitles.align"; static constexpr auto SETTING_SUBTITLES_STEREOSCOPICDEPTH = "subtitles.stereoscopicdepth"; - static constexpr auto SETTING_SUBTITLES_FONT = "subtitles.font"; + static constexpr auto SETTING_SUBTITLES_FONTNAME = "subtitles.fontname"; static constexpr auto SETTING_SUBTITLES_FONTSIZE = "subtitles.fontsize"; static constexpr auto SETTING_SUBTITLES_STYLE = "subtitles.style"; static constexpr auto SETTING_SUBTITLES_COLOR = "subtitles.colorpick"; diff --git a/xbmc/settings/SubtitlesSettings.cpp b/xbmc/settings/SubtitlesSettings.cpp index 6414de7fd5..25742dd203 100644 --- a/xbmc/settings/SubtitlesSettings.cpp +++ b/xbmc/settings/SubtitlesSettings.cpp @@ -8,15 +8,23 @@ #include "SubtitlesSettings.h" +#include "FileItem.h" #include "ServiceBroker.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "guilib/GUIFontManager.h" +#include "guilib/LocalizeStrings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "settings/lib/Setting.h" +#include "utils/FontUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + using namespace KODI; using namespace SUBTITLES; - CSubtitlesSettings::CSubtitlesSettings() { m_settings = CServiceBroker::GetSettingsComponent()->GetSettings(); @@ -25,7 +33,7 @@ CSubtitlesSettings::CSubtitlesSettings() CSettings::SETTING_SUBTITLES_PARSECAPTIONS, CSettings::SETTING_SUBTITLES_ALIGN, CSettings::SETTING_SUBTITLES_STEREOSCOPICDEPTH, - CSettings::SETTING_SUBTITLES_FONT, + CSettings::SETTING_SUBTITLES_FONTNAME, CSettings::SETTING_SUBTITLES_FONTSIZE, CSettings::SETTING_SUBTITLES_STYLE, CSettings::SETTING_SUBTITLES_COLOR, @@ -70,3 +78,26 @@ void CSubtitlesSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& SetChanged(); NotifyObservers(ObservableMessageSettingsChanged); } + +void CSubtitlesSettings::SettingOptionsSubtitleFontsFiller(const SettingConstPtr& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + // From application system fonts folder we add the default font only + std::string defaultFontPath = + URIUtils::AddFileToFolder("special://xbmc/media/Fonts/", UTILS::FONT::FONT_DEFAULT_FILENAME); + if (XFILE::CFile::Exists(defaultFontPath)) + { + std::string familyName = UTILS::FONT::GetFontFamily(defaultFontPath); + if (!familyName.empty()) + { + list.emplace_back(g_localizeStrings.Get(571) + " " + familyName, FONT_DEFAULT_FAMILYNAME); + } + } + // Add additionals fonts from the user fonts folder + for (std::string familyName : g_fontManager.GetUserFontsFamilyNames()) + { + list.emplace_back(familyName, familyName); + } +} diff --git a/xbmc/settings/SubtitlesSettings.h b/xbmc/settings/SubtitlesSettings.h index ad7e41c783..0940c589a7 100644 --- a/xbmc/settings/SubtitlesSettings.h +++ b/xbmc/settings/SubtitlesSettings.h @@ -11,13 +11,19 @@ #include "settings/lib/ISettingCallback.h" #include "utils/Observer.h" +#include <memory> + class CSetting; class CSettings; +struct StringSettingOption; namespace KODI { namespace SUBTITLES { +// This is a placeholder to keep the fontname setting valid +// even if the default app font could be changed +constexpr const char* FONT_DEFAULT_FAMILYNAME = "DEFAULT"; class CSubtitlesSettings : public ISettingCallback, public Observable { @@ -27,6 +33,11 @@ public: // Inherited from ISettingCallback void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + static void SettingOptionsSubtitleFontsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + protected: CSubtitlesSettings(); ~CSubtitlesSettings() override; diff --git a/xbmc/settings/windows/GUIControlSettings.cpp b/xbmc/settings/windows/GUIControlSettings.cpp index 12aced38e7..eea6e76330 100644 --- a/xbmc/settings/windows/GUIControlSettings.cpp +++ b/xbmc/settings/windows/GUIControlSettings.cpp @@ -670,9 +670,8 @@ bool CGUIControlListSetting::OnClick() if (!bAllowNewOption) { // Do not show dialog if - // * there are no items to be chosen or - // * only one value can be chosen and there are less than two items available - if (!optionsValid || options.Size() <= 0 || (!control->CanMultiSelect() && options.Size() <= 1)) + // there are no items to be chosen + if (!optionsValid || options.Size() <= 0) return false; dialog->Reset(); @@ -851,10 +850,8 @@ void CGUIControlListSetting::Update(bool fromControl, bool updateDisplayOnly) if (!updateDisplayOnly) { // Disable the control if no items can be added and - // * there are no items to be chosen - // * only one value can be chosen and there are less than two items available - if (!m_pButton->IsDisabled() && !bAllowNewOption && - (options.Size() <= 0 || (!control->CanMultiSelect() && options.Size() <= 1))) + // there are no items to be chosen + if (!m_pButton->IsDisabled() && !bAllowNewOption && (options.Size() <= 0)) m_pButton->SetEnabled(false); } } diff --git a/xbmc/threads/Lockables.h b/xbmc/threads/Lockables.h index 333d330229..28489f8a60 100644 --- a/xbmc/threads/Lockables.h +++ b/xbmc/threads/Lockables.h @@ -21,6 +21,7 @@ namespace XbmcThreads * lock(); * try_lock(); * unlock(); + * IsLocked(); * * "Exitable" specifically means that, no matter how deep the recursion * on the mutex/critical section, we can exit from it and then restore @@ -50,6 +51,12 @@ namespace XbmcThreads inline bool try_lock() { return mutex.try_lock() ? count++, true : false; } inline void unlock() { count--; mutex.unlock(); } + /*! + * \brief Check if have a lock owned + * \return True if have a lock, otherwise false + */ + inline bool IsLocked() const { return count > 0; } + /** * This implements the "exitable" behavior mentioned above. */ diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index cb3a084741..201f2e992a 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -24,6 +24,7 @@ set(SOURCES ActorProtocol.cpp Fanart.cpp FileOperationJob.cpp FileUtils.cpp + FontUtils.cpp GroupUtils.cpp HTMLUtil.cpp HttpHeader.cpp @@ -99,6 +100,7 @@ set(HEADERS ActorProtocol.h Fanart.h FileOperationJob.h FileUtils.h + FontUtils.h Geometry.h GlobalsHandling.h GroupUtils.h diff --git a/xbmc/utils/FontUtils.cpp b/xbmc/utils/FontUtils.cpp new file mode 100644 index 0000000000..c93b15e8fb --- /dev/null +++ b/xbmc/utils/FontUtils.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 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 "FontUtils.h" + +#include "FileItem.h" +#include "StringUtils.h" +#include "URIUtils.h" +#include "Util.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "filesystem/SpecialProtocol.h" +#include "utils/log.h" + +#include <ft2build.h> + +#include FT_FREETYPE_H + +using namespace XFILE; + +std::string UTILS::FONT::GetFontFamily(std::vector<uint8_t>& buffer) +{ + FT_Library m_library{nullptr}; + FT_Init_FreeType(&m_library); + if (!m_library) + { + CLog::LogF(LOGERROR, "Unable to initialize freetype library"); + return ""; + } + + // Load the font face + FT_Face face; + std::string familyName; + if (FT_New_Memory_Face(m_library, reinterpret_cast<const FT_Byte*>(buffer.data()), buffer.size(), + 0, &face) == 0) + { + familyName = std::string(face->family_name); + } + else + { + CLog::LogF(LOGERROR, "Failed to process font memory buffer"); + } + + FT_Done_Face(face); + FT_Done_FreeType(m_library); + return familyName; +} + +std::string UTILS::FONT::GetFontFamily(const std::string& filepath) +{ + std::vector<uint8_t> buffer; + if (filepath.empty()) + return ""; + if (XFILE::CFile().LoadFile(filepath, buffer) <= 0) + { + CLog::LogF(LOGERROR, "Failed to load file {}", filepath); + return ""; + } + return GetFontFamily(buffer); +} + +bool UTILS::FONT::IsSupportedFontExtension(const std::string& filepath) +{ + return URIUtils::HasExtension(filepath, UTILS::FONT::SUPPORTED_EXTENSIONS_MASK); +} + +void UTILS::FONT::ClearTemporaryFonts() +{ + if (!CDirectory::Exists(UTILS::FONT::FONTPATH::TEMP)) + return; + + CFileItemList items; + CDirectory::GetDirectory(UTILS::FONT::FONTPATH::TEMP, items, + UTILS::FONT::SUPPORTED_EXTENSIONS_MASK, + DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_BYPASS_CACHE | DIR_FLAG_GET_HIDDEN); + for (const auto& item : items) + { + if (item->m_bIsFolder) + continue; + + CFile::Delete(item->GetPath()); + } +} + +std::string UTILS::FONT::FONTPATH::GetSystemFontPath(const std::string& filename) +{ + std::string fontPath = URIUtils::AddFileToFolder( + CSpecialProtocol::TranslatePath(UTILS::FONT::FONTPATH::SYSTEM), filename); + if (XFILE::CFile::Exists(fontPath)) + { + return CSpecialProtocol::TranslatePath(fontPath); + } + + CLog::LogF(LOGERROR, "Could not find application system font {}", filename); + return ""; +} diff --git a/xbmc/utils/FontUtils.h b/xbmc/utils/FontUtils.h new file mode 100644 index 0000000000..d4b92ddcee --- /dev/null +++ b/xbmc/utils/FontUtils.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 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. + */ + +#pragma once + +#include <stdint.h> +#include <string> +#include <vector> + +namespace UTILS +{ +namespace FONT +{ +constexpr const char* SUPPORTED_EXTENSIONS_MASK = ".ttf|.otf"; + +// The default application font +constexpr const char* FONT_DEFAULT_FILENAME = "arial.ttf"; + +namespace FONTPATH +{ +// Directory where Kodi bundled fonts files are located +constexpr const char* SYSTEM = "special://xbmc/media/Fonts/"; +// Directory where user defined fonts are located +constexpr const char* USER = "special://home/media/Fonts/"; +// Temporary font path (where MKV fonts are extracted and temporarily stored) +constexpr const char* TEMP = "special://temp/fonts/"; + +/*! + * \brief Provided a font filename returns the complete path for the font in + * the system font folder (if it exists) or an empty string otherwise + * \param filename The font file name + * \return The path for the font or an empty string if the path does not exist + */ +std::string GetSystemFontPath(const std::string& filename); +}; // namespace FONTPATH + +/*! + * \brief Get the font family name from a font file + * \param buffer The font data + * \return The font family name, otherwise empty if fails + */ +std::string GetFontFamily(std::vector<uint8_t>& buffer); + +/*! + * \brief Get the font family name from a font file + * \param filepath The path where read the font data + * \return The font family name, otherwise empty if fails + */ +std::string GetFontFamily(const std::string& filepath); + +/*! + * \brief Check if a filename have a supported font extension. + * \param filepath The font file path + * \return True if it has a supported extension, otherwise false + */ +bool IsSupportedFontExtension(const std::string& filepath); + +/*! + * \brief Removes all temporary fonts, e.g.those extract from MKV containers + * that are only available during playback + */ +void ClearTemporaryFonts(); + +} // namespace FONT +} // namespace UTILS diff --git a/xbmc/utils/StringUtils.h b/xbmc/utils/StringUtils.h index 9ff28e20d1..d30c64c5be 100644 --- a/xbmc/utils/StringUtils.h +++ b/xbmc/utils/StringUtils.h @@ -395,7 +395,7 @@ private: struct sortstringbyname { - bool operator()(const std::string& strItem1, const std::string& strItem2) + bool operator()(const std::string& strItem1, const std::string& strItem2) const { return StringUtils::CompareNoCase(strItem1, strItem2) < 0; } |