冠军

导航

在 PdfSharp 中使用私有字体

在 PdfSharp 中使用私有字体

在 PdfSharp 1.5 中提供了在 Web 服务器上使用私有字体的示例,见:http://www.pdfsharp.net/wiki/(X(1)S(mg0wojiafv2wdqhklvachrti))/FontResolver-sample.ashx。

注意:FontResolver 是一个全局对象,应用于 PdfSharp 库的所有消费者。也被 MigraDoc 库用于创建 PDF 文档。

1. 私有字体

JetBrain 是一套适合程序员使用的字体,我从 https://www.jetbrains.com/lp/mono/ 下载了它,下载下来的文件是一个 Zip 包,解压之后,里面是多个 TrueType 格式的字体文件,文件名的氛围两部分组成,以中划线分隔,前面都是 JetBrainsMono,后面是字体的风格。例如,对于普通的字体来说,就是 JetBrainsMono-Regular.ttf 这个字体文件。粗体对应的字体文件是 JetBrainsMono-Bold.ttf,而斜体对应的字体文件是 JetBrains-Mono-Italic.ttf。

我还下载了一个微软雅慧的 TrueType 字体文件。名称为 YaHei.ttf。通过它来使用中文字体。

字体族和字体

这里有两个概念:字体家族 Font Family 和 字体 Font Face。

Font Family 是一套字体的名称,例如,对于 JetBrains Mono 字体家族来说,这就是一套字体的名称。而一套字体内部,有包括了普通字体,粗体,斜体等等多种具体的字体。这些最终的字体称为 Font Face。

当使用字体的时候,我们需要提供字体家族的名称,具体对应的样式,它们组合起来,我们可以得到实际使用的字体文件,最终得到 TrueType 字体数据。

下面的示例将展示如何使用这两种字体。

2. IFontResolver 接口

PdfSharp 通过 IFontResolver 接口定义了如何获取字体。该接口定义如下。见:https://github.com/empira/PDFsharp/blob/master/src/PdfSharp/Fonts/IFontResolver.cs

namespace PdfSharp.Fonts
{
    /// <summary>
    /// Provides functionality that converts a requested typeface into a physical font.
    /// </summary>
    public interface IFontResolver
    {
        /// <summary>
        /// Converts specified information about a required typeface into a specific font.
        /// </summary>
        /// <param name="familyName">Name of the font family.</param>
        /// <param name="isBold">Set to <c>true</c> when a bold fontface is required.</param>
        /// <param name="isItalic">Set to <c>true</c> when an italic fontface is required.</param>
        /// <returns>Information about the physical font, or null if the request cannot be satisfied.</returns>
        FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic);

        //FontResolverInfo ResolveTypeface(Typeface); TODO in PDFsharp 2.0

        /// <summary>
        /// Gets the bytes of a physical font with specified face name.
        /// </summary>
        /// <param name="faceName">A face name previously retrieved by ResolveTypeface.</param>
        byte[] GetFont(string faceName);
    }
}

接口定义了两个方法。

2.1 ResolveTypeface

获得字体信息。将需要使用的特定字体信息表示为一个 FontResolverInfo 对象。需要提供字体家族名称,是否粗体,是否泄题。

对于字体来说,我们需要提供一个字体族的名称来代表一类字体,在内部,我们需要针对每种具体的字形,定义出针对每种字形的字符串名称。通过字符串常量定义出来。

/// <summary>
/// The font family names that can be used in the constructor of XFont.
/// Used in the first parameter of ResolveTypeface.
/// Family names are given in lower case because the implementation of JetBrainsMonoFontResolver ignores case.
/// </summary>
static class FamilyNames
{
		public const string JetBrainsMonoRegular    = "JetBrains Mono";
		public const string HansRegular = "Hans";
}

内部在实际使用字体的时候,使用的是字形名称。对于字形信息,定义如下的字符串常量。

/// <summary>
/// The internal names that uniquely identify a font's type faces (i.e. a physical font file).
/// Used in the first parameter of the FontResolverInfo constructor.
/// </summary>
static class FaceNames
{
    public const string JetBrainsMonoRegular        = "JetBrainsMono-Regular";
    public const string JetBrainsMonoBold           = "JetBrainsMono-Bold";
    public const string JetBrainsMonoBoldItalic     = "JetBrainsMono-Bold-Italic";

    public const string HansRegular                 = "SourceHanSerif";
}

在 ResolveTypeface() 方法内部,需要实现将字体族名称和样式的组合对应到实际的字形映射。

针对对应的字体族名称,以及所希望使用的字体样式,来找到对应的字形名称。

public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
  // Note: PDFsharp calls ResolveTypeface only once for each unique combination
  // of familyName, isBold, and isItalic.

  string lowerFamilyName = familyName.ToLowerInvariant();

  // Looking for a JetBrains Mono font?
  if (lowerFamilyName.StartsWith("jetBrains mono") || lowerFamilyName.StartsWith("hans"))
  {
    // Bold simulation is not recommended, but used here for demonstration.
    bool simulateBold = false;

    // Since Segoe WP typefaces do not contain any italic font
    // always simulate italic if it is requested.
    bool simulateItalic = false;

    string faceName;

    // In this sample family names are case sensitive. You can relax this in your own implementation
    // and make them case insensitive.
    switch (familyName)
    {
      case FamilyNames.JetBrainsMonoRegular:
        faceName = FaceNames.JetBrainsMonoRegular;
        break;

      case FamilyNames.HansRegular:
        faceName = FaceNames.HansRegular;
        break;

      default:
        Debug.Assert(false, "Unknown JetBrains Mono font: " + lowerFamilyName);
        goto case FamilyNames.JetBrainsMonoRegular;  // Alternatively throw an exception in this case.
    }

    // Tell the caller the effective typeface name and whether bold  or italic should be simulated.
    return new FontResolverInfo(faceName, simulateBold, simulateItalic);
  }

  // Return null means that the typeface cannot be resolved and PDFsharp stops working.
  // Alternatively forward call to PlatformFontResolver.
  return PlatformFontResolver.ResolveTypeface(familyName, isBold, isItalic);
}

使用系统字体

如果没有提供对应的私有字体,使用平台所提供的字体。

return PlatformFontResolver.ResolveTypeface(familyName, isBold, isItalic);

2.2 GetFont()

根据字符串格式的字体名称,获得 TrueType 字体数据。注意这里使用的是字体名称而不是字体族名称。实际返回的是 TrueType 字体数据。

public byte[] GetFont(string faceName)
{
    // Note: PDFsharp never calls GetFont twice with the same face name.

    // Return the bytes of a font.
    switch (faceName)
    {
        case FaceNames.JetBrainsMonoBold:
            return FontDataHelper.JetBrainsMonoBold;

        case FaceNames.JetBrainsMonoRegular:
            return FontDataHelper.JetBrainsMonoRegular;

        case FaceNames.HansRegular:
            return FontDataHelper.HanSerif;
    }
    // PDFsharp never calls GetFont with a face name that was not returned by ResolveTypeface.
    throw new ArgumentException(String.Format("Invalid typeface name '{0}'", faceName));
}

3. 实现

using PdfSharp.Fonts;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

/*
 * An sample at: https://github.com/empira/PDFsharp-samples/blob/master/samples/core/FontResolver/SegoeWpFontResolver.cs
 */
namespace pdfSharpStarter.PrivateFonts
{
    public class JetBrainsMonoFontResolver : IFontResolver
    {
        /// <summary>
        /// The font family names that can be used in the constructor of XFont.
        /// Used in the first parameter of ResolveTypeface.
        /// Family names are given in lower case because the implementation of JetBrainsMonoFontResolver ignores case.
        /// </summary>
        static class FamilyNames
        {
            public const string JetBrainsMonoRegular    = "JetBrains Mono";
            public const string HansRegular = "Hans";
        }

        /// <summary>
        /// The internal names that uniquely identify a font's type faces (i.e. a physical font file).
        /// Used in the first parameter of the FontResolverInfo constructor.
        /// </summary>
        static class FaceNames
        {
            public const string JetBrainsMonoRegular        = "JetBrainsMono-Regular";
            public const string JetBrainsMonoBold           = "JetBrainsMono-Bold";
            public const string JetBrainsMonoBoldItalic     = "JetBrainsMono-Bold-Italic";

            public const string HansRegular                 = "SourceHanSerif";
        }

        /// <summary>
        /// Converts specified information about a required typeface into a specific font.
        /// </summary>
        /// <param name="familyName">Name of the font family.</param>
        /// <param name="isBold">Set to <c>true</c> when a bold fontface is required.</param>
        /// <param name="isItalic">Set to <c>true</c> when an italic fontface is required.</param>
        /// <returns>
        /// Information about the physical font, or null if the request cannot be satisfied.
        /// </returns>
        public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
        {
            // Note: PDFsharp calls ResolveTypeface only once for each unique combination
            // of familyName, isBold, and isItalic.

            string lowerFamilyName = familyName.ToLowerInvariant();

            // Looking for a JetBrains Mono font?
            if (lowerFamilyName.StartsWith("jetBrains mono") || lowerFamilyName.StartsWith("hans"))
            {
                // Bold simulation is not recommended, but used here for demonstration.
                bool simulateBold = false;

                // Since Segoe WP typefaces do not contain any italic font
                // always simulate italic if it is requested.
                bool simulateItalic = false;

                string faceName;

                // In this sample family names are case sensitive. You can relax this in your own implementation
                // and make them case insensitive.
                switch (familyName)
                {
                    case FamilyNames.JetBrainsMonoRegular:
                        faceName = FaceNames.JetBrainsMonoRegular;
                        break;

                    case FamilyNames.HansRegular:
                        faceName = FaceNames.HansRegular;
                        break;

                    default:
                        Debug.Assert(false, "Unknown JetBrains Mono font: " + lowerFamilyName);
                        goto case FamilyNames.JetBrainsMonoRegular;  // Alternatively throw an exception in this case.
                }

                // Tell the caller the effective typeface name and whether bold  or italic should be simulated.
                return new FontResolverInfo(faceName, simulateBold, simulateItalic);
            }

            // Return null means that the typeface cannot be resolved and PDFsharp stops working.
            // Alternatively forward call to PlatformFontResolver.
            return PlatformFontResolver.ResolveTypeface(familyName, isBold, isItalic);
        }

        public byte[] GetFont(string faceName)
        {
            // Note: PDFsharp never calls GetFont twice with the same face name.

            // Return the bytes of a font.
            switch (faceName)
            {
                case FaceNames.JetBrainsMonoBold:
                    return FontDataHelper.JetBrainsMonoBold;

                case FaceNames.JetBrainsMonoRegular:
                    return FontDataHelper.JetBrainsMonoRegular;

                case FaceNames.HansRegular:
                    return FontDataHelper.HanSerif;
            }
            // PDFsharp never calls GetFont with a face name that was not returned by ResolveTypeface.
            throw new ArgumentException(String.Format("Invalid typeface name '{0}'", faceName));
        }  
    }
}

辅助类

这里使用了嵌入到程序集内的资源。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace pdfSharpStarter.PrivateFonts
{
    /*
     * pdfSharpStarter.Resources.ttf.JetBrainsMono-Bold.ttf
     * pdfSharpStarter.Resources.ttf.JetBrainsMono-Light.ttf
     * pdfSharpStarter.Resources.ttf.JetBrainsMono-Regular.ttf
     */
    public static class FontDataHelper
    {
        public static byte[] JetBrainsMonoRegular
        {
            get { return LoadFontData("pdfSharpStarter.Resources.ttf.JetBrainsMono-Regular.ttf"); }
        }

        public static byte[] JetBrainsMonoBold
        {
            get { return LoadFontData("pdfSharpStarter.Resources.ttf.JetBrainsMono-Bold.ttf");  }
        }

        public static byte[] HanSerif
        {
            get { return LoadFontData("pdfSharpStarter.Resources.YaHei.ttf"); }
        }
        
        static byte[] LoadFontData(string name)
        {
            Assembly assembly = typeof(FontDataHelper).Assembly;
            using (Stream stream = assembly.GetManifestResourceStream(name))
            {
                if (stream == null)
                    throw new ArgumentException("No resource with name " + name);

                var count = (int)stream.Length;
                var data = new byte[count];
                stream.Read(data, 0, count);
                return data;
            }
        }
    }
}

4. 使用私有字体

注册

// REgister font resolver before start using PDFSharp
PdfSharp.Fonts.GlobalFontSettings.FontResolver
  = new PrivateFonts.JetBrainsMonoFontResolver();

使用字体

下面创建了 2 种字体进行绘制。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace pdfSharpStarter.PrivateFonts
{
    /*
     * REgister font resolver before start using PDFSharp
     *
     * PdfSharp.Fonts.GlobalFontSettings.FontResolver
     *           = new PrivateFonts.JetBrainsMonoFontResolver();
     */
    public class PrivateFontsSample
    {
        public static void Demo()
        {
            // Create a new PDF document.
            var document = new PdfSharp.Pdf.PdfDocument();
            document.Info.Title = "Font Resolver Sample";

            // Create an empty page in this document.
            var page = document.AddPage();
            page.Size = PdfSharp.PageSize.A4;

            // Get an XGraphics object for drawing on this PDF page.
            var gfx = PdfSharp.Drawing.XGraphics.FromPdfPage(page);

            // Create some fonts.
            const double fontSize = 24;
            PdfSharp.Drawing.XPdfFontOptions options 
                = new PdfSharp.Drawing.XPdfFontOptions(
                    PdfSharp.Pdf.PdfFontEncoding.Unicode);
          
            var font = new PdfSharp.Drawing.XFont("JetBrains Mono", fontSize, PdfSharp.Drawing.XFontStyle.Regular, options);
            var cfont = new PdfSharp.Drawing.XFont("Hans", fontSize, PdfSharp.Drawing.XFontStyle.Regular, options);
            // Draw the text.
            const double x = 40;
            double y = 50;

            const string ctext = "字体:JetBrains Mono";
            gfx.DrawString(ctext, cfont, PdfSharp.Drawing.XBrushes.Black, x, y);
            y += 60;

            const string text1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            gfx.DrawString(text1, font, PdfSharp.Drawing.XBrushes.Black, x, y);
            y += 60;

            const string text2 = "abcdefghijklmnopqrstuvwxyz";
            gfx.DrawString(text2, font, PdfSharp.Drawing.XBrushes.Black, x, y);
            y += 60;

            const string text3 = "0123456789";
            gfx.DrawString(text3, font, PdfSharp.Drawing.XBrushes.Black, x, y);
            y += 60;

            // Save the document...
            const string filename = "../../../Output/FontResolver_tempfile.pdf";
            document.Save(filename);
            // ...and start a viewer.
        }
    }
}

posted on 2022-03-06 21:01  冠军  阅读(1634)  评论(0编辑  收藏  举报