You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

.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

火山引擎 最新活动