Merge pull request #26166 from ksooo/improve-plugin-ctx-menus
[xbmc.git] / xbmc / utils / FontUtils.cpp
blobc5e47698506ebc57a83a2a57a07f7dfc28620d4e
1 /*
2 * Copyright (C) 2022 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "FontUtils.h"
11 #include "FileItem.h"
12 #include "FileItemList.h"
13 #include "StringUtils.h"
14 #include "URIUtils.h"
15 #include "filesystem/Directory.h"
16 #include "filesystem/File.h"
17 #include "filesystem/SpecialProtocol.h"
18 #include "utils/CharsetConverter.h"
19 #include "utils/log.h"
21 #include <ft2build.h>
23 #include FT_FREETYPE_H
24 #include FT_SFNT_NAMES_H
25 #include FT_TRUETYPE_IDS_H
27 using namespace XFILE;
29 namespace KODI::UTILS::FONT
32 namespace
34 // \brief Get font family from SFNT table entries
35 std::string GetFamilyNameFromSfnt(FT_Face face)
37 std::string familyName;
39 for (FT_UInt index = 0; index < FT_Get_Sfnt_Name_Count(face); ++index)
41 FT_SfntName name;
42 if (FT_Get_Sfnt_Name(face, index, &name) != 0)
44 CLog::LogF(LOGWARNING, "Failed to get SFNT name at index {}", index);
45 continue;
48 // Get the unicode font family name (format-specific interface)
49 // In properties there may be one or more font family names encoded for each platform.
50 // NOTE: we give preference to MS/APPLE platform data, then fallback to MAC
51 // because has been found some fonts that provide names not convertible MAC text to UTF8
52 if (name.name_id == TT_NAME_ID_FONT_FAMILY)
54 const std::string nameEnc{reinterpret_cast<const char*>(name.string), name.string_len};
56 if (name.platform_id == TT_PLATFORM_MICROSOFT ||
57 name.platform_id == TT_PLATFORM_APPLE_UNICODE)
59 if (name.language_id != TT_MAC_LANGID_ENGLISH &&
60 name.language_id != TT_MS_LANGID_ENGLISH_UNITED_STATES &&
61 name.language_id != TT_MS_LANGID_ENGLISH_UNITED_KINGDOM)
62 continue;
64 if (CCharsetConverter::utf16BEtoUTF8(nameEnc, familyName))
65 break; // Stop here to prefer the name given with this platform
66 else
67 CLog::LogF(LOGERROR, "Failed to convert the font name string encoded as \"UTF-16BE\"");
69 else if (name.platform_id == TT_PLATFORM_MACINTOSH && familyName.empty())
71 if (name.language_id != TT_MAC_LANGID_ENGLISH || name.encoding_id != TT_MAC_ID_ROMAN)
72 continue;
74 if (!CCharsetConverter::MacintoshToUTF8(nameEnc, familyName))
75 CLog::LogF(LOGERROR, "Failed to convert the font name string encoded as \"macintosh\"");
77 else
79 CLog::LogF(LOGERROR, "Unsupported font SFNT name platform \"{}\"", name.platform_id);
83 return familyName;
85 } // unnamed namespace
87 bool GetFontFamilyNames(const std::vector<uint8_t>& buffer, std::set<std::string>& familyNames)
89 FT_Library m_library{nullptr};
90 FT_Init_FreeType(&m_library);
91 if (!m_library)
93 CLog::LogF(LOGERROR, "Unable to initialize freetype library");
94 return false;
97 FT_Open_Args args{};
98 args.flags = FT_OPEN_MEMORY;
99 args.memory_base = reinterpret_cast<const FT_Byte*>(buffer.data());
100 args.memory_size = static_cast<FT_Long>(buffer.size());
102 FT_Long numFaces{0};
103 FT_Long numInstances{0};
104 FT_Long faceIndex{0};
105 FT_Long instanceIndex{0};
107 // Iterate over all font faces, files like .ttc (TrueType Collection) contains multiple fonts
110 FT_Long idx = (instanceIndex << 16) + faceIndex;
111 FT_Face face{nullptr};
113 FT_Error error = FT_Open_Face(m_library, &args, idx, &face);
114 if (error)
116 CLog::LogF(LOGERROR, "Failed to open font face at index {} error code {}", idx, error);
117 break;
120 std::string familyName = GetFamilyNameFromSfnt(face);
121 if (familyName.empty())
123 CLog::LogF(LOGWARNING, "Failed to get the unicode family name for \"{}\", fallback to ASCII",
124 face->family_name);
125 // ASCII font family name may differ from the unicode one, use this as fallback only
126 familyName = std::string{face->family_name};
127 if (familyName.empty())
129 CLog::LogF(LOGERROR, "Family name missing in the font");
130 return false;
134 // We use the "set" container to avoid duplicate names that can happens
135 // for example when a font have each style on different files
136 familyNames.insert(familyName);
138 numFaces = face->num_faces;
139 numInstances = face->style_flags >> 16;
141 FT_Done_Face(face);
143 if (instanceIndex < numInstances)
144 instanceIndex++;
145 else
147 faceIndex++;
148 instanceIndex = 0;
151 } while (faceIndex < numFaces);
153 FT_Done_FreeType(m_library);
154 return true;
157 bool GetFontFamilyNames(const std::string& filepath, std::set<std::string>& familyNames)
159 std::vector<uint8_t> buffer;
160 if (filepath.empty())
161 return false;
163 if (XFILE::CFile().LoadFile(filepath, buffer) <= 0)
165 CLog::LogF(LOGERROR, "Failed to load file {}", filepath);
166 return false;
168 return GetFontFamilyNames(buffer, familyNames);
171 std::string GetFontFamily(std::vector<uint8_t>& buffer)
173 FT_Library m_library{nullptr};
174 FT_Init_FreeType(&m_library);
175 if (!m_library)
177 CLog::LogF(LOGERROR, "Unable to initialize freetype library");
178 return "";
181 // Load the font face
182 FT_Face face;
183 std::string familyName;
184 if (FT_New_Memory_Face(m_library, reinterpret_cast<const FT_Byte*>(buffer.data()), buffer.size(),
185 0, &face) == 0)
187 familyName = GetFamilyNameFromSfnt(face);
188 if (familyName.empty())
190 CLog::LogF(LOGWARNING, "Failed to get the unicode family name for \"{}\", fallback to ASCII",
191 face->family_name);
192 // ASCII font family name may differ from the unicode one, use this as fallback only
193 familyName = std::string{face->family_name};
194 if (familyName.empty())
195 CLog::LogF(LOGERROR, "Family name missing in the font");
198 else
200 CLog::LogF(LOGERROR, "Failed to process font memory buffer");
203 FT_Done_Face(face);
204 FT_Done_FreeType(m_library);
205 return familyName;
208 std::string GetFontFamily(const std::string& filepath)
210 std::vector<uint8_t> buffer;
211 if (filepath.empty())
212 return "";
213 if (XFILE::CFile().LoadFile(filepath, buffer) <= 0)
215 CLog::LogF(LOGERROR, "Failed to load file {}", filepath);
216 return "";
218 return GetFontFamily(buffer);
221 bool IsSupportedFontExtension(const std::string& filepath)
223 return URIUtils::HasExtension(filepath, SUPPORTED_EXTENSIONS_MASK);
226 void ClearTemporaryFonts()
228 if (!CDirectory::Exists(FONTPATH::TEMP))
229 return;
231 CFileItemList items;
232 CDirectory::GetDirectory(FONTPATH::TEMP, items, SUPPORTED_EXTENSIONS_MASK,
233 DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_BYPASS_CACHE | DIR_FLAG_GET_HIDDEN);
234 for (const auto& item : items)
236 if (item->m_bIsFolder)
237 continue;
239 CFile::Delete(item->GetPath());
243 std::string FONTPATH::GetSystemFontPath(const std::string& filename)
245 std::string fontPath =
246 URIUtils::AddFileToFolder(CSpecialProtocol::TranslatePath(FONTPATH::SYSTEM), filename);
247 if (XFILE::CFile::Exists(fontPath))
249 return CSpecialProtocol::TranslatePath(fontPath);
252 CLog::LogF(LOGERROR, "Could not find application system font {}", filename);
253 return "";
256 } // namespace KODI::UTILS::FONT