使用EnumFontFamiliesEx函数枚举时字体太多

时间:2012-06-28 22:30:13

标签: c++ c windows winapi fonts

我正在尝试创建一个字体列表供用户选择。我是通过使用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);

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

enter image description here

正如您所看到的,ChooseFont字体常用对话框显示了一个非常合理的字体列表,这些字体用户友好且有意义。另一方面,我的代码显示了一长串额外字体:以“@”开头的字体(为什么?它们甚至用于什么?),3种额外的Arial字体变体,以及其他一些未知用途的字体,如Aheroni, Andalus,Angsana New,AngsanaUPC等。这太疯狂了。

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

4 个答案:

答案 0 :(得分:26)

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

此过滤是通过在Windows 7控制面板中实际“隐藏”字体来完成的。默认情况下,其他语言环境的字体会被隐藏,但用户可以显示这些字体。至少,这是个主意。以下是讨论此功能的MSDN页面:International Font Management

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

  

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

     

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

     

当在设备上下文中选择字体时,由于字体被隐藏,对绘图没有影响。 EnumFontFamiliesEx函数继续枚举设置为隐藏的字体。 [强调我的;显然没有办法用EnumFontFamiliesEx]

来区分隐藏和可见的字体      

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

     

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

换句话说,除非您使用ChooseFont常用对话框或官方Windows功能区控件(仅适用于Windows Vista / 7),否则您完全不支持过滤掉隐藏字体。互联网上的许多用户抱怨在Windows 7控制面板中隐藏字体似乎没有任何效果,这是否令人感到惊讶或奇怪?!? (我之前错误地发布了MS Word 2010过滤掉隐藏字体。它似乎没有,因为它们使用自己的自定义功能区控件而不是内置在Windows中的功能区。有趣的是Windows 7字体控制面板,按设计,与Microsoft的旗舰产品之一不兼容,如果没有在Office中使用更强大的功能区,则无法兼容。)

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

  • 功能区控件在FMS.DLL(字体管理服务)中调用名为FmsGetFilteredFontList的未记录函数。它的目的显而易见。真是太遗憾了,他们无法公开记录和维护它。
  • 设置存储在未记录的注册表项中,可由FMS.DLL访问。
  • 如果删除了注册表项,则会使用FmsGetFilteredFontList的默认设置重新创建注册表项,这些设置将隐藏与当前输入语言无关的字体​​。
  • 在干净安装的Windows上创建的全新用户配置文件不包含与应隐藏哪些字体相关的任何注册表项。

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

答案 1 :(得分:4)

鉴于FmsGetFilteredFontList未被记录,您的完全与用户在Windows 7+ ChooseFont对话框中看到的列表相同的选项可能会受到限制。但是,可以通过仅使用文档化的API来获得默认字体列表的良好近似值。

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

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

诀窍是找出用户需要的子范围。在我的情况下,它相对容易,因为我确切地知道我将要渲染的文本。我基于documentation构建了子范围到FONTSIGNATURE样式位掩码值的映射。

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

字体签名位掩码是字体提供的字符的第一个剪切。您可以完全确定使用GetFontUnicodeRanges,但我发现这不是必需的,它也比检查字体签名更慢。

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

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

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

答案 2 :(得分:1)

毫无疑问,微软没有记录这一功能,说实话,但这越来越多,这是我们对他们的期望。

过滤自己的字体列表的另一种方法是通过枚举fonts文件夹来利用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的结果。

答案 3 :(得分:-3)

我认为这里的整个讨论都是误导性的。

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

如果我的用户只想使用Microsoft隐藏的其中一种字体,该怎么办?我是否会将负担转移到控制面板以取消隐藏此字体?

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

我认为有一种更好的方法来限制EnumFontFamiliesEx()返回的大量字体。

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

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

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位来过滤并减少您在字体列表中显示的字体数量:

enter image description here