From 897ca32c26829982cbb1328df8c6b94ca81ed2e6 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Tue, 19 Mar 2024 23:32:12 +0100 Subject: [PATCH 1/3] Allow setting Encoding=-1 in style editor --- src/ass_style.cpp | 1 + src/dialog_style_editor.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ass_style.cpp b/src/ass_style.cpp index 0b778b328..ad3b9ccc9 100644 --- a/src/ass_style.cpp +++ b/src/ass_style.cpp @@ -192,6 +192,7 @@ void AssStyle::UpdateData() { void AssStyle::GetEncodings(wxArrayString &encodingStrings) { encodingStrings.Clear(); + encodingStrings.Add(wxString("-1 - ") + _("Auto-detect base direction (libass only)")); encodingStrings.Add(wxString("0 - ") + _("ANSI")); encodingStrings.Add(wxString("1 - ") + _("Default")); encodingStrings.Add(wxString("2 - ") + _("Symbol")); diff --git a/src/dialog_style_editor.cpp b/src/dialog_style_editor.cpp index 856ef4b68..6092b6341 100644 --- a/src/dialog_style_editor.cpp +++ b/src/dialog_style_editor.cpp @@ -256,7 +256,7 @@ DialogStyleEditor::DialogStyleEditor(wxWindow *parent, AssStyle *style, agi::Con break; } } - if (!found) Encoding->Select(0); + if (!found) Encoding->Select(2); // Style name sizer NameSizer->Add(StyleName, 1, wxALL, 0); @@ -472,8 +472,10 @@ void DialogStyleEditor::UpdateWorkStyle() { work->font = from_wx(FontName->GetValue()); + wxString encoding_selection = Encoding->GetValue(); + wxString encoding_num = encoding_selection.substr(0, 1) + encoding_selection.substr(1).BeforeFirst('-'); // Have to account for -1 long templ = 0; - Encoding->GetValue().BeforeFirst('-').ToLong(&templ); + encoding_num.ToLong(&templ); work->encoding = templ; work->borderstyle = OutlineType->IsChecked() ? 3 : 1; From 7543060f1d8f008b3220850da7ad51dcb0cc11b0 Mon Sep 17 00:00:00 2001 From: moi15moi <80980684+moi15moi@users.noreply.github.com> Date: Wed, 31 Jan 2024 20:48:34 -0500 Subject: [PATCH 2/3] Rework Windows font collector (arch1t3cht/Aegisub#107) [src\meson.build] Add DirectWrite has dependency [src\font_file_lister_gdi] Rework GDI FontCollector to use DirectWrite This replaces all the logic of using the Windows registry to obtain the font path by using DirectWrite. The goal is simply to improve the quality of the code. This doesn't change any functionality [src\meson.build] Remove Uniscribe has dependency Uniscribe was only used for the FontCollector. Since we now use DirectWrite, we don't need it anymore. [src\dialog_fonts_collector] Catch exceptions that FontCollector may raise On Windows, the initialization of the FontCollector can raise an exception [src\font_file_lister] Document the exception that GdiFontFileLister can throw [src\font_file_lister_gdi] Correct possible memory leak when an error occur Fix error caused by AddFontResource on Windows 10 or higher [meson.build] Replace add_project_arguments with conf.set for HAVE_DWRITE_3 [src\dialog_fonts_collector] Update message error and optimisation [src\font_file_lister_gdi] Correct documentation typo [src\font_file_lister_gdi] Cosmetic nit - Initialize hfont in one line [src\font_file_lister_gdi] Cosmetic nit - Remove if statements brace [src\font_file_lister_gdi] Replace WCHAR param of normalizeFilePathCase to std::wstring [src\font_file_lister_gdi] Replace WCHAR by std::wstring [src\font_file_lister_gdi] Use IDWriteFontFace::GetSimulations to detect fake_italic/fake_bold See this comment: https://github.com/arch1t3cht/Aegisub/pull/107#issuecomment-1975229652 [src\font_file_lister_gdi] If Win7/8 has Win 10 SDK on compile time, correctly verify if font has character(s) With the Visual Studio 2019 toolchain on Windows 7, it installs the Windows 10 SDK by default. Because of this, ``HAVE_DWRITE_3`` is true, so the ``QueryInterface`` always fails. Now, if the ``QueryInterface`` fails, we try to verify if the font has characters with a Windows Vista SP2 compatible code. [src\font_file_lister_gdi] Support facename that contains only whitespace AND truncated facename Problem 1: Previously, if a user wrote "\fn ", it would return the font Arial, which is not what we want. This is because when we request EnumFontFamiliesEx with whitespace or an empty lfFaceName, it will enumerate all the installed fonts. Solution 1: To resolve this issue, let's implement a solution similar to libass to determine if the selected facename exists: https://github.com/libass/libass/blob/649a7c2e1fc6f4188ea1a89968560715800b883d/libass/ass_directwrite.c#L737-L747 Problem 2: GDI truncates font names to 31 characters. See: https://github.com/libass/libass/issues/459 However, since I changed the method to determine if a facename exists, I ensured that we still support this "feature". To test this, I used the font in: https://github.com/libass/libass/issues/710 [src\font_file_lister_gdi] Add a FIXME comment regarding the utilization of std::wstring over WCHAR [src\font_file_lister_gdi] Add FIXME comment about charset --- meson.build | 4 + src/dialog_fonts_collector.cpp | 9 +- src/font_file_lister.h | 14 +- src/font_file_lister_gdi.cpp | 409 +++++++++++++-------------------- src/meson.build | 6 +- 5 files changed, 184 insertions(+), 258 deletions(-) diff --git a/meson.build b/meson.build index 9029f5070..a018bee31 100644 --- a/meson.build +++ b/meson.build @@ -241,6 +241,10 @@ if host_machine.system() == 'windows' and not get_option('directsound').disabled endif endif +if host_machine.system() == 'windows' and cc.has_header('dwrite_3.h') + conf.set('HAVE_DWRITE_3', 1) +endif + if host_machine.system() == 'darwin' frameworks_dep = dependency('appleframeworks', modules : ['CoreText', 'CoreFoundation', 'AppKit', 'Carbon', 'IOKit']) deps += frameworks_dep diff --git a/src/dialog_fonts_collector.cpp b/src/dialog_fonts_collector.cpp index bc97c152d..e29526334 100644 --- a/src/dialog_fonts_collector.cpp +++ b/src/dialog_fonts_collector.cpp @@ -94,7 +94,14 @@ void FontsCollectorThread(AssFile *subs, agi::fs::path const& destination, FcMod collector->AddPendingEvent(ValueEvent(EVT_ADD_TEXT, -1, {colour, text.Clone()})); }; - auto paths = FontCollector(AppendText).GetFontPaths(subs); + std::vector paths; + try { + paths = FontCollector(AppendText).GetFontPaths(subs); + } + catch (agi::EnvironmentError const& err) { + AppendText(fmt_tl("* An error occurred when enumerating the used fonts: %s.\n", err.GetMessage()), 2); + } + if (paths.empty()) { collector->AddPendingEvent(wxThreadEvent(EVT_COLLECTION_DONE)); return; diff --git a/src/font_file_lister.h b/src/font_file_lister.h index 9c7cd51ad..29f54a3f7 100644 --- a/src/font_file_lister.h +++ b/src/font_file_lister.h @@ -41,17 +41,17 @@ struct CollectionResult { }; #ifdef _WIN32 +#include class GdiFontFileLister { - std::unordered_multimap index; - agi::scoped_holder dc; - std::string buffer; - - bool ProcessLogFont(LOGFONTW const& expected, LOGFONTW const& actual, std::vector const& characters); + agi::scoped_holder dc_sh; + agi::scoped_holder dwrite_factory_sh; + agi::scoped_holder font_collection_sh; + agi::scoped_holder gdi_interop_sh; public: /// Constructor - /// @param cb Callback for status logging - GdiFontFileLister(FontCollectorStatusCallback &cb); + /// @throws agi::EnvironmentError if an error occurs during construction. + GdiFontFileLister(FontCollectorStatusCallback &); /// @brief Get the path to the font with the given styles /// @param facename Name of font face diff --git a/src/font_file_lister_gdi.cpp b/src/font_file_lister_gdi.cpp index 14ff9261a..0d625f590 100644 --- a/src/font_file_lister_gdi.cpp +++ b/src/font_file_lister_gdi.cpp @@ -16,284 +16,199 @@ #include "font_file_lister.h" -#include "compat.h" - #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include -static void read_fonts_from_key(HKEY hkey, agi::fs::path font_dir, std::vector &files) { - static const auto fonts_key_name = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; - - HKEY key; - auto ret = RegOpenKeyExW(hkey, fonts_key_name, 0, KEY_QUERY_VALUE, &key); - if (ret != ERROR_SUCCESS) return; - BOOST_SCOPE_EXIT_ALL(=) { RegCloseKey(key); }; +#ifdef HAVE_DWRITE_3 +#include +#endif - DWORD name_buf_size = SHRT_MAX; - DWORD data_buf_size = MAX_PATH; +/// @brief Normalize the case of a file path. +/// @param path The path to be normalized. It can be a directory or a file. +/// @return A string representing the normalized path. +/// If the path normalization fails due to file handling errors or other issues, +/// an empty string is returned. +/// @example For "C:\WINDOWS\FONTS\ARIAL.TTF", it would return "C:\Windows\Fonts\arial.ttf" +std::wstring normalizeFilePathCase(const std::wstring path) { + /* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */ + HANDLE hfile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (hfile == INVALID_HANDLE_VALUE) + return L""; + agi::scoped_holder hfile_sh(hfile, [](HANDLE hfile) { CloseHandle(hfile); }); - auto font_name = new wchar_t[name_buf_size]; - auto font_filename = new wchar_t[data_buf_size]; + DWORD normalized_path_length = GetFinalPathNameByHandle(hfile_sh, nullptr, 0, FILE_NAME_NORMALIZED); + if (!normalized_path_length) + return L""; - for (DWORD i = 0;; ++i) { -retry: - DWORD name_len = name_buf_size; - DWORD data_len = data_buf_size; + agi::scoped_holder normalized_path_sh(new WCHAR[normalized_path_length + 1], [](WCHAR* p) { delete[] p; }); + if (!GetFinalPathNameByHandle(hfile_sh, normalized_path_sh, normalized_path_length + 1, FILE_NAME_NORMALIZED)) + return L""; - ret = RegEnumValueW(key, i, font_name, &name_len, NULL, NULL, reinterpret_cast(font_filename), &data_len); - if (ret == ERROR_MORE_DATA) { - data_buf_size = data_len; - delete font_filename; - font_filename = new wchar_t[data_buf_size]; - goto retry; - } - if (ret == ERROR_NO_MORE_ITEMS) break; - if (ret != ERROR_SUCCESS) continue; + std::wstring normalized_path(normalized_path_sh); - agi::fs::path font_path(font_filename); - if (!agi::fs::FileExists(font_path)) - // Doesn't make a ton of sense to do this with user fonts, but they seem to be stored as full paths anyway - font_path = font_dir / font_path; - if (agi::fs::FileExists(font_path)) // The path might simply be invalid - files.push_back(font_path); - } + // GetFinalPathNameByHandle return path into ``device path`` form. Ex: "\\?\C:\Windows\Fonts\ariali.ttf" + // We need to convert it to ``fully qualified DOS Path``. Ex: "C:\Windows\Fonts\ariali.ttf" + // There isn't any public API that remove the prefix (there is RtlNtPathNameToDosPathName, but it is really hacky to use it) + // See: https://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-result-without-prepended + // Even CPython remove the prefix manually: https://github.com/python/cpython/blob/963904335e579bfe39101adf3fd6a0cf705975ff/Lib/ntpath.py#L733-L793 + // Gecko: https://github.com/mozilla/gecko-dev/blob/6032a565e3be7dcdd01e4fe26791c84f9222a2e0/widget/windows/WinUtils.cpp#L1577-L1584 + if (normalized_path.compare(0, 7, L"\\\\?\\UNC") == 0) + normalized_path.erase(2, 6); + else if (normalized_path.compare(0, 4, L"\\\\?\\") == 0) + normalized_path.erase(0, 4); - delete font_name; - delete font_filename; + return normalized_path; } -namespace { -uint32_t murmur3(const char *data, uint32_t len) { - static const uint32_t c1 = 0xcc9e2d51; - static const uint32_t c2 = 0x1b873593; - static const uint32_t r1 = 15; - static const uint32_t r2 = 13; - static const uint32_t m = 5; - static const uint32_t n = 0xe6546b64; - - uint32_t hash = 0; - - const int nblocks = len / 4; - auto blocks = reinterpret_cast(data); - for (uint32_t i = 0; i * 4 < len; ++i) { - uint32_t k = blocks[i]; - k *= c1; - k = _rotl(k, r1); - k *= c2; - - hash ^= k; - hash = _rotl(hash, r2) * m + n; - } - - hash ^= len; - hash ^= hash >> 16; - hash *= 0x85ebca6b; - hash ^= hash >> 13; - hash *= 0xc2b2ae35; - hash ^= hash >> 16; - - return hash; -} - -std::vector get_installed_fonts() { - std::vector files; - - wchar_t fdir[MAX_PATH]; - SHGetFolderPathW(NULL, CSIDL_FONTS, NULL, 0, fdir); - agi::fs::path font_dir(fdir); - - // System fonts - read_fonts_from_key(HKEY_LOCAL_MACHINE, font_dir, files); - - // User fonts - read_fonts_from_key(HKEY_CURRENT_USER, font_dir, files); - - return files; -} - -using font_index = std::unordered_multimap; - -font_index index_fonts(FontCollectorStatusCallback &cb) { - font_index hash_to_path; - auto fonts = get_installed_fonts(); - std::unique_ptr buffer(new char[1024]); - for (auto const& path : fonts) { - try { - auto stream = agi::io::Open(path, true); - stream->read(&buffer[0], 1024); - auto hash = murmur3(&buffer[0], stream->tellg()); - hash_to_path.emplace(hash, path); - } - catch (agi::Exception const& e) { - cb(to_wx(e.GetMessage() + "\n"), 3); - } - } - return hash_to_path; -} - -void get_font_data(std::string& buffer, HDC dc) { - buffer.clear(); - - // For ttc files we have to ask for the "ttcf" table to get the complete file - DWORD ttcf = 0x66637474; - auto size = GetFontData(dc, ttcf, 0, nullptr, 0); - if (size == GDI_ERROR) { - ttcf = 0; - size = GetFontData(dc, 0, 0, nullptr, 0); - } - if (size == GDI_ERROR || size == 0) - return; - - buffer.resize(size); - GetFontData(dc, ttcf, 0, &buffer[0], size); -} -} - -GdiFontFileLister::GdiFontFileLister(FontCollectorStatusCallback &cb) -: dc(CreateCompatibleDC(nullptr), [](HDC dc) { DeleteDC(dc); }) +GdiFontFileLister::GdiFontFileLister(FontCollectorStatusCallback &) +: dwrite_factory_sh(nullptr, [](IDWriteFactory* p) { p->Release(); }) +, font_collection_sh(nullptr, [](IDWriteFontCollection* p) { p->Release(); }) +, dc_sh(nullptr, [](HDC dc) { DeleteDC(dc); }) +, gdi_interop_sh(nullptr, [](IDWriteGdiInterop* p) { p->Release(); }) { - cb(_("Updating font cache\n"), 0); - index = index_fonts(cb); + IDWriteFactory* dwrite_factory; + if (FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&dwrite_factory)))) + throw agi::EnvironmentError("Failed to initialize the DirectWrite Factory"); + dwrite_factory_sh = dwrite_factory; + + IDWriteFontCollection* font_collection; + if (FAILED(dwrite_factory_sh->GetSystemFontCollection(&font_collection, true))) + throw agi::EnvironmentError("Failed to initialize the system font collection"); + font_collection_sh = font_collection; + + HDC dc = CreateCompatibleDC(nullptr); + if (dc == nullptr) + throw agi::EnvironmentError("Failed to initialize the HDC"); + dc_sh = dc; + + IDWriteGdiInterop* gdi_interop; + if (FAILED(dwrite_factory_sh->GetGdiInterop(&gdi_interop))) + throw agi::EnvironmentError("Failed to initialize the Gdi Interop"); + gdi_interop_sh = gdi_interop; } CollectionResult GdiFontFileLister::GetFontPaths(std::string const& facename, int bold, bool italic, std::vector const& characters) { CollectionResult ret; + int weight = bold == 0 ? 400 : + bold == 1 ? 700 : + bold; + + // From VSFilter + // - https://sourceforge.net/p/guliverkli2/code/HEAD/tree/src/subtitles/RTS.cpp#l45 + // - https://sourceforge.net/p/guliverkli2/code/HEAD/tree/src/subtitles/STS.cpp#l2992 LOGFONTW lf{}; - lf.lfCharSet = DEFAULT_CHARSET; - wcsncpy(lf.lfFaceName, agi::charset::ConvertW(facename).c_str(), LF_FACESIZE); + lf.lfCharSet = DEFAULT_CHARSET; // FIXME: Note that this currently ignores the font encoding specified in the ass file. + wcsncpy_s(lf.lfFaceName, LF_FACESIZE, agi::charset::ConvertW(facename).c_str(), _TRUNCATE); lf.lfItalic = italic ? -1 : 0; - lf.lfWeight = bold == 0 ? 400 : - bold == 1 ? 700 : - bold; + lf.lfWeight = weight; + lf.lfOutPrecision = OUT_TT_PRECIS; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfQuality = ANTIALIASED_QUALITY; + lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; - // Gather all of the styles for the given family name - std::vector matches; - using type = decltype(matches); - EnumFontFamiliesEx(dc, &lf, [](const LOGFONT *lf, const TEXTMETRIC *, DWORD, LPARAM lParam) -> int { - reinterpret_cast(lParam)->push_back(*lf); - return 1; - }, (LPARAM)&matches, 0); - - if (matches.empty()) + agi::scoped_holder hfont_sh(CreateFontIndirect(&lf), [](HFONT p) { DeleteObject(p); }); + if (hfont_sh == nullptr) return ret; - // If the user asked for a non-regular style, verify that it actually exists - if (italic || bold) { - bool has_bold = false; - bool has_italic = false; - bool has_bold_italic = false; + SelectFont(dc_sh, hfont_sh); - auto is_italic = [&](LOGFONTW const& lf) { - return !italic || lf.lfItalic; - }; - auto is_bold = [&](LOGFONTW const& lf) { - return !bold - || (bold == 1 && lf.lfWeight >= 700) - || (bold > 1 && lf.lfWeight > bold); - }; - - for (auto const& match : matches) { - has_bold = has_bold || is_bold(match); - has_italic = has_italic || is_italic(match); - has_bold_italic = has_bold_italic || (is_bold(match) && is_italic(match)); - } - - ret.fake_italic = !has_italic; - ret.fake_bold = (italic && has_italic ? !has_bold_italic : !has_bold); - } - - // Use the family name supplied by EnumFontFamiliesEx as it may be a localized version - memcpy(lf.lfFaceName, matches[0].lfFaceName, LF_FACESIZE); - - // Open the font and get the data for it to look up in the index - auto hfont = CreateFontIndirectW(&lf); - SelectObject(dc, hfont); - BOOST_SCOPE_EXIT_ALL(=) { - SelectObject(dc, nullptr); - DeleteObject(hfont); - }; - - get_font_data(buffer, dc); - - auto range = index.equal_range(murmur3(buffer.c_str(), std::min(buffer.size(), 1024U))); - if (range.first == range.second) - return ret; // could instead write to a temp dir - - // Compare the full files for each of the fonts with the same prefix - std::unique_ptr file_buffer(new char[buffer.size()]); - for (auto it = range.first; it != range.second; ++it) { - auto stream = agi::io::Open(it->second, true); - stream->read(&file_buffer[0], buffer.size()); - if ((size_t)stream->tellg() != buffer.size()) - continue; - if (memcmp(&file_buffer[0], &buffer[0], buffer.size()) == 0) { - ret.paths.push_back(it->second); - break; - } - } - - // No fonts actually matched - if (ret.paths.empty()) + std::wstring selected_name(LF_FACESIZE - 1, L'\0'); + // FIXME: This will override the string's terminator, which is not technically correct. + // After switching to C++20 this should use .data(). + if (!GetTextFaceW(dc_sh, LF_FACESIZE, &selected_name[0])) return ret; - // Convert the characters to a utf-16 string - std::wstring utf16characters; - utf16characters.reserve(characters.size()); - for (int chr : characters) { - if (U16_LENGTH(chr) == 1) - utf16characters.push_back(static_cast(chr)); - else { - utf16characters.push_back(U16_LEAD(chr)); - utf16characters.push_back(U16_TRAIL(chr)); - } - } + // If the selected_name is different then the lf.lfFaceName, + // it means that the requested font doesn't exist. + if (_wcsnicmp(&selected_name[0], lf.lfFaceName, LF_FACESIZE)) + return ret; - SCRIPT_CACHE cache = nullptr; - std::unique_ptr indices(new WORD[utf16characters.size()]); + IDWriteFontFace* font_face; + if (FAILED(gdi_interop_sh->CreateFontFaceFromHdc(dc_sh, &font_face))) + return ret; + agi::scoped_holder font_face_sh(font_face, [](IDWriteFontFace* p) { p->Release(); }); - // First try to check glyph coverage with Uniscribe, since it - // handles non-BMP unicode characters - auto hr = ScriptGetCMap(dc, &cache, utf16characters.data(), - utf16characters.size(), 0, indices.get()); + ret.fake_italic = font_face_sh->GetSimulations() & DWRITE_FONT_SIMULATIONS_OBLIQUE; + ret.fake_bold = font_face_sh->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD; - // Uniscribe doesn't like some types of fonts, so fall back to GDI - if (hr == E_HANDLE) { - GetGlyphIndicesW(dc, utf16characters.data(), utf16characters.size(), - indices.get(), GGI_MARK_NONEXISTING_GLYPHS); - for (size_t i = 0; i < utf16characters.size(); ++i) { - if (U16_IS_SURROGATE(utf16characters[i])) - continue; - if (indices[i] == SHRT_MAX) - ret.missing += utf16characters[i]; - } - } - else if (hr == S_FALSE) { - for (size_t i = 0; i < utf16characters.size(); ++i) { - // Uniscribe doesn't report glyph indexes for non-BMP characters, - // so we have to call ScriptGetCMap on each individual pair to - // determine if it's the missing one - if (U16_IS_LEAD(utf16characters[i])) { - hr = ScriptGetCMap(dc, &cache, &utf16characters[i], 2, 0, &indices[i]); - if (hr == S_FALSE) { - ret.missing += utf16characters[i]; - ret.missing += utf16characters[i + 1]; - } - ++i; - } - else if (indices[i] == 0) { - ret.missing += utf16characters[i]; + bool is_query_font_face_3_succeeded = false; +#ifdef HAVE_DWRITE_3 + // Fonts added via the AddFontResource API are not included in the IDWriteFontCollection. + // This omission causes GetFontFromFontFace to fail. + // This issue is unavoidable on Windows 8 or lower. + // However, on Windows 10 or higher, we address this by querying IDWriteFontFace to IDWriteFontFace3. + // From this new instance, we can verify font character(s) availability. + + IDWriteFontFace3* font_face_3; + if (SUCCEEDED(font_face_sh->QueryInterface(__uuidof(IDWriteFontFace3), (void**)&font_face_3))) { + agi::scoped_holder font_face_3_sh(font_face_3, [](IDWriteFontFace3* p) { p->Release(); }); + is_query_font_face_3_succeeded = true; + + for (int character : characters) { + if (!font_face_3_sh->HasCharacter((UINT32)character)) { + ret.missing += character; } } } - ScriptFreeCache(&cache); +#endif + + if (!is_query_font_face_3_succeeded) { + IDWriteFont* font; + if (FAILED(font_collection_sh->GetFontFromFontFace(font_face_sh, &font))) + return ret; + agi::scoped_holder font_sh(font, [](IDWriteFont* p) { p->Release(); }); + + BOOL exists; + HRESULT hr; + for (int character : characters) { + hr = font_sh->HasCharacter((UINT32)character, &exists); + if (FAILED(hr) || !exists) + ret.missing += character; + } + } + + UINT32 file_count = 1; + IDWriteFontFile* font_file; + // DirectWrite only supports one file per face + if (FAILED(font_face_sh->GetFiles(&file_count, &font_file))) + return ret; + agi::scoped_holder font_file_sh(font_file, [](IDWriteFontFile* p) { p->Release(); }); + + IDWriteFontFileLoader* loader; + if (FAILED(font_file_sh->GetLoader(&loader))) + return ret; + agi::scoped_holder loader_sh(loader, [](IDWriteFontFileLoader* p) { p->Release(); }); + + IDWriteLocalFontFileLoader* local_loader; + if (FAILED(loader_sh->QueryInterface(__uuidof(IDWriteLocalFontFileLoader), (void**)&local_loader))) + return ret; + agi::scoped_holder local_loader_sh(local_loader, [](IDWriteLocalFontFileLoader* p) { p->Release(); }); + + LPCVOID font_file_reference_key; + UINT32 font_file_reference_key_size; + if (FAILED(font_file_sh->GetReferenceKey(&font_file_reference_key, &font_file_reference_key_size))) + return ret; + + UINT32 path_length; + if (FAILED(local_loader_sh->GetFilePathLengthFromKey(font_file_reference_key, font_file_reference_key_size, &path_length))) + return ret; + + std::wstring path(path_length, L'\0'); + // FIXME: This will override the string's terminator, which is not technically correct. + // After switching to C++20 this should use .data(). + if (FAILED(local_loader_sh->GetFilePathFromKey(font_file_reference_key, font_file_reference_key_size, &path[0], path_length + 1))) + return ret; + + // DirectWrite always return the file path in upper case. Ex: "C:\WINDOWS\FONTS\ARIAL.TTF" + std::wstring normalized_path = normalizeFilePathCase(path); + if (normalized_path.empty()) + return ret; + + ret.paths.push_back(agi::fs::path(normalized_path)); return ret; } diff --git a/src/meson.build b/src/meson.build index 72587d366..b0a832380 100644 --- a/src/meson.build +++ b/src/meson.build @@ -182,10 +182,10 @@ elif host_machine.system() == 'windows' else error('Missing Windows SDK GDI Library (wingdi.h / gdi32.lib)') endif - if cc.has_header('usp10.h') - deps += cc.find_library('usp10', required: true) + if cc.has_header('dwrite.h') + deps += cc.find_library('dwrite', required: true) else - error('Missing Windows SDK Uniscribe Library (usp10.h / usp10.lib)') + error('Missing Windows SDK DirectWrite Library (dwrite.h / dwrite.lib)') endif res_inc = include_directories('bitmaps/windows') From 4b20bc7df0ee70327a82b900e6f9ae1bdf0fd917 Mon Sep 17 00:00:00 2001 From: Ryan Lucia Date: Fri, 24 Nov 2023 12:58:16 -0500 Subject: [PATCH 3/3] ci: try to workaround github actions bug --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6eec487d8..5bc460d52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,7 +116,10 @@ jobs: - name: Install dependencies (MacOS) if: matrix.config.os == 'macos-latest' run: | - brew update + export HOMEBREW_NO_INSTALL_CLEANUP=1 + export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 + # Skip brew update for now, see https://github.com/actions/setup-python/issues/577 + # brew update brew install luarocks ninja luarocks install luafilesystem 1.8.0 luarocks install moonscript --dev