首页 > 解决方案 > 如何正确渲染嵌入的字体?

问题描述

我下载了一个 True Type 字体,并按照本页的说明嵌入了它。
我必须设置UseCompatibleTextRendering属性才能加载它,但它看起来很奇怪,我不知道为什么它在浏览器中看起来不错,但在应用程序中却没有。

为了清楚起见,我将字体添加到我的资源中,将其设置为嵌入式资源,我使用了这个模块:

Imports System.IO
Imports System.Reflection
Imports System.Drawing.Text
Imports System.Runtime.InteropServices

Module ExternalFontType
    Public Function GetFont(aAssembly As Assembly,
      strFontName As String, intFontSize As Integer,
      fsFontStyle As FontStyle) As Font

        Using pcolFonts As New PrivateFontCollection

            Dim bFont() As Byte = ExternalFontType.bRawFontData(aAssembly, strFontName)
            Dim ptrMemFont As IntPtr =
               Marshal.AllocCoTaskMem(bFont.Length)

            Marshal.Copy(bFont, 0, ptrMemFont, bFont.Length)
            pcolFonts.AddMemoryFont(ptrMemFont, bFont.Length)

            Marshal.FreeCoTaskMem(ptrMemFont)

            Return New Font(pcolFonts.Families(0),
               intFontSize, fsFontStyle)
        End Using
    End Function

    Private Function bRawFontData(aAssembly As Assembly, strFontName As String) As Byte()
        Using stFont As Stream =
            aAssembly.GetManifestResourceStream(strFontName)

            If (stFont Is Nothing) Then Throw _
               New Exception(String.Format("Cannot load _
            font '{0}'", strFontName))

            Dim bFontBuffer() As Byte = New _
               Byte(CInt(stFont.Length - 1)) {}

            stFont.Read(bFontBuffer, 0, CInt(stFont.Length))
            Return bFontBuffer
        End Using
    End Function
End Module

并将其包含在此代码中

lbl.UseCompatibleTextRendering = True
lbl.Font = ExternalFontType.GetFont(Me.GetType.Assembly, "ProyectName.FontName.ttf", 15, FontStyle.Bold)

标签: vb.netwinformsfontsrenderembedded-resource

解决方案


该代码不止一个问题:

  1. PrivateFontCollection不能用声明声明:只要需要它指向的字体,就必须保留这个集合Using它通常在使用它的类(表单)或共享类(或模块,此处)中声明为字段,然后在不再需要时将其丢弃。

  2. Marshal.FreeCoTaskMem()不能在这里使用;在 之后调用它 是一种诱惑Marshal.AllocCoTaskMem(),但不是在这种情况下。这可能(将)损害字体数据分配。您需要做的是处理该PrivateFontcollection对象。框架将处理 COM事务(即使您忘记处置PrivateFontcollection对象,它也会为您完成。不过,您应该尽量不要忘记)。

  3. 不需要程序集引用:字体作为字节数组添加到项目的资源中,这就是所需要的。然后可以通过名称检索它,例如,My.Resources.SomeFontName或使用ResourceManager.GetObject()方法,将返回的对象转换为Byte()

    Dim fontData As Byte() = My.Resources.SomeFontName
    Dim fontData As Byte() = DirectCast(My.Resources.ResourceManager.GetObject("SomeFontName"), Byte())
    

▶ 您已经提到了这一点,但让我们再说一遍:并非所有控件都可以使用这些字体。只有可以使用 GDI+ 绘制的 Fonts 的控件才能真正使用来自 的 Fonts PrivateFontCollection,Label 和 Button 控件就是其中之一,实际上它们都暴露了UseCompatibleTextRendering属性。例如,RichTextBox 不能。

  • 如果字体创建正确,您可以使用Graphics.DrawString()该字体绘制字符串内容,即使您无法将其设置为控件的字体。
Private myFontCollection As PrivateFontCollection = New PrivateFontCollection()

在表单的构造器中,从项目的资源中添加字体。

  • 这里我使用了一个辅助类,FontManager它公开了一个public shared方法AddFontsFromResource():传递给这个方法PrivateFontCollection和一个与字体名称相对应的资源名称列表。
    该方法用可以成功安装的字体填充集合,并返回已安装的字体数量。
    当然,您可以使用您喜欢的任何其他方法来引用您的字体。

  • 注意。在示例中,将三个 Font 资源添加到集合中:
    {"FontFamily1Regular", "FontFamily1Italics", "OtherFontFamily"}
    但两个属于同一个FontFamily,因此PrivateFontCollection将只包含两个元素,而不是三个。

Public Sub New()
    Dim installedFontsCount = FontManager.AddFontsFromResources(myFontCollection, 
        {"FontFamily1Regular", "FontFamily1Italics", "OtherFontFamily"})
    ' The Font can set here or anywhere else
    someLabel.UseCompatibleTextRendering = True
    someLabel.Font = New Font(myFontCollection.Families(0), 10.5F, FontStyle.Regular)
    someButton.UseCompatibleTextRendering = True
    someButton.Font = New Font(myFontCollection.Families(0), 10.5F, FontStyle.Italic)
End Sub

PrivateFontCollection当不再需要它时处理它很重要:当初始化它的表单关闭或应用程序关闭之前:
您还可以使用共享对象来引用PrivateFontCollection可在项目中的任何地方使用的 a。在这种情况下,需要在应用程序关闭时处理该集合。

Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed
    myFontCollection.Dispose()
End Sub

助手类:

Imports System.Drawing.Text
Imports System.Runtime.InteropServices

Public Class FontManager
    Public Shared Function AddFontsFromResources(fontCollection As PrivateFontCollection, fontNames As String()) As Integer
    If fontNames.Length = 0 Then Return Nothing
    Dim installedFontsCount = 0

    For Each fontName As String In fontNames
        Try
            Dim fontData As Byte() = CType(My.Resources.ResourceManager.GetObject(fontName), Byte())
            If fontData Is Nothing Then Throw New InvalidOperationException()

            Dim data As IntPtr = Marshal.AllocCoTaskMem(fontData.Length)
            Marshal.Copy(fontData, 0, data, fontData.Length)
            fontCollection.AddMemoryFont(data, fontData.Length)
            installedFontsCount += 1
        Catch ex As Exception
            ' Placeholder: Notify User/Log/Whatever
            Debug.Print($"Font installation failed for {fontName}")
        End Try
    Next
    Return installedFontsCount
    End Function
End Class

C#版本:

using System.Drawing.Text;
using System.Runtime.InteropServices;

public static int AddFontsFromResources(PrivateFontCollection fontCollection, string[] fontNames)
{
    int installedFontsCount = 0;
    if (fontNames.Length == 0) return 0;

    foreach (string fontName in fontNames) {
        try {
            byte[] fontData = (byte[])Properties.Resources.ResourceManager.GetObject(fontName);
            var data = Marshal.AllocCoTaskMem(fontData.Length);
            Marshal.Copy(fontData, 0, data, fontData.Length);
            fontCollection.AddMemoryFont(data, fontData.Length);
            installedFontsCount += 1;
        }
        catch (Exception) {
            // Placeholder: Notify User/Log/Whatever
            Console.WriteLine($"Font installation failed for {fontName}");
        }
    }
    return installedFontsCount;
}

推荐阅读