.NET的InstalledFontCollection在Windows Server 2012中无法枚举全部字体
为什么.NET无法枚举全部已安装字体?
这种情况我之前处理过好几次,尤其是针对非拉丁字体(比如你提到的Titr系列),核心原因通常集中在字体元数据冲突或者**.NET字体缓存机制的限制**上,结合你的场景,具体分析如下:
1. 字体Family Name重复是最可能的元凶
Windows的字体系统和.NET的InstalledFontCollection对字体的识别逻辑不一样:
- 写字板这类原生Windows应用是基于字体的显示名称和内部样式差异来区分字体的,哪怕两款字体的Family Name相同,只要样式(比如粗体、常规)不同,Windows就能识别并显示。
- 但.NET的
InstalledFontCollection是严格按照字体文件内部的Family Name字段来枚举的,如果Titr1和Titr2的Family Name都是"Titr"(很多非拉丁字体在制作时会共用这个名称),.NET只会保留最后安装的那一款,另一款会被“覆盖”,不会出现在枚举列表里。
验证方法:
- 右键点击Titr1和Titr2的字体文件,选择「查看」,查看窗口里的「字体名称」(不是显示在控制面板里的名称),如果两者一致,就确认是这个问题。
- 或者用FontForge这类字体编辑工具打开字体文件,检查「Family Name」字段的值。
2. .NET字体缓存未刷新
Windows Server 2012的.NET框架(尤其是4.x版本)的字体缓存机制可能没有及时更新,当你先后安装两款同Family Name的字体时,缓存只会记录最后安装的那一个,导致之前的字体无法被枚举。
解决步骤:
- 先重启服务器,让系统强制刷新全局字体缓存,这是最简单的方法。
- 如果重启无效,手动清理.NET相关的字体缓存:
- 删除
C:\Users\<你的用户名>\AppData\Local\Microsoft\Windows\FontCache目录下的所有文件。 - 如果是ASP.NET应用,还需要清理
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files目录,然后重启应用池。
- 删除
3. FastReport的字体列表依赖.NET枚举
FastReport是基于.NET框架开发的,它的字体选择列表本质上是直接调用了.NET的InstalledFontCollection接口,所以.NET枚举不到的字体,FastReport自然也显示不出来——这也解释了为什么移除Titr2后,Titr1能被.NET识别,FastReport也能显示了。
替代解决方案:用Windows原生API枚举字体
如果不想修改字体的Family Name,可以通过P/Invoke调用Windows原生的EnumFontFamiliesExAPI来枚举所有字体,这个API和写字板用的逻辑一致,能识别所有已安装的字体,包括同Family Name的不同样式。示例代码片段如下:
using System; using System.Collections.Generic; using System.Runtime.InteropServices; public class FontEnumerator { private delegate int EnumFontFamExProc(IntPtr lpelfe, IntPtr lpntme, uint FontType, IntPtr lParam); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct LOGFONT { public int lfHeight; public int lfWidth; public int lfEscapement; public int lfOrientation; public int lfWeight; public byte lfItalic; public byte lfUnderline; public byte lfStrikeOut; public byte lfCharSet; public byte lfOutPrecision; public byte lfClipPrecision; public byte lfQuality; public byte lfPitchAndFamily; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string lfFaceName; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct NEWTEXTMETRICEX { public NEWTEXTMETRIC ntmTm; public uint ntmFlags; public uint ntmSizeEM; public uint ntmCellHeight; public uint ntmAvgWidth; public uint ntmMaxCharWidth; public uint ntmWeight; public uint ntmOverhang; public uint ntmDigitizedAspectX; public uint ntmDigitizedAspectY; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string ntmFaceName; public ushort ntmCharSet; public ushort ntmDefaultChar; public ushort ntmBreakChar; public ushort ntmItalic; public ushort ntmUnderline; public ushort ntmStrikeOut; public uint ntmPitchAndFamily; public uint ntmCharSetFlags; } [StructLayout(LayoutKind.Sequential)] private struct NEWTEXTMETRIC { public int tmHeight; public int tmAscent; public int tmDescent; public int tmInternalLeading; public int tmExternalLeading; public int tmAveCharWidth; public int tmMaxCharWidth; public int tmWeight; public int tmOverhang; public int tmDigitizedAspectX; public int tmDigitizedAspectY; public char tmFirstChar; public char tmLastChar; public char tmDefaultChar; public char tmBreakChar; public byte tmItalic; public byte tmUnderline; public byte tmStruckOut; public byte tmPitchAndFamily; public byte tmCharSet; } [DllImport("gdi32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr GetDC(IntPtr hWnd); [DllImport("gdi32.dll", CharSet = CharSet.Unicode)] private static extern int EnumFontFamiliesEx(IntPtr hdc, ref LOGFONT lpLogfont, EnumFontFamExProc lpEnumFontFamExProc, IntPtr lParam, uint dwFlags); [DllImport("user32.dll")] private static extern int ReleaseDC(IntPtr hWnd, IntPtr hdc); private static List<string> _fontNames = new List<string>(); private static int EnumFontCallback(IntPtr lpelfe, IntPtr lpntme, uint FontType, IntPtr lParam) { var ntmex = Marshal.PtrToStructure<NEWTEXTMETRICEX>(lpntme); if (!_fontNames.Contains(ntmex.ntmFaceName)) { _fontNames.Add(ntmex.ntmFaceName); } return 1; } public static List<string> GetAllFonts() { _fontNames.Clear(); IntPtr hdc = GetDC(IntPtr.Zero); LOGFONT lf = new LOGFONT(); lf.lfCharSet = 0; // 枚举所有字符集 EnumFontFamiliesEx(hdc, ref lf, EnumFontCallback, IntPtr.Zero, 0); ReleaseDC(IntPtr.Zero, hdc); return _fontNames; } } // 使用示例: // var allFonts = FontEnumerator.GetAllFonts(); // foreach (var font in allFonts) // { // Console.WriteLine(font); // }
用这个方法枚举的字体列表会和写字板一致,你可以把这个结果传给FastReport,替代它默认的字体枚举逻辑。
内容的提问来源于stack exchange,提问作者Hosein Razaghi




