diff --git a/build/Aegisub/Aegisub.vcxproj b/build/Aegisub/Aegisub.vcxproj index 1474ef17b..efd4a71ea 100644 --- a/build/Aegisub/Aegisub.vcxproj +++ b/build/Aegisub/Aegisub.vcxproj @@ -353,7 +353,7 @@ - + diff --git a/build/Aegisub/Aegisub.vcxproj.filters b/build/Aegisub/Aegisub.vcxproj.filters index 458dc1fe4..d8c2da283 100644 --- a/build/Aegisub/Aegisub.vcxproj.filters +++ b/build/Aegisub/Aegisub.vcxproj.filters @@ -767,7 +767,7 @@ Features\Style editor - + Features\Font collector @@ -1093,4 +1093,4 @@ - \ No newline at end of file + diff --git a/src/font_file_lister.h b/src/font_file_lister.h index 7646552fd..daef55da6 100644 --- a/src/font_file_lister.h +++ b/src/font_file_lister.h @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -39,6 +40,30 @@ struct CollectionResult { bool fake_italic = false; }; +#ifdef _WIN32 +class GdiFontFileLister { + std::unordered_map index; + agi::scoped_holder dc; + std::string buffer; + + bool ProcessLogFont(LOGFONTW const& expected, LOGFONTW const& actual, std::vector const& characters); + +public: + /// Constructor + /// @param cb Callback for status logging + GdiFontFileLister(FontCollectorStatusCallback &cb); + + /// @brief Get the path to the font with the given styles + /// @param facename Name of font face + /// @param bold ASS font weight + /// @param italic Italic? + /// @param characters Characters in this style + /// @return Path to the matching font file(s), or empty if not found + CollectionResult GetFontPaths(std::string const& facename, int bold, bool italic, std::vector const& characters); +}; + +using FontFileLister = GdiFontFileLister; +#else typedef struct _FcConfig FcConfig; typedef struct _FcFontSet FcFontSet; @@ -67,6 +92,9 @@ public: CollectionResult GetFontPaths(std::string const& facename, int bold, bool italic, std::vector const& characters); }; +using FontFileLister = FontConfigFontFileLister; +#endif + /// @class FontCollector /// @brief Class which collects the paths to all fonts used in a script class FontCollector { @@ -88,7 +116,7 @@ class FontCollector { /// Message callback provider by caller FontCollectorStatusCallback status_callback; - FontConfigFontFileLister lister; + FontFileLister lister; /// The set of all glyphs used in the file std::map used_styles; diff --git a/src/font_file_lister_gdi.cpp b/src/font_file_lister_gdi.cpp new file mode 100644 index 000000000..82a46bde5 --- /dev/null +++ b/src/font_file_lister_gdi.cpp @@ -0,0 +1,165 @@ +// Copyright (c) 2016, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include "font_file_lister.h" + +#include +#include +#include +#include + +#include +#include + +namespace { +std::vector get_installed_fonts() { + static const auto fonts_key_name = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; + + std::vector files; + + HKEY key; + auto ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, fonts_key_name, 0, KEY_QUERY_VALUE, &key); + if (ret != ERROR_SUCCESS) return files; + BOOST_SCOPE_EXIT_ALL(=) { RegCloseKey(key); }; + + wchar_t fdir[MAX_PATH]; + SHGetFolderPathW(NULL, CSIDL_FONTS, NULL, 0, fdir); + agi::fs::path font_dir(fdir); + + for (DWORD i = 0;; ++i) { + wchar_t font_name[SHRT_MAX], font_filename[MAX_PATH]; + DWORD name_len = sizeof(font_name); + DWORD data_len = sizeof(font_filename); + + ret = RegEnumValueW(key, i, font_name, &name_len, NULL, NULL, reinterpret_cast(font_filename), &data_len); + if (ret == ERROR_NO_MORE_ITEMS) break; + if (ret != ERROR_SUCCESS) continue; + + agi::fs::path font_path(font_filename); + if (!agi::fs::FileExists(font_path)) + font_path = font_dir / font_path; + files.push_back(font_path); + } + + return files; +} + +std::string read_file(agi::fs::path const& path) { + std::string data; + auto stream = agi::io::Open(path, true); + stream->seekg(0, std::ios::end); + data.resize(stream->tellg()); + stream->seekg(0, std::ios::beg); + stream->read(&data[0], data.size()); + return data; +} + +using font_index = std::unordered_map; + +font_index index_fonts() { + font_index hash_to_path; + auto fonts = get_installed_fonts(); + for (auto const& path : fonts) { + hash_to_path[read_file(path)] = path; + } + 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); + index = index_fonts(); +} + +CollectionResult GdiFontFileLister::GetFontPaths(std::string const& facename, int bold, bool italic, std::vector const& characters) { + CollectionResult ret; + + LOGFONTW lf{}; + lf.lfCharSet = DEFAULT_CHARSET; + wcsncpy(lf.lfFaceName, agi::charset::ConvertW(facename).c_str(), LF_FACESIZE); + lf.lfItalic = italic; + lf.lfWeight = bold == 0 ? 400 : + bold == 1 ? 700 : + bold; + + auto cb = [&](LOGFONTW const& actual) { + auto hfont = CreateFontIndirectW(&actual); + SelectObject(dc, hfont); + BOOST_SCOPE_EXIT_ALL(=) { + SelectObject(dc, nullptr); + DeleteObject(hfont); + }; + + get_font_data(buffer, dc); + + auto it = index.find(buffer); + if (it == end(index)) + return false; // could instead write to a temp dir + + ret.paths.push_back(it->second); + if (actual.lfWeight < lf.lfWeight) + ret.fake_bold = true; + if (actual.lfItalic < lf.lfItalic) + ret.fake_italic = true; + + // Convert the characters to a utf-16 string + std::wstring utf16characters; + utf16characters.reserve(characters.size()); + for (int chr : characters) { + // GetGlyphIndices does not support surrogate pairs, so only check BMP characters + if (chr < std::numeric_limits::max()) + utf16characters.push_back(static_cast(chr)); + } + + std::unique_ptr indices(new WORD[utf16characters.size()]); + GetGlyphIndicesW(dc, utf16characters.data(), utf16characters.size(), + indices.get(), GGI_MARK_NONEXISTING_GLYPHS); + + for (size_t i = 0; i < utf16characters.size(); ++i) { + if (indices[i] == 0xFFFF) + ret.missing += utf16characters[i]; + } + + return true; + }; + + using type = decltype(cb); + bool found = !EnumFontFamiliesEx(dc, &lf, + [](const LOGFONT *lf, const TEXTMETRIC *, DWORD, LPARAM lParam) -> int { + return !(*reinterpret_cast(lParam))(*lf); + }, (LPARAM)&cb, 0); + + return ret; +}