【发布时间】:2018-11-30 13:34:53
【问题描述】:
在 WPF 中,我们希望将ttf 字体用作嵌入式资源,而不需要将它们复制或安装到系统中,也不需要将它们实际写入磁盘。没有内存泄漏问题。
没有详细的解决方案:
How to include external font in WPF application without installing it
由于 WPF 内存泄漏,在这种情况下可用:
WPF TextBlock memory leak when using Font
只能通过AddFontMemResourceEx 在 GDI 中从内存和进程中安装字体。由于这会为进程安装字体,因此它也应该适用于 WPF,但在通过 AddFontMemResourceEx 安装字体后,我们得到的 FontFamily 似乎存在问题。例如:
var font = new FontFamily("Roboto");
这样做的原因在于它不会给出任何错误,但实际上并未更改字体,更改了一些行间距和其他指标,但出于某种原因,字体看起来与Segoe UI 完全相同。
那么问题来了,如何在 WPF 中使用AddFontMemResourceEx 安装的字体?
PS:这里是 P/Invoke 代码:
const string GdiDllName = "gdi32";
[DllImport(GdiDllName, ExactSpelling= true)]
private static extern IntPtr AddFontMemResourceEx(byte[] pbFont, int cbFont, IntPtr pdv, out uint pcFonts);
public static void AddFontMemResourceEx(string fontResourceName, byte[] bytes, Action<string> log)
{
var handle = AddFontMemResourceEx(bytes, bytes.Length, IntPtr.Zero, out uint fontCount);
if (handle == IntPtr.Zero)
{
log?.Invoke($"Font install failed for '{fontResourceName}'");
}
else
{
var message = $"Font installed '{fontResourceName}' with font count '{fontCount}'";
log?.Invoke(message);
}
}
此代码成功,并显示如下日志消息:
Font installed 'Roboto-Regular.ttf' with font count '1'
支持将嵌入资源加载为字节数组的代码:
public static byte[] ReadResourceByteArray(Assembly assembly, string resourceName)
{
using (var stream = assembly.GetManifestResourceStream(resourceName))
{
var bytes = new byte[stream.Length];
int read = 0;
while (read < bytes.Length)
{
read += stream.Read(bytes, read, bytes.Length - read);
}
if (read != bytes.Length)
{
throw new ArgumentException(
$"Resource '{resourceName}' has unexpected length " +
$"'{read}' expected '{bytes.Length}'");
}
return bytes;
}
}
这意味着可以安装嵌入字体,assembly 是包含嵌入字体资源的程序集,EMBEDDEDFONTNAMESPACE 是嵌入资源的命名空间,例如SomeProject.Fonts:
var resourceNames = assembly.GetManifestResourceNames();
string Prefix = "EMBEDDEDFONTNAMESPACE" + ".";
var fontFileNameToResourceName = resourceNames.Where(n => n.StartsWith(Prefix))
.ToDictionary(n => n.Replace(Prefix, string.Empty), n => n);
var fontFileNameToBytes = fontFileNameToResourceName
.ToDictionary(p => p.Key, p => ReadResourceByteArray(assembly, p.Value));
foreach (var fileNameBytes in fontFileNameToBytes)
{
AddFontMemResourceEx(fileNameBytes.Key, fileNameBytes.Value, log);
}
【问题讨论】:
-
如果你只加载一次字体(作为共享资源),你不应该泄漏那么多。您的程序只会消耗更多内存(如果内存泄漏问题仍然存在,则复制项目已与 Microsoft 连接站点一起消失)