Aegisub/src/font_file_lister.h

172 lines
5.9 KiB
C
Raw Normal View History

// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
//
// 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 <libaegisub/fs_fwd.h>
#include <libaegisub/scoped_ptr.h>
#include <boost/filesystem/path.hpp>
2012-09-25 01:35:27 +02:00
#include <functional>
#include <map>
#include <string>
#include <vector>
#include <unordered_map>
#include <wx/string.h>
class AssDialogue;
class AssFile;
2012-09-25 01:35:27 +02:00
typedef std::function<void (wxString, int)> FontCollectorStatusCallback;
struct CollectionResult {
/// Characters which could not be found in any font files
wxString missing;
/// Paths to the file(s) containing the requested font
std::vector<agi::fs::path> paths;
bool fake_bold = false;
bool fake_italic = false;
};
#ifdef _WIN32
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
2024-02-01 02:48:34 +01:00
#include <dwrite.h>
class GdiFontFileLister {
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
2024-02-01 02:48:34 +01:00
agi::scoped_holder<HDC> dc_sh;
agi::scoped_holder<IDWriteFactory*> dwrite_factory_sh;
agi::scoped_holder<IDWriteFontCollection*> font_collection_sh;
agi::scoped_holder<IDWriteGdiInterop*> gdi_interop_sh;
public:
/// Constructor
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
2024-02-01 02:48:34 +01:00
/// @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
/// @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<int> const& characters);
};
using FontFileLister = GdiFontFileLister;
#elif defined(__APPLE__)
struct CoreTextFontFileLister {
CoreTextFontFileLister(FontCollectorStatusCallback &) {}
/// @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<int> const& characters);
};
using FontFileLister = CoreTextFontFileLister;
#else
typedef struct _FcConfig FcConfig;
typedef struct _FcFontSet FcFontSet;
/// @class FontConfigFontFileLister
/// @brief fontconfig powered font lister
class FontConfigFontFileLister {
agi::scoped_holder<FcConfig*> config;
/// @brief Case-insensitive match ASS/SSA font family against full name. (also known as "name for humans")
/// @param family font fullname
/// @param bold weight attribute
/// @param italic italic attribute
/// @return font set
FcFontSet *MatchFullname(const char *family, int weight, int slant);
public:
/// Constructor
/// @param cb Callback for status logging
FontConfigFontFileLister(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<int> const& characters);
};
using FontFileLister = FontConfigFontFileLister;
#endif
/// @class FontCollector
/// @brief Class which collects the paths to all fonts used in a script
class FontCollector {
/// All data needed to find the font file used to render text
struct StyleInfo {
2012-12-30 01:32:36 +01:00
std::string facename;
int bold;
bool italic;
bool operator<(StyleInfo const& rgt) const;
};
/// Data about where each style is used
struct UsageData {
std::vector<int> chars; ///< Characters used in this style which glyphs will be needed for
bool drawing = false; ///< Whether this style is used for a drawing
std::vector<int> lines; ///< Lines on which this style is used via overrides
std::vector<std::string> styles; ///< ASS styles which use this style
};
/// Message callback provider by caller
FontCollectorStatusCallback status_callback;
FontFileLister lister;
/// The set of all glyphs used in the file
std::map<StyleInfo, UsageData> used_styles;
/// Style name -> ASS style definition
2012-12-30 01:32:36 +01:00
std::map<std::string, StyleInfo> styles;
/// Paths to found required font files
std::vector<agi::fs::path> results;
/// Number of fonts which could not be found
2013-12-12 01:29:48 +01:00
int missing = 0;
/// Number of fonts which were found, but did not contain all used glyphs
2013-12-12 01:29:48 +01:00
int missing_glyphs = 0;
/// Gather all of the unique styles with text on a line
void ProcessDialogueLine(const AssDialogue *line, int index);
/// Get the font for a single style
void ProcessChunk(std::pair<StyleInfo, UsageData> const& style);
/// Print the lines and styles on which a missing font is used
void PrintUsage(UsageData const& data);
public:
/// Constructor
/// @param status_callback Function to pass status updates to
/// @param lister The actual font file lister
FontCollector(FontCollectorStatusCallback status_callback);
/// @brief Get a list of the locations of all font files used in the file
/// @param file Lines in the subtitle file to check
/// @param status Callback function for messages
/// @return List of paths to fonts
std::vector<agi::fs::path> GetFontPaths(const AssFile *file);
};