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: 649a7c2e1f/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
This commit is contained in:
moi15moi 2024-01-31 20:48:34 -05:00 committed by arch1t3cht
parent 897ca32c26
commit 7543060f1d
5 changed files with 184 additions and 258 deletions

View File

@ -241,6 +241,10 @@ if host_machine.system() == 'windows' and not get_option('directsound').disabled
endif endif
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' if host_machine.system() == 'darwin'
frameworks_dep = dependency('appleframeworks', modules : ['CoreText', 'CoreFoundation', 'AppKit', 'Carbon', 'IOKit']) frameworks_dep = dependency('appleframeworks', modules : ['CoreText', 'CoreFoundation', 'AppKit', 'Carbon', 'IOKit'])
deps += frameworks_dep deps += frameworks_dep

View File

@ -94,7 +94,14 @@ void FontsCollectorThread(AssFile *subs, agi::fs::path const& destination, FcMod
collector->AddPendingEvent(ValueEvent<color_str_pair>(EVT_ADD_TEXT, -1, {colour, text.Clone()})); collector->AddPendingEvent(ValueEvent<color_str_pair>(EVT_ADD_TEXT, -1, {colour, text.Clone()}));
}; };
auto paths = FontCollector(AppendText).GetFontPaths(subs); std::vector<agi::fs::path> 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()) { if (paths.empty()) {
collector->AddPendingEvent(wxThreadEvent(EVT_COLLECTION_DONE)); collector->AddPendingEvent(wxThreadEvent(EVT_COLLECTION_DONE));
return; return;

View File

@ -41,17 +41,17 @@ struct CollectionResult {
}; };
#ifdef _WIN32 #ifdef _WIN32
#include <dwrite.h>
class GdiFontFileLister { class GdiFontFileLister {
std::unordered_multimap<uint32_t, agi::fs::path> index; agi::scoped_holder<HDC> dc_sh;
agi::scoped_holder<HDC> dc; agi::scoped_holder<IDWriteFactory*> dwrite_factory_sh;
std::string buffer; agi::scoped_holder<IDWriteFontCollection*> font_collection_sh;
agi::scoped_holder<IDWriteGdiInterop*> gdi_interop_sh;
bool ProcessLogFont(LOGFONTW const& expected, LOGFONTW const& actual, std::vector<int> const& characters);
public: public:
/// Constructor /// Constructor
/// @param cb Callback for status logging /// @throws agi::EnvironmentError if an error occurs during construction.
GdiFontFileLister(FontCollectorStatusCallback &cb); GdiFontFileLister(FontCollectorStatusCallback &);
/// @brief Get the path to the font with the given styles /// @brief Get the path to the font with the given styles
/// @param facename Name of font face /// @param facename Name of font face

View File

@ -16,284 +16,199 @@
#include "font_file_lister.h" #include "font_file_lister.h"
#include "compat.h"
#include <libaegisub/charset_conv_win.h> #include <libaegisub/charset_conv_win.h>
#include <libaegisub/fs.h>
#include <libaegisub/io.h>
#include <libaegisub/log.h>
#include <ShlObj.h> #include <dwrite.h>
#include <boost/scope_exit.hpp> #include <wchar.h>
#include <unicode/utf16.h> #include <windowsx.h>
#include <Usp10.h>
static void read_fonts_from_key(HKEY hkey, agi::fs::path font_dir, std::vector<agi::fs::path> &files) { #ifdef HAVE_DWRITE_3
static const auto fonts_key_name = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; #include <dwrite_3.h>
#endif
HKEY key; /// @brief Normalize the case of a file path.
auto ret = RegOpenKeyExW(hkey, fonts_key_name, 0, KEY_QUERY_VALUE, &key); /// @param path The path to be normalized. It can be a directory or a file.
if (ret != ERROR_SUCCESS) return; /// @return A string representing the normalized path.
BOOST_SCOPE_EXIT_ALL(=) { RegCloseKey(key); }; /// 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<HANDLE> hfile_sh(hfile, [](HANDLE hfile) { CloseHandle(hfile); });
DWORD name_buf_size = SHRT_MAX; DWORD normalized_path_length = GetFinalPathNameByHandle(hfile_sh, nullptr, 0, FILE_NAME_NORMALIZED);
DWORD data_buf_size = MAX_PATH; if (!normalized_path_length)
return L"";
auto font_name = new wchar_t[name_buf_size]; agi::scoped_holder<WCHAR*> normalized_path_sh(new WCHAR[normalized_path_length + 1], [](WCHAR* p) { delete[] p; });
auto font_filename = new wchar_t[data_buf_size]; if (!GetFinalPathNameByHandle(hfile_sh, normalized_path_sh, normalized_path_length + 1, FILE_NAME_NORMALIZED))
return L"";
for (DWORD i = 0;; ++i) { std::wstring normalized_path(normalized_path_sh);
retry:
DWORD name_len = name_buf_size;
DWORD data_len = data_buf_size;
ret = RegEnumValueW(key, i, font_name, &name_len, NULL, NULL, reinterpret_cast<BYTE*>(font_filename), &data_len); // GetFinalPathNameByHandle return path into ``device path`` form. Ex: "\\?\C:\Windows\Fonts\ariali.ttf"
if (ret == ERROR_MORE_DATA) { // We need to convert it to ``fully qualified DOS Path``. Ex: "C:\Windows\Fonts\ariali.ttf"
data_buf_size = data_len; // There isn't any public API that remove the prefix (there is RtlNtPathNameToDosPathName, but it is really hacky to use it)
delete font_filename; // See: https://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-result-without-prepended
font_filename = new wchar_t[data_buf_size]; // Even CPython remove the prefix manually: https://github.com/python/cpython/blob/963904335e579bfe39101adf3fd6a0cf705975ff/Lib/ntpath.py#L733-L793
goto retry; // Gecko: https://github.com/mozilla/gecko-dev/blob/6032a565e3be7dcdd01e4fe26791c84f9222a2e0/widget/windows/WinUtils.cpp#L1577-L1584
} if (normalized_path.compare(0, 7, L"\\\\?\\UNC") == 0)
if (ret == ERROR_NO_MORE_ITEMS) break; normalized_path.erase(2, 6);
if (ret != ERROR_SUCCESS) continue; else if (normalized_path.compare(0, 4, L"\\\\?\\") == 0)
normalized_path.erase(0, 4);
agi::fs::path font_path(font_filename); return normalized_path;
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);
} }
delete font_name; GdiFontFileLister::GdiFontFileLister(FontCollectorStatusCallback &)
delete font_filename; : dwrite_factory_sh(nullptr, [](IDWriteFactory* p) { p->Release(); })
} , font_collection_sh(nullptr, [](IDWriteFontCollection* p) { p->Release(); })
, dc_sh(nullptr, [](HDC dc) { DeleteDC(dc); })
namespace { , gdi_interop_sh(nullptr, [](IDWriteGdiInterop* p) { p->Release(); })
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<const uint32_t *>(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<agi::fs::path> get_installed_fonts() {
std::vector<agi::fs::path> 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<uint32_t, agi::fs::path>;
font_index index_fonts(FontCollectorStatusCallback &cb) {
font_index hash_to_path;
auto fonts = get_installed_fonts();
std::unique_ptr<char[]> 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); })
{ {
cb(_("Updating font cache\n"), 0); IDWriteFactory* dwrite_factory;
index = index_fonts(cb); if (FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&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<int> const& characters) { CollectionResult GdiFontFileLister::GetFontPaths(std::string const& facename, int bold, bool italic, std::vector<int> const& characters) {
CollectionResult ret; CollectionResult ret;
LOGFONTW lf{}; int weight = bold == 0 ? 400 :
lf.lfCharSet = DEFAULT_CHARSET;
wcsncpy(lf.lfFaceName, agi::charset::ConvertW(facename).c_str(), LF_FACESIZE);
lf.lfItalic = italic ? -1 : 0;
lf.lfWeight = bold == 0 ? 400 :
bold == 1 ? 700 : bold == 1 ? 700 :
bold; bold;
// Gather all of the styles for the given family name // From VSFilter
std::vector<LOGFONTW> matches; // - https://sourceforge.net/p/guliverkli2/code/HEAD/tree/src/subtitles/RTS.cpp#l45
using type = decltype(matches); // - https://sourceforge.net/p/guliverkli2/code/HEAD/tree/src/subtitles/STS.cpp#l2992
EnumFontFamiliesEx(dc, &lf, [](const LOGFONT *lf, const TEXTMETRIC *, DWORD, LPARAM lParam) -> int { LOGFONTW lf{};
reinterpret_cast<type*>(lParam)->push_back(*lf); lf.lfCharSet = DEFAULT_CHARSET; // FIXME: Note that this currently ignores the font encoding specified in the ass file.
return 1; wcsncpy_s(lf.lfFaceName, LF_FACESIZE, agi::charset::ConvertW(facename).c_str(), _TRUNCATE);
}, (LPARAM)&matches, 0); lf.lfItalic = italic ? -1 : 0;
lf.lfWeight = weight;
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfQuality = ANTIALIASED_QUALITY;
lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
if (matches.empty()) agi::scoped_holder<HFONT> hfont_sh(CreateFontIndirect(&lf), [](HFONT p) { DeleteObject(p); });
if (hfont_sh == nullptr)
return ret; return ret;
// If the user asked for a non-regular style, verify that it actually exists SelectFont(dc_sh, hfont_sh);
if (italic || bold) {
bool has_bold = false;
bool has_italic = false;
bool has_bold_italic = false;
auto is_italic = [&](LOGFONTW const& lf) { std::wstring selected_name(LF_FACESIZE - 1, L'\0');
return !italic || lf.lfItalic; // FIXME: This will override the string's terminator, which is not technically correct.
}; // After switching to C++20 this should use .data().
auto is_bold = [&](LOGFONTW const& lf) { if (!GetTextFaceW(dc_sh, LF_FACESIZE, &selected_name[0]))
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<size_t>(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<char[]> 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())
return ret; return ret;
// Convert the characters to a utf-16 string // If the selected_name is different then the lf.lfFaceName,
std::wstring utf16characters; // it means that the requested font doesn't exist.
utf16characters.reserve(characters.size()); if (_wcsnicmp(&selected_name[0], lf.lfFaceName, LF_FACESIZE))
for (int chr : characters) { return ret;
if (U16_LENGTH(chr) == 1)
utf16characters.push_back(static_cast<wchar_t>(chr)); IDWriteFontFace* font_face;
else { if (FAILED(gdi_interop_sh->CreateFontFaceFromHdc(dc_sh, &font_face)))
utf16characters.push_back(U16_LEAD(chr)); return ret;
utf16characters.push_back(U16_TRAIL(chr)); agi::scoped_holder<IDWriteFontFace*> font_face_sh(font_face, [](IDWriteFontFace* p) { p->Release(); });
ret.fake_italic = font_face_sh->GetSimulations() & DWRITE_FONT_SIMULATIONS_OBLIQUE;
ret.fake_bold = font_face_sh->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD;
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<IDWriteFontFace3*> 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;
}
}
}
#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<IDWriteFont*> 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;
} }
} }
SCRIPT_CACHE cache = nullptr; UINT32 file_count = 1;
std::unique_ptr<WORD[]> indices(new WORD[utf16characters.size()]); 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<IDWriteFontFile*> font_file_sh(font_file, [](IDWriteFontFile* p) { p->Release(); });
// First try to check glyph coverage with Uniscribe, since it IDWriteFontFileLoader* loader;
// handles non-BMP unicode characters if (FAILED(font_file_sh->GetLoader(&loader)))
auto hr = ScriptGetCMap(dc, &cache, utf16characters.data(), return ret;
utf16characters.size(), 0, indices.get()); agi::scoped_holder<IDWriteFontFileLoader*> loader_sh(loader, [](IDWriteFontFileLoader* p) { p->Release(); });
// Uniscribe doesn't like some types of fonts, so fall back to GDI IDWriteLocalFontFileLoader* local_loader;
if (hr == E_HANDLE) { if (FAILED(loader_sh->QueryInterface(__uuidof(IDWriteLocalFontFileLoader), (void**)&local_loader)))
GetGlyphIndicesW(dc, utf16characters.data(), utf16characters.size(), return ret;
indices.get(), GGI_MARK_NONEXISTING_GLYPHS); agi::scoped_holder<IDWriteLocalFontFileLoader*> local_loader_sh(local_loader, [](IDWriteLocalFontFileLoader* p) { p->Release(); });
for (size_t i = 0; i < utf16characters.size(); ++i) {
if (U16_IS_SURROGATE(utf16characters[i])) LPCVOID font_file_reference_key;
continue; UINT32 font_file_reference_key_size;
if (indices[i] == SHRT_MAX) if (FAILED(font_file_sh->GetReferenceKey(&font_file_reference_key, &font_file_reference_key_size)))
ret.missing += utf16characters[i]; return ret;
}
} UINT32 path_length;
else if (hr == S_FALSE) { if (FAILED(local_loader_sh->GetFilePathLengthFromKey(font_file_reference_key, font_file_reference_key_size, &path_length)))
for (size_t i = 0; i < utf16characters.size(); ++i) { return ret;
// Uniscribe doesn't report glyph indexes for non-BMP characters,
// so we have to call ScriptGetCMap on each individual pair to std::wstring path(path_length, L'\0');
// determine if it's the missing one // FIXME: This will override the string's terminator, which is not technically correct.
if (U16_IS_LEAD(utf16characters[i])) { // After switching to C++20 this should use .data().
hr = ScriptGetCMap(dc, &cache, &utf16characters[i], 2, 0, &indices[i]); if (FAILED(local_loader_sh->GetFilePathFromKey(font_file_reference_key, font_file_reference_key_size, &path[0], path_length + 1)))
if (hr == S_FALSE) { return ret;
ret.missing += utf16characters[i];
ret.missing += utf16characters[i + 1]; // DirectWrite always return the file path in upper case. Ex: "C:\WINDOWS\FONTS\ARIAL.TTF"
} std::wstring normalized_path = normalizeFilePathCase(path);
++i; if (normalized_path.empty())
} return ret;
else if (indices[i] == 0) {
ret.missing += utf16characters[i]; ret.paths.push_back(agi::fs::path(normalized_path));
}
}
}
ScriptFreeCache(&cache);
return ret; return ret;
} }

View File

@ -182,10 +182,10 @@ elif host_machine.system() == 'windows'
else else
error('Missing Windows SDK GDI Library (wingdi.h / gdi32.lib)') error('Missing Windows SDK GDI Library (wingdi.h / gdi32.lib)')
endif endif
if cc.has_header('usp10.h') if cc.has_header('dwrite.h')
deps += cc.find_library('usp10', required: true) deps += cc.find_library('dwrite', required: true)
else else
error('Missing Windows SDK Uniscribe Library (usp10.h / usp10.lib)') error('Missing Windows SDK DirectWrite Library (dwrite.h / dwrite.lib)')
endif endif
res_inc = include_directories('bitmaps/windows') res_inc = include_directories('bitmaps/windows')