如何正确渲染嵌入的字体? [英] How to properly render an embedded Font?

查看:30
本文介绍了如何正确渲染嵌入的字体?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I download a True Type Font and I embedded it just as this page explains.
I had to set the UseCompatibleTextRendering property to be able to load it but it looks very weird, I don't know why it looks good in the browser but not in the application.

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

Just to be clear I added the font to my resources, set it as embedded resource, I used this module:

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)

推荐答案

该代码的问题不止一个:

More than one problem with that code:

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

  1. The PrivateFontCollection cannot be declared with a Using statement: this collection must be preserved as long as the Fonts it points to are needed. It's usually declared as a Field in the class (Form) that uses it or in a shared class (or Module, here), then disposed of when not needed anymore.

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

Marshal.FreeCoTaskMem() cannot be used here; it's a temptation to call it after Marshal.AllocCoTaskMem(), but not in this occasion. This can (will) compromise the Font data allocation. What you need to do is dispose of the PrivateFontcollection object. The Framework will take care of the COM affair (it will do it for you even if you forget to dispose of the PrivateFontcollection object. You should try not to forget, though).

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

The assembly reference is not required: the Font is added to the Project's Resources as a byte array, which is all that's needed. It can then be retrieved either by name, e.g., My.Resources.SomeFontName, or using the ResourceManager.GetObject() method, casting the returned object to 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 不能.

▶ You have already mentioned this but let's say it again: not all controls can use these Fonts. Only controls that can use Fonts drawn by GDI+ can actually use Fonts from the PrivateFontCollection, the Label and Button controls are among of these, in fact both expose a UseCompatibleTextRendering property. A RichTextBox, for example, cannot.

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

在表单的构造函数中,从项目的资源中添加字体.

In the Form's Constructor, add Font from the Project's Resources.

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

  • Here I'm using a helper class, FontManager, which exposes a public shared method AddFontsFromResource(): pass to this method the PrivateFontCollection and a list of resources names corresponding to the Font names.
    This method fills the collection with Fonts that can be installed successfully and returns the number of Fonts installed.
    Of course you use whatever other method you prefer to reference your Fonts.

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

Note. In the example, three Font resources are added to the collection:
{"FontFamily1Regular", "FontFamily1Italics", "OtherFontFamily"}
but two belong to the same FontFamily, so the PrivateFontCollection will contain just two elements, not three.

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 时处理它很重要:当初始化它的 Form 关闭时或在应用程序关闭之前:
您还可以使用共享对象来引用可在项目中的任何位置使用的 PrivateFontCollection.在这种情况下,需要在应用程序关闭时处理集合.

It's important to dispose of the PrivateFontCollection when it's not needed anymore: when the Form that initialized it closes or before the Application closes:
You could also use a shared object to reference a PrivateFontCollection that can be used anywhere in the Project. In this case the collection needs to be disposed of when the Application closes.

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# 版本:


C# version:

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

这篇关于如何正确渲染嵌入的字体?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆