【问题标题】:How to properly render an embedded Font?如何正确渲染嵌入的字体?
【发布时间】:2021-02-07 01:55:34
【问题描述】:

我下载了 True Type 字体并按照this page 的说明嵌入了它。
我必须设置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.net winforms fonts render embedded-resource


    【解决方案1】:

    该代码不止一个问题:

    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 的控件才能真正使用来自 PrivateFontCollection 的 Fonts,Label 和 Button 控件就在其中,实际上都暴露了 UseCompatibleTextRendering 属性。例如,RichTextBox 不能。

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

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

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

    • 注意。在示例中,将三个字体资源添加到集合中:
      {"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。在这种情况下,需要在应用程序关闭时处理该集合。

    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;
    }
    

    【讨论】:

    • :o 非常感谢您花时间向我解释这个@jimi sempai,但我有两个问题,第一个,为什么你在字体大小中使用“F”?,第二个,你为什么用String.Empty?,我总是用""来声明我的字符串变量。
    • F 表示float(VB.Net 中的Single)。这是 Font 构造函数所期望的类型,我只是明确地声明它。你也可以写,例如10.5,它也被接受。在 C# 中,您必须改为编写 10.5F。 String.Empty 只是声明不包含任何字符的字符串的标准形式。你也可以使用= "",它不会改变结果。
    • 您好@jimi,我有一个新问题,如果我正在制作自定义文本框,在哪里调用myfontcollection.dispose() 比较合适? (这是我使用link的代码)
    • 一如既往,在Dispose() 方法覆盖。您将拥有protected overrides sub Dispose(disposing as Boolean) if disposing then myFontCollection?.Dispose() end if MyBase.Dispose(disposing) end sub。顺便说一句,将OnCreateControl() 更改为OnHandleCreated(),将installedFontsCount = FontManager.AddFontsFromResource(...) 移动到构造函数(public sub new() )。此外,当您创建自定义控件时,不要订阅事件,覆盖引发事件的方法。因此,删除所有 AddHandler 部分并改用,例如Protected overrides OnEnter()。删除JoinEvents(True)
    • [...] 在设置字体之前确保installedFontsCount > 0。 -- 此处发布的代码是否回答了您的问题?如果是这样,你应该接受它。如果您还有其他问题,请发布一个新的实际问题:对于在 cmets 部分中回答无关问题的其他人来说,这并不是真正有用的。
    猜你喜欢
    • 2012-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-06
    • 2014-02-10
    • 1970-01-01
    相关资源
    最近更新 更多