枚举EnumFontFamiliesEx函数时字体太多

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

结果列表看起来像这样,按字母顺序sorting和删除重复的脸名称的字体:

在这里输入图像说明

正如你所看到的, ChooseFont字体公共对话框显示了一个非常合理的字体列表,用户友好和有意义。 另一方面,我的代码显示了一长串额外的字体:以“@”开头的字体(为什么?它们甚至是什么?),Arial字体的另外三种变体,以及其他一些未知目的的字体,如Aheroni,安达卢斯,Angsana New,Angsana UPC等等。 这是疯了。

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

Solutions Collecting From Web of "枚举EnumFontFamiliesEx函数时字体太多"

感谢Jesse Good,我现在已经了解了Windows 7团队做出的一些疯狂的不幸设计决定。 我不会接受我自己的答案,因为如果别人想出了一种在Windows 7中使用这种隐藏字体特性的方法,即使注册表项还不存在(例如,也许通过使用未公开的API或其他他们的答案是有效的,我会接受的。

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

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

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

在Windows 7中,不存在用于直接查询隐藏哪些字体或设置要隐藏的字体的API。 [我的重点]如果您使用Windows ChooseFont API(字体常用对话框)来启用今天的字体选择,您将获得免费的新行为。 在Windows 7中引入的新Windows风格功能区​​(字体控件)也支持这种行为,并提供了另一个“分条”您的应用程序的原因。

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

请注意,charset是与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%的时间。 如果不存在,您需要一种可靠地重新创建这些注册表项的方法(或者至少采用默认值)。 即使注册表项不存在(例如在新的用户配置文件中),默认行为仍然是隐藏一些字体。

鉴于FmsGetFilteredFontList是未记录的,您可以选择用于获取用户在Windows 7 + ChooseFont对话框中看到的完全相同的列表。 但是,通过仅使用记录的API,可能可以很好地近似默认的字体列表。

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

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

诀窍是找出用户需要的子范围。 就我而言,这是相对容易的,因为我确切地知道我将要呈现的文本。 我根据文档建立了子范围到FONTSIGNATURE样式位掩码值的映射。

我扫描了待渲染文本中的代码点,在映射中查找它们,并构建了一个目标位掩码。 我按位添加了每个枚举字体的字体签名中的目标位掩码。 每当结果与目标位掩码匹配时,我就知道字体可能(很可能)支持文本。 对于我的应用程序,我要求所有的目标位都存在于字体中。 对于你的应用程序,我想你会想要任何目标位。

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

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

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

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

说实话,微软没有记录这个功能是可耻的,但是越来越多,这是我们所期待的。

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

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

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

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

如果有一天中国用户想在英文窗口上写中文文本,中文字体是隐藏的呢?

我认为有一个更好的方法来限制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位来筛选和减少您在字体列表中显示的字体数量:

在这里输入图像说明