VB.NET中PrintDocument使用PrivateFontCollection加载免安装私有字体问题
1. 私有字体集合(PrivateFontCollection)生命周期错误
两种方案都存在同一个核心错误:Font实例依赖加载它的PrivateFontCollection的完整生命周期,你在生成Font之后立刻释放了PrivateFontCollection,或是将PrivateFontCollection定义为方法内的局部变量,方法执行完后变量被GC回收,都会导致Font找不到对应的字体源,系统自动回退到默认字体(MS Sans Serif)。
- 修复方案:将PrivateFontCollection定义为类级别的全局变量,在需要使用该字体的整个周期内不要释放,直到完全不需要使用该字体后再统一释放资源。
2. 内存字体资源释放顺序错误(第一种方案特有)
你注释掉Marshal.FreeCoTaskMem(FontPtr)是正确的,这块内存必须在PrivateFontCollection释放之后才能释放,不能在AddMemoryFont执行完就释放。同时你在返回Font之前调用的pfc.Families(0).Dispose()和pfc.Dispose()属于错误操作,必须删除这两行释放代码,等不需要使用字体的时候再统一释放资源。
3. 打印渲染模式配置缺失
打印默认使用GDI渲染,不支持私有内存字体,需要为打印的Graphics对象开启GDI+兼容渲染,在调用DrawString之前添加如下配置:
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit ' WinForm应用还需要在应用启动时开启全局兼容文本渲染 Application.SetCompatibleTextRenderingDefault(True)
修复后代码示例(资源加载方案)
首先在窗体/业务类级别声明全局变量:
' 全局变量,不要放在方法内部 Private _customPfc As PrivateFontCollection Private _openSansBold As Font
在窗体初始化或首次打印前调用一次字体加载方法:
Public Sub LoadCustomFont() Try Dim fontData As Byte() = My.Resources.OpenSans_Bold _customPfc = New PrivateFontCollection() Dim fontPtr As IntPtr = Marshal.AllocCoTaskMem(fontData.Length) Marshal.Copy(fontData, 0, fontPtr, fontData.Length) _customPfc.AddMemoryFont(fontPtr, fontData.Length) _openSansBold = New Font(_customPfc.Families(0), 22, FontStyle.Bold) Catch ex As Exception MsgBox("加载字体失败:" & ex.Message) _openSansBold = New Font(FontFamily.GenericMonospace, 8) End Try End Sub
打印方法内直接使用预加载的字体:
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit e.Graphics.DrawString("Hello World", _openSansBold, Brushes.Black, 50, 2)
在窗体关闭/程序退出时统一释放资源:
Protected Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then _openSansBold?.Dispose() If _customPfc IsNot Nothing Then ' 此处可将之前申请的fontPtr存为全局变量后在此处释放,应用生命周期较短的场景也可以不用手动释放,进程退出后系统会自动回收 _customPfc.Dispose() End If End If MyBase.Dispose(disposing) End Sub
本地文件加载方案修复说明
和资源加载方案逻辑一致,只需要将PrivateFontCollection改为全局变量、提前加载不要在打印方法内声明为局部变量,同时添加Graphics的TextRenderingHint配置即可正常运行。
额外校验点
如果配置完还是不生效,可以添加调试代码确认字体加载正确:
Debug.WriteLine("加载的字体名:" & _customPfc.Families(0).Name)
如果输出的字体名和预期不一致,说明资源文件嵌入错误或字体文件损坏,需要替换正确的字体文件重新嵌入。
内容的提问来源于stack exchange,提问作者Andy Andromeda

