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

Jam*_*ton 25 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);
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

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

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

Jam*_*ton 26

感谢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(字体控件)也支持此行为,并提供了"将应用程序""功能化"的另一个原因.

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

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

ChooseFont将仅列出显示的字体,并在列表框中显示字体时过滤掉隐藏的字体.在CHOOSEFONT结构的标志构件的附加标志(CF_INACTIVEFONTS)加入到允许您在字体列表中显示的所有安装的字体,相同CHOOSEFONT视窗7之前表现.

因此换句话说,除非您使用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.多么可笑的烂摊子.微软最近已经非常擅长实施50%的解决方案. (4认同)

Adr*_*thy 5

鉴于 FmsGetFilteredFontList 未记录,您获取与用户在 Windows 7+ ChooseFont 对话框中看到的完全相同的列表的选项可能会受到限制然而,仅使用记录的 API 就有可能获得默认字体列表的良好近似值。

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

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

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

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

字体签名位掩码是确定字体提供的字符的一个很好的切入点。您可以使用GetFontUnicodeRanges来完全确定,但我发现这没有必要,而且它也比仅检查字体签名慢。

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

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

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