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;
auto ret = RegOpenKeyExW(hkey, fonts_key_name, 0, KEY_QUERY_VALUE, &key);
if (ret != ERROR_SUCCESS) return;
BOOST_SCOPE_EXIT_ALL(=) { RegCloseKey(key); };
DWORD name_buf_size = SHRT_MAX; /// @brief Normalize the case of a file path.
DWORD data_buf_size = MAX_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<HANDLE> hfile_sh(hfile, [](HANDLE hfile) { CloseHandle(hfile); });
auto font_name = new wchar_t[name_buf_size]; DWORD normalized_path_length = GetFinalPathNameByHandle(hfile_sh, nullptr, 0, FILE_NAME_NORMALIZED);
auto font_filename = new wchar_t[data_buf_size]; if (!normalized_path_length)
return L"";
for (DWORD i = 0;; ++i) { agi::scoped_holder<WCHAR*> normalized_path_sh(new WCHAR[normalized_path_length + 1], [](WCHAR* p) { delete[] p; });
retry: if (!GetFinalPathNameByHandle(hfile_sh, normalized_path_sh, normalized_path_length + 1, FILE_NAME_NORMALIZED))
DWORD name_len = name_buf_size; return L"";
DWORD data_len = data_buf_size;
ret = RegEnumValueW(key, i, font_name, &name_len, NULL, NULL, reinterpret_cast<BYTE*>(font_filename), &data_len); std::wstring normalized_path(normalized_path_sh);
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;
agi::fs::path font_path(font_filename); // GetFinalPathNameByHandle return path into ``device path`` form. Ex: "\\?\C:\Windows\Fonts\ariali.ttf"
if (!agi::fs::FileExists(font_path)) // We need to convert it to ``fully qualified DOS Path``. Ex: "C:\Windows\Fonts\ariali.ttf"
// Doesn't make a ton of sense to do this with user fonts, but they seem to be stored as full paths anyway // There isn't any public API that remove the prefix (there is RtlNtPathNameToDosPathName, but it is really hacky to use it)
font_path = font_dir / font_path; // See: https://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-result-without-prepended
if (agi::fs::FileExists(font_path)) // The path might simply be invalid // Even CPython remove the prefix manually: https://github.com/python/cpython/blob/963904335e579bfe39101adf3fd6a0cf705975ff/Lib/ntpath.py#L733-L793
files.push_back(font_path); // 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; return normalized_path;
delete font_filename;
} }
namespace { GdiFontFileLister::GdiFontFileLister(FontCollectorStatusCallback &)
uint32_t murmur3(const char *data, uint32_t len) { : dwrite_factory_sh(nullptr, [](IDWriteFactory* p) { p->Release(); })
static const uint32_t c1 = 0xcc9e2d51; , font_collection_sh(nullptr, [](IDWriteFontCollection* p) { p->Release(); })
static const uint32_t c2 = 0x1b873593; , dc_sh(nullptr, [](HDC dc) { DeleteDC(dc); })
static const uint32_t r1 = 15; , gdi_interop_sh(nullptr, [](IDWriteGdiInterop* p) { p->Release(); })
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;
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{}; LOGFONTW lf{};
lf.lfCharSet = DEFAULT_CHARSET; lf.lfCharSet = DEFAULT_CHARSET; // FIXME: Note that this currently ignores the font encoding specified in the ass file.
wcsncpy(lf.lfFaceName, agi::charset::ConvertW(facename).c_str(), LF_FACESIZE); wcsncpy_s(lf.lfFaceName, LF_FACESIZE, agi::charset::ConvertW(facename).c_str(), _TRUNCATE);
lf.lfItalic = italic ? -1 : 0; lf.lfItalic = italic ? -1 : 0;
lf.lfWeight = bold == 0 ? 400 : lf.lfWeight = weight;
bold == 1 ? 700 : lf.lfOutPrecision = OUT_TT_PRECIS;
bold; 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 agi::scoped_holder<HFONT> hfont_sh(CreateFontIndirect(&lf), [](HFONT p) { DeleteObject(p); });
std::vector<LOGFONTW> matches; if (hfont_sh == nullptr)
using type = decltype(matches);
EnumFontFamiliesEx(dc, &lf, [](const LOGFONT *lf, const TEXTMETRIC *, DWORD, LPARAM lParam) -> int {
reinterpret_cast<type*>(lParam)->push_back(*lf);
return 1;
}, (LPARAM)&matches, 0);
if (matches.empty())
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));
else {
utf16characters.push_back(U16_LEAD(chr));
utf16characters.push_back(U16_TRAIL(chr));
}
}
SCRIPT_CACHE cache = nullptr; IDWriteFontFace* font_face;
std::unique_ptr<WORD[]> indices(new WORD[utf16characters.size()]); if (FAILED(gdi_interop_sh->CreateFontFaceFromHdc(dc_sh, &font_face)))
return ret;
agi::scoped_holder<IDWriteFontFace*> font_face_sh(font_face, [](IDWriteFontFace* p) { p->Release(); });
// First try to check glyph coverage with Uniscribe, since it ret.fake_italic = font_face_sh->GetSimulations() & DWRITE_FONT_SIMULATIONS_OBLIQUE;
// handles non-BMP unicode characters ret.fake_bold = font_face_sh->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD;
auto hr = ScriptGetCMap(dc, &cache, utf16characters.data(),
utf16characters.size(), 0, indices.get());
// Uniscribe doesn't like some types of fonts, so fall back to GDI bool is_query_font_face_3_succeeded = false;
if (hr == E_HANDLE) { #ifdef HAVE_DWRITE_3
GetGlyphIndicesW(dc, utf16characters.data(), utf16characters.size(), // Fonts added via the AddFontResource API are not included in the IDWriteFontCollection.
indices.get(), GGI_MARK_NONEXISTING_GLYPHS); // This omission causes GetFontFromFontFace to fail.
for (size_t i = 0; i < utf16characters.size(); ++i) { // This issue is unavoidable on Windows 8 or lower.
if (U16_IS_SURROGATE(utf16characters[i])) // However, on Windows 10 or higher, we address this by querying IDWriteFontFace to IDWriteFontFace3.
continue; // From this new instance, we can verify font character(s) availability.
if (indices[i] == SHRT_MAX)
ret.missing += utf16characters[i]; 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(); });
else if (hr == S_FALSE) { is_query_font_face_3_succeeded = true;
for (size_t i = 0; i < utf16characters.size(); ++i) {
// Uniscribe doesn't report glyph indexes for non-BMP characters, for (int character : characters) {
// so we have to call ScriptGetCMap on each individual pair to if (!font_face_3_sh->HasCharacter((UINT32)character)) {
// determine if it's the missing one ret.missing += character;
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];
} }
} }
} }
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<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;
}
}
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<IDWriteFontFile*> font_file_sh(font_file, [](IDWriteFontFile* p) { p->Release(); });
IDWriteFontFileLoader* loader;
if (FAILED(font_file_sh->GetLoader(&loader)))
return ret;
agi::scoped_holder<IDWriteFontFileLoader*> 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<IDWriteLocalFontFileLoader*> 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; 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')