27

我正在尝试创建一个字体列表供用户选择。我正在使用该EnumFontFamiliesEx函数来执行此操作,但不幸的是,返回的字体列表太长了。有许多额外的字体看起来很无聊、重复、用于不同的语言,或者以其他方式不希望向用户显示。我的屏幕截图最能说明我试图过滤掉的垃圾。

我的调用代码EnumFontFamiliesEx如下所示:

LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfCharSet = DEFAULT_CHARSET;
// screenDC is result of CreateCompatibleDC(NULL)
EnumFontFamiliesEx(screenDC, &lf, GetFontsCallback, NULL, 0);

在按字母顺序排序并删除具有重复面孔名称的字体后,结果列表如下所示:

在此处输入图像描述

如您所见,ChooseFont字体通用对话框显示了一个非常合理的字体列表,该列表对用户友好且有意义。另一方面,我的代码显示了一长串额外字体:以“@”开头的字体(为什么?它们甚至是为了什么?),Arial 字体的 3 种额外变体,以及其他几种用途不明的字体,如 Aheroni,安达卢斯、新悦椿、悦椿UPC等。这太疯狂了。

如何过滤返回的字体列表EnumFontFamiliesEx,使其与对话框中显示的列表完全匹配ChooseFont

4

4 回答 4

28

感谢 Jesse Good,我现在了解到 Windows 7 团队做出的一些疯狂的不幸设计决策。我不会接受我自己的答案,因为如果其他人想出一种在 Windows 7 中使用此隐藏字体功能的方法,即使注册表项尚不存在(例如,可能通过使用未记录的 API 或其他一些诡计)并且他们的回答有效,我会接受。

此过滤是通过在 Windows 7 控制面板中实际“隐藏”字体来完成的。默认情况下,其他语言环境的字体是隐藏的,但它们可以由用户显示。至少,这是这个想法。这是讨论此功能的 MSDN 页面: 国际字体管理

以下是此页面和 MSDN 中其他附近页面的一些关键摘录(另请参阅Windows中的http://msdn.microsoft.com/en-us/library/windows/desktop/dd371704(v=vs.85).aspx 7 兼容性食谱):

从 Windows 7 开始,字体管理基础结构支持隐藏不适合用户字体选择列表的字体。...此功能意味着用户不再需要面对一长串不合适的字体。

在 Windows 7 中,没有用于直接查询隐藏哪些字体或设置隐藏字体的 API。[强调我的] 如果您今天使用 Windows ChooseFont API(字体常用对话框)启用字体选择,您将免费获得新行为。Windows 7 中引入的新 Windows Scenic Ribbon(字体控件)也支持此行为,并为“Ribbonize”您的应用程序提供了另一个理由。

将字体选择到设备上下文中时,由于字体被隐藏,因此不会影响绘图。EnumFontFamiliesEx 函数继续枚举设置为隐藏的字体。[强调我的;显然没有办法用 EnumFontFamiliesEx 区分隐藏和可见字体]

请注意,字符集是对应于 Unicode 前字符集的遗留概念。[强调我的]

ChooseFont 将只列出显示的字体,并在列表框中显示字体时过滤掉隐藏的字体。在 CHOOSEFONT 结构的 flags 成员中添加了附加标志(CF_INACTIVEFONTS),以允许您在字体列表中显示所有已安装的字体,与 Windows 7 之前的 ChooseFont 行为相同。

换句话说,除非您使用 ChooseFont 通用对话框或官方的 Windows 功能区控件(仅在 Windows Vista/7 上可用),否则您根本没有支持过滤隐藏字体的方法。互联网上的许多用户都在抱怨在 Windows 7 控制面板中隐藏字体似乎没有效果,这有什么奇怪或奇怪的吗?!?(我之前错误地发布了 MS Word 2010 过滤掉隐藏字体。它似乎没有,因为它们使用自己的自定义功能区控件,而不是 Windows 内置的功能区。有趣的是,Windows 7 字体控制面板,按设计,与微软的一款旗舰产品不兼容,如果不在 Office 中倾倒更强大的功能区,就无法使其兼容。)

根据 Jesse Good 发布的链接,我了解到隐藏字体存储在未记录的注册表项中。通过此链接,以及使用 Process Monitor 进行的一些实验和分析(查看堆栈跟踪和注册表访问),我了解到以下内容:

  • 功能区控件调用 FMS.DLL(字体管理服务)中名为 FmsGetFilteredFontList 的未记录函数。它的目的似乎很明显。真可惜,他们懒得公开记录和维护它。
  • 这些设置存储在一个未记录的注册表项中,FMS.DLL 可以访问该注册表项。
  • 如果注册表项被删除,它会使用 FmsGetFilteredFontList 的默认设置重新创建,这将隐藏与当前输入语言无关的字体​​。
  • 在全新安装的 Windows 上创建的全新用户配置文件不包含与应隐藏哪些字体相关的任何注册表项。

因此,Jesse Good 发布的链接可能适用于许多/大多数情况,但不是 100% 的时间。如果它们不存在,您需要一种方法来可靠地重新创建这些注册表项(或至少假设默认值)。默认行为仍然是隐藏一些字体,即使注册表项已经消失(例如在新的用户配置文件上)。

于 2012-06-29T17:17:41.340 回答
4

鉴于 FmsGetFilteredFontList 未记录在案,您获得与用户在 Windows 7+ ChooseFont 对话框中看到的完全相同的列表的选项可能会受到限制。但是,仅使用记录在案的 API 可能会很好地接近默认字体列表。

为了减少自动选择适当字体的算法的可能性,我做了类似的事情。

我的方法是在FONTSIGNATURE中使用 Unicode 子范围位掩码,可以在枚举字体时对其进行检查。如果您知道需要哪个 Unicode 子范围,字体签名会告诉您当前字体是否覆盖它。如果是,请将其包含在列表中。如果没有,则跳过它。我怀疑这可能类似于 FmsGetFilteredFontList 构建其默认列表的方式。

诀窍是弄清楚用户需要什么子范围。就我而言,这相对容易,因为我确切地知道要渲染什么文本。我根据文档构建了子范围到 FONTSIGNATURE 样式的位掩码值的映射。

我扫描了要渲染的文本中的代码点,在映射中查找它们,并建立了一个目标位掩码。我将这个目标位掩码与每个枚举字体的字体签名中的位掩码进行位与运算。每当结果与目标位掩码匹配时,我就知道字体可以(很可能)支持文本。对于我的应用程序,我要求所有目标位都出现在字体中。对于您的应用程序,我认为您需要任何目标位。

字体签名位掩码是字体提供的字符的一个很好的初步选择。您可以完全确定地使用GetFontUnicodeRanges,但我发现这不是必需的,而且它也比仅检查字体签名要慢。

在您的情况下,也许您会以用户的语言提供一些具有代表性的文本字符串。例如,来自他们正在编辑的文档或来自已翻译的 UI 资源。您可以扫描该示例文本以获取目标字体签名。

例如,如果您扫描一些英文文本,您会发现所有必要的字符都在拉丁子范围内。如果您查看 Windows 7 中针对英语用户的字体控制面板小程序(并切换到详细信息视图),您会发现“显示/隐藏”列与“设计用于”列中是否列出拉丁语密切相关,这似乎是字体签名的 Unicode 子范围位掩码的文本表示。

更新:我刚刚尝试使用 DirectWrite 枚举字体,认为这个较新的 API 可能会处理字体隐藏功能。唉,它返回所有内容并且没有过滤隐藏字体的选项(我可以找到)。

于 2013-07-28T20:59:00.043 回答
2

老实说,Microsoft 没有记录此功能是可耻的,但我们对他们的期望越来越多。

过滤您自己的字体列表的另一种方法是通过枚举字体文件夹来利用 shell。如果您使用资源管理器查看,您会看到隐藏的字体带有一个幻影图标 - 我们可以使用该属性来判断字体是否隐藏。

例如(不完整):

LPITEMIDLIST pidlFonts;
if (SUCCEEDED(SHGetKnownFolderIDList(FOLDERID_Fonts, 0, nullptr, &pidlFonts)))
{
    CComPtr<IShellFolder> psf;
    if (SUCCEEDED(SHBindToObject(nullptr, pidlFonts, nullptr, IID_IShellFolder, reinterpret_cast<void**>(&psf))))
    {
        CComPtr<IEnumIDList> pEnum;
        if (SUCCEEDED(psf->EnumObjects(hWnd, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN | SHCONTF_INIT_ON_FIRST_NEXT, &pEnum)))
        {
            LPITEMIDLIST pidl;
            ULONG celt = 0;
            while (pEnum->Next(1, &pidl, &celt) == S_OK)
            {
                SFGAOF hidden = SFGAO_GHOSTED;
                if (SUCCEEDED(psf->GetAttributesOf(1, const_cast<LPCITEMIDLIST*>(&pidl), &hidden)) && (hidden & SFGAO_GHOSTED) == SFGAO_GHOSTED)
                {
                    // this font should be hidden
                    // get its name via IShellFolder::GetDisplayNameOf
                }
                CoTaskMemFree(pidl);
            }
        }
    }
    CoTaskMemFree(pidlFonts);
}

您可以使用此方法构建一组隐藏字体,然后使用它来过滤EnumFontFamiliesEx.

于 2014-07-25T05:06:20.413 回答
-3

我认为这里的整个讨论具有误导性。

当我向我的用户提供字体选择器时,我为什么要关心 Microsoft 隐藏了哪些字体?为什么我要隐藏微软认为应该默认隐藏的所有字体?

如果我的用户只想使用 Microsoft 隐藏的字体之一怎么办?我会让我的用户负担去控制面板取消隐藏这个字体吗?

如果有一天中国用户想在英文 Windows 上写中文文本并且中文字体被隐藏了怎么办?

我认为有一种更好的方法来限制EnumFontFamiliesEx().

我编写了自己的字体选择器,它有一个字体过滤器,允许用户选择他想要使用的字体组。这样我就不会隐藏任何东西,并将所有的权力交给用户而不是微软!

用户可能想查看所有字体!有时人们只需要 Arial Black 或 Arial Narrow,尽管 Microsoft 认为它应该被隐藏。

int CALLBACK EnumFontFamExProc(const LOGFONT* pk_Font, 
                               const TEXTMETRIC* pk_Metric, 
                               DWORD e_FontType, 
                               LPARAM lParam)
{
    if (e_FontType & TRUETYPE_FONTTYPE)
    {   
        // u32_Flags128 = DWORD[4] = 4 * 32 bit = 128 bit
        DWORD* u32_Flags128 = ((NEWTEXTMETRICEX*)pk_Metric)->ntmFontSig.fsUsb;

        if (u32_Flags128[13 / 32] & (1 << (13 % 32)))
        {
            // the font contains arabic characters (bit 13)
        }
        if (u32_Flags128[38 / 32] & (1 << (38 % 32)))
        {
            // the font contains mathematical symbols (bit 38)
        }
        if (u32_Flags128[70 / 32] & (1 << (70 % 32)))
        {
            // the font contains tibetan characters (bit 70)
        }
    }

在回调中,您会得到一个 128 位标志,它准确定义了字体支持的 Unicode 区域。

请参阅http://msdn.microsoft.com/en-us/library/dd374090%28v=vs.85%29.aspx

您可以使用这些 128 位来过滤和减少您在字体列表中显示的字体数量:

在此处输入图像描述

于 2014-07-27T00:51:41.687 回答