私有字体集合在更多迭代中不可靠。

4

我已将MemoryStream加载到PrivateFontCollection中,并打印Font-Family计数。

我已经执行了这个过程10次,我希望每次迭代都有相同的输出。我想要两次迭代的正确输出,但有时第一次迭代也会出错。我无法得到一致的输出。

提供一种使用PrivateFontCollection获得一致输出的解决方案。
注意:Fonts文件夹包含5种不同的字体。

private static void Work()
{
    string fontPath = @"D:\fonts";
    PrivateFontCollection fontCollection = null;
    for (int i = 1; i < 11; i++)
    {
        var fileList = Directory.GetFiles(fontPath, "*.ttf", SearchOption.TopDirectoryOnly);
        fontCollection = SafeLoadFontFamily(fileList);
        Console.WriteLine(i+" Iteration and families count:"+fontCollection.Families.Length);
        fontCollection.Dispose();
    }
    Console.ReadKey();
}
private static PrivateFontCollection SafeLoadFontFamily(IEnumerable<string> fontList)
{
    if (fontList == null) return null;
    var fontCollection = new PrivateFontCollection();

    foreach (string fontFile in fontList)
    {
        if (!File.Exists(fontFile)) continue;
        byte[] fontBytes = File.ReadAllBytes(fontFile);
        var fontData = Marshal.AllocCoTaskMem(fontBytes.Length);
        Marshal.Copy(fontBytes, 0, fontData, fontBytes.Length);
        fontCollection.AddMemoryFont(fontData, fontBytes.Length);
    }
    return fontCollection;
}

10次操作的期望输出:
1 次迭代和家族计数:5
2 次迭代和家族计数:5
3 次迭代和家族计数:5
4 次迭代和家族计数:5
5 次迭代和家族计数:5
6 次迭代和家族计数:5
7 次迭代和家族计数:5
8 次迭代和家族计数:5
9 次迭代和家族计数:5
10 次迭代和家族计数:5

实际输出:[不一致的输出]
1 次迭代和家族计数:5
2 次迭代和家族计数:5
3 次迭代和家族计数:5
4 次迭代和家族计数:5
5 次迭代和家族计数:4
6 次迭代和家族计数:3
7 次迭代和家族计数:3
8 次迭代和家族计数:4
9 次迭代和家族计数:4
10 次迭代和家族计数:4

1个回答

2
如果你只想打印存储在目录中的每个字体文件的字体家族名称,那么可以使用以下类似代码简化你的代码。它会按名称排序字体文件然后打印家族名称。
string fontsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "fonts");
var fontFiles = Directory.GetFiles(fontsPath).OrderBy(s => s).ToList();

fontFiles.ForEach(f => {
    using (var fontCollection = new PrivateFontCollection())
    {
        fontCollection.AddFontFile(f);
        Console.WriteLine(fontCollection.Families[0].Name);
    };
});

如果您想保留字体族名称列表(供其他用途使用),则将每个族名称添加到 List<string> 中(在此处作为字段):
//  As a field
List<string> fontNames = new List<string>();


 // Inside a method
var fontFiles = Directory.GetFiles(fontsPath).ToList();
fontFiles.ForEach(f => {
    using (var fontCollection = new PrivateFontCollection())
    {
        fontCollection.AddFontFile(f);
        fontNames.Add(fontCollection.Families[0].Name);
    };
});
fontNames = fontNames.OrderBy(s => s).ToList();
fontNames.ForEach(familyName => Console.WriteLine(familyName));

使用PrivateFontCollection.AddMemoryFont()方法。该方法可用于字体文件和作为项目资源添加的字体数据(它只是一个字节数组)。
重要提示:这些方法返回的PrivateFontCollection必须在Form.FormClosingForm.FormClosed(或处理应用程序终止的位置)中进行处理。
调用这些方法时传递文件路径集合:
// Field/Class object
PrivateFontCollection fontCollection = null;
// (...)
// Somewhere else
string fontsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "fonts");
var fontFiles = Directory.GetFiles(fontsPath);

fontCollection = UnsafeLoadFontFamily(fontFiles);
// Or...
fontCollection = SafeLoadFontFamily(fontFiles);

fontCollection.Families.OrderBy(f => f.Name).ToList().ForEach(font =>
{
    Console.WriteLine(font.GetName(0));
});

使用unsafe模式和byte*指针:
(必须在项目的属性 -> 构建面板中启用不安全代码)
private unsafe PrivateFontCollection UnsafeLoadFontFamily(IEnumerable<string> fontList)
{
    if (fontList.Length == 0) return null;
    var fontCollection = new PrivateFontCollection();

    foreach (string fontFile in fontList)
    {
        if (!File.Exists(fontFile)) continue;
        byte[] fontData = File.ReadAllBytes(fontFile);
        fixed (byte* fontPtr = fontData)
        {
            fontCollection.AddMemoryFont(new IntPtr(fontPtr), fontData.Length);
        }
    }
    return fontCollection;
}

使用 Marshal.AllocCoTaskMem()Marshal.Copy()
不要在这里调用 Marshal.FreeCoTaskMem()。可能会导致字体损坏。请调用 PrivateFontCollection.Dispose(),如已经提到的那样。
private PrivateFontCollection SafeLoadFontFamily(IEnumerable<string> fontList)
{
    if (fontList == null) return null;
    var fontCollection = new PrivateFontCollection();

    foreach (string fontFile in fontList)
    {
        if (!File.Exists(fontFile)) continue;
        byte[] fontBytes = File.ReadAllBytes(fontFile);
        var fontData = Marshal.AllocCoTaskMem(fontBytes.Length);
        Marshal.Copy(fontBytes, 0, fontData, fontBytes.Length);
        fontCollection.AddMemoryFont(fontData, fontBytes.Length);
    }
    return fontCollection;
}

打印字体集合的内容:
string fontPath = [The Fonts Path];
PrivateFontCollection fontCollection = null;
for (int i = 0; i < 5; i++) {
    var fileList = Directory.GetFiles(fontPath, "*.ttf", SearchOption.TopDirectoryOnly);
    fontCollection = SafeLoadFontFamily(fileList);
    fontCollection.Families.ToList().ForEach(ff => Console.WriteLine(ff.Name));
    fontCollection.Dispose();
}

"System.Windows.Media" 提供了 Fonts.GetFontFamilies() 方法,以防万一。

MemoryStream是字节数组。如果MemoryStream包含字体文件数据,则byte[] fontBytes = [MemoryStream].ToArray()byte[] fontBytes = File.ReadAllBytes(fontFile);没有任何区别。因此,如果您不指定MemoryStream或任何其他结构/类/对象包含什么(如已经询问的那样),提到MemoryStream对问题没有任何帮助。 - Jimi
byte[]MemoryStream没有问题。我有5个不同的byte[],并使用AddMemoryFont方法将它们添加到PrivateFontCollection中。整个过程重复了5次。对于第一次迭代,我有privateFontCollection.Families.count为5,而第3或第4次迭代其字体系列计数不同,我无法获得可靠的字体系列计数。 - Madhan
你没有按照我发布的SafeLoadFontFamily方法使用它(不要更改任何内容)。每次调用该方法,您将始终以相同的顺序获得相同的字体系列名称。在此处不要调用Marshal.FreeCoTaskMem()(如上所述),也不要为每个字体处理PrivateFontCollection。调用SafeLoadFontFamily(),将其打印到控制台中,然后将其处理并再次调用SafeLoadFontFamily()以重新填充集合。我已将此过程添加到新编辑中。您还可以使用UnsafeLoadFontFamily而无需进行编组。 - Jimi
我已经更新了我的代码,请使用新的代码来重新现问题。 - Madhan
也可以尝试使用 unsafe 方法。您需要在“项目->属性->生成”面板中启用不安全代码。 - Jimi
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接