Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Subtitles] Add support to font collection (.ttc) #23429

Merged
merged 1 commit into from Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 25 additions & 12 deletions xbmc/guilib/GUIFontManager.cpp
Expand Up @@ -597,10 +597,16 @@ void GUIFontManager::LoadUserFonts()
while (fontNode)
{
std::string filename;
std::string familyName;
XMLUtils::GetString(fontNode, "filename", filename);
XMLUtils::GetString(fontNode, "familyname", familyName);
m_userFontsCache.emplace_back(filename, familyName);

std::set<std::string> familyNames;
for (const TiXmlElement* fnChildNode = fontNode->FirstChildElement("familyname");
fnChildNode; fnChildNode = fnChildNode->NextSiblingElement("familyname"))
{
familyNames.emplace(fnChildNode->GetText());
}

m_userFontsCache.emplace_back(filename, familyNames);
fontNode = fontNode->NextSibling("font");
}
}
Expand Down Expand Up @@ -645,10 +651,10 @@ void GUIFontManager::LoadUserFonts()
if (item->m_bIsFolder)
continue;

std::string familyName = UTILS::FONT::GetFontFamily(filepath);
if (!familyName.empty())
std::set<std::string> familyNames;
if (UTILS::FONT::GetFontFamilyNames(filepath, familyNames))
{
m_userFontsCache.emplace_back(item->GetLabel(), familyName);
m_userFontsCache.emplace_back(item->GetLabel(), familyNames);
}
}
isCacheChanged = isCacheChanged || previousCacheSize != m_userFontsCache.size();
Expand All @@ -660,22 +666,26 @@ void GUIFontManager::LoadUserFonts()
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)
for (const FontMetadata& 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);
for (const std::string& familyName : fontMetadata.m_familyNames)
{
XMLUtils::SetString(fontNode, "familyname", familyName);
}
}
if (!xmlDoc.SaveFile(userFontCacheFilepath))
CLog::LogF(LOGERROR, "Failed to save fonts cache file '{}'", userFontCacheFilepath);
CLog::LogF(LOGERROR, "Failed to save fonts cache file \"{}\"", userFontCacheFilepath);
}
else
{
CLog::LogF(LOGERROR, "Failed to create XML 'fonts' node");
CLog::LogF(LOGERROR, "Failed to create XML \"fonts\" node");
}
}
CLog::LogF(LOGDEBUG, "Updating user fonts cache... DONE");
Expand All @@ -687,9 +697,12 @@ std::vector<std::string> GUIFontManager::GetUserFontsFamilyNames()
// 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)
for (const FontMetadata& fontMetadata : m_userFontsCache)
{
familyNames.insert(fontMetadata.m_familyName);
for (const std::string& familyName : fontMetadata.m_familyNames)
{
familyNames.insert(familyName);
}
}
return std::vector<std::string>(familyNames.begin(), familyNames.end());
}
7 changes: 4 additions & 3 deletions xbmc/guilib/GUIFontManager.h
Expand Up @@ -20,6 +20,7 @@
#include "utils/GlobalsHandling.h"
#include "windowing/GraphicContext.h"

#include <set>
#include <utility>
#include <vector>

Expand All @@ -44,13 +45,13 @@ struct OrigFontInfo

struct FontMetadata
{
FontMetadata(const std::string& filename, const std::string& familyName)
: m_filename{filename}, m_familyName{familyName}
FontMetadata(const std::string& filename, const std::set<std::string>& familyNames)
: m_filename{filename}, m_familyNames{familyNames}
{
}

std::string m_filename;
std::string m_familyName;
std::set<std::string> m_familyNames;
};

/*!
Expand Down
180 changes: 144 additions & 36 deletions xbmc/utils/FontUtils.cpp
Expand Up @@ -25,6 +25,147 @@

using namespace XFILE;

namespace
{
// \brief Get font family from SFNT table entries
std::string GetFamilyNameFromSfnt(FT_Face face)
{
std::string familyName;

for (FT_UInt index = 0; index < FT_Get_Sfnt_Name_Count(face); ++index)
{
FT_SfntName name;
if (FT_Get_Sfnt_Name(face, index, &name) != 0)
{
CLog::LogF(LOGWARNING, "Failed to get SFNT name at index {}", index);
continue;
}

// Get the unicode font family name (format-specific interface)
// In properties there may be one or more font family names encoded for each platform.
// NOTE: we give preference to MS/APPLE platform data, then fallback to MAC
// because has been found some fonts that provide names not convertible MAC text to UTF8
if (name.name_id == TT_NAME_ID_FONT_FAMILY)
{
const std::string nameEnc{reinterpret_cast<const char*>(name.string), name.string_len};

if (name.platform_id == TT_PLATFORM_MICROSOFT ||
name.platform_id == TT_PLATFORM_APPLE_UNICODE)
{
if (name.language_id != TT_MAC_LANGID_ENGLISH &&
name.language_id != TT_MS_LANGID_ENGLISH_UNITED_STATES &&
name.language_id != TT_MS_LANGID_ENGLISH_UNITED_KINGDOM)
continue;

if (CCharsetConverter::utf16BEtoUTF8(nameEnc, familyName))
break; // Stop here to prefer the name given with this platform
else
CLog::LogF(LOGERROR, "Failed to convert the font name string encoded as \"UTF-16BE\"");
}
else if (name.platform_id == TT_PLATFORM_MACINTOSH && familyName.empty())
{
if (name.language_id != TT_MAC_LANGID_ENGLISH || name.encoding_id != TT_MAC_ID_ROMAN)
continue;

if (!CCharsetConverter::MacintoshToUTF8(nameEnc, familyName))
CLog::LogF(LOGERROR, "Failed to convert the font name string encoded as \"macintosh\"");
}
else
{
CLog::LogF(LOGERROR, "Unsupported font SFNT name platform \"{}\"", name.platform_id);
}
}
}
return familyName;
}
} // unnamed namespace

bool UTILS::FONT::GetFontFamilyNames(const std::vector<uint8_t>& buffer,
std::set<std::string>& familyNames)
{
FT_Library m_library{nullptr};
FT_Init_FreeType(&m_library);
if (!m_library)
{
CLog::LogF(LOGERROR, "Unable to initialize freetype library");
return false;
}

FT_Open_Args args{};
args.flags = FT_OPEN_MEMORY;
args.memory_base = reinterpret_cast<const FT_Byte*>(buffer.data());
args.memory_size = static_cast<FT_Long>(buffer.size());

FT_Long numFaces{0};
FT_Long numInstances{0};
FT_Long faceIndex{0};
FT_Long instanceIndex{0};

// Iterate over all font faces, files like .ttc (TrueType Collection) contains multiple fonts
do
{
FT_Long idx = (instanceIndex << 16) + faceIndex;
FT_Face face{nullptr};

FT_Error error = FT_Open_Face(m_library, &args, idx, &face);
if (error)
{
CLog::LogF(LOGERROR, "Failed to open font face at index {} error code {}", idx, error);
break;
}

std::string familyName = GetFamilyNameFromSfnt(face);
if (familyName.empty())
{
CLog::LogF(LOGWARNING, "Failed to get the unicode family name for \"{}\", fallback to ASCII",
face->family_name);
// ASCII font family name may differ from the unicode one, use this as fallback only
familyName = std::string{face->family_name};
if (familyName.empty())
{
CLog::LogF(LOGERROR, "Family name missing in the font");
return false;
}
}

// We use the "set" container to avoid duplicate names that can happens
// for example when a font have each style on different files
familyNames.insert(familyName);

numFaces = face->num_faces;
numInstances = face->style_flags >> 16;

FT_Done_Face(face);

if (instanceIndex < numInstances)
instanceIndex++;
else
{
faceIndex++;
instanceIndex = 0;
}

} while (faceIndex < numFaces);

FT_Done_FreeType(m_library);
return true;
}

bool UTILS::FONT::GetFontFamilyNames(const std::string& filepath,
std::set<std::string>& familyNames)
{
std::vector<uint8_t> buffer;
if (filepath.empty())
return false;

if (XFILE::CFile().LoadFile(filepath, buffer) <= 0)
{
CLog::LogF(LOGERROR, "Failed to load file {}", filepath);
return false;
}
return GetFontFamilyNames(buffer, familyNames);
}

std::string UTILS::FONT::GetFontFamily(std::vector<uint8_t>& buffer)
{
FT_Library m_library{nullptr};
Expand All @@ -41,48 +182,15 @@ std::string UTILS::FONT::GetFontFamily(std::vector<uint8_t>& buffer)
if (FT_New_Memory_Face(m_library, reinterpret_cast<const FT_Byte*>(buffer.data()), buffer.size(),
0, &face) == 0)
{
// Get SFNT table entries to get font properties
for (FT_UInt index = 0; index < FT_Get_Sfnt_Name_Count(face); index++)
{
FT_SfntName name;
if (FT_Get_Sfnt_Name(face, index, &name) != 0)
{
CLog::LogF(LOGWARNING, "Failed to get SFNT name at index {}", index);
continue;
}

// Get the unicode font family name (format-specific interface)
// In properties there may be one or more font family names encoded for
// each platform, we take the first available converting it to UTF-8
if (name.name_id == TT_NAME_ID_FONT_FAMILY)
{
const std::string nameEnc{reinterpret_cast<const char*>(name.string), name.string_len};

if (name.platform_id == TT_PLATFORM_MICROSOFT ||
name.platform_id == TT_PLATFORM_APPLE_UNICODE)
{
if (!CCharsetConverter::utf16BEtoUTF8(nameEnc, familyName))
CLog::LogF(LOGERROR, "Failed to convert the font name string encoded as \"UTF-16BE\"");
}
else if (name.platform_id == TT_PLATFORM_MACINTOSH)
{
if (!CCharsetConverter::MacintoshToUTF8(nameEnc, familyName))
CLog::LogF(LOGERROR, "Failed to convert the font name string encoded as \"macintosh\"");
}
else
{
CLog::LogF(LOGERROR, "Unsupported font SFNT name platform \"{}\"", name.platform_id);
}
if (!familyName.empty())
break;
}
}
familyName = GetFamilyNameFromSfnt(face);
if (familyName.empty())
{
CLog::LogF(LOGWARNING, "Failed to get the unicode family name for \"{}\", fallback to ASCII",
face->family_name);
// ASCII font family name may differ from the unicode one, use this as fallback only
familyName = std::string{face->family_name};
if (familyName.empty())
CLog::LogF(LOGERROR, "Family name missing in the font");
}
}
else
Expand Down
27 changes: 24 additions & 3 deletions xbmc/utils/FontUtils.h
Expand Up @@ -8,6 +8,7 @@

#pragma once

#include <set>
#include <stdint.h>
#include <string>
#include <vector>
Expand All @@ -16,7 +17,7 @@ namespace UTILS
{
namespace FONT
{
constexpr const char* SUPPORTED_EXTENSIONS_MASK = ".ttf|.otf";
constexpr const char* SUPPORTED_EXTENSIONS_MASK = ".ttf|.ttc|.otf";

// The default application font
constexpr const char* FONT_DEFAULT_FILENAME = "arial.ttf";
Expand All @@ -40,14 +41,34 @@ std::string GetSystemFontPath(const std::string& filename);
}; // namespace FONTPATH

/*!
* \brief Get the font family name from a font file
* \brief Get the font family name from a font file,
* in case of font collection (.ttc) will take the family name of all fonts
* \param buffer The font data
* \param familyNames The font family names
* \return True if success, otherwise false when an error occurs
*/
bool GetFontFamilyNames(const std::vector<uint8_t>& buffer, std::set<std::string>& familyNames);

/*!
* \brief Get the font family name from a font file,
* in case of font collection (.ttc) will take the family name of all fonts
* \param filepath The path where read the font data
* \param familyNames The font family names
* \return True if success, otherwise false when an error occurs
*/
bool GetFontFamilyNames(const std::string& filepath, std::set<std::string>& familyNames);

/*!
* \brief Get the font family name from a font file,
* in case of font collection (.ttc) will take the first available
* \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
* \brief Get the font family name from a font file,
* in case of font collection (.ttc) will take the first available
* \param filepath The path where read the font data
* \return The font family name, otherwise empty if fails
*/
Expand Down