Use private fonts in PdfSharp

Use private fonts in PdfSharp

An example of using private fonts on a web server is provided in PdfSharp 1.5, see: http://www.pdfsharp.net/wiki/(X(1)S(mg0wojiafv2wdqhklvachrti))/FontResolver-sample.ashx.

Note: FontResolver is a global object that applies to all consumers of the PdfSharp library. Also used by the MigraDoc library to create PDF documents.

1. Private Font

JetBrain is a set of fonts suitable for programmers. I downloaded it from https://www.jetbrains.com/lp/mono/. The downloaded file is a Zip package. After decompression, it contains multiple TrueType formats. The font file consists of two parts: the atmosphere of the file name, separated by a dash. The front is JetBrainsMono, and the back is the font style. For example, for ordinary fonts, it is the font file JetBrainsMono-Regular.ttf. The font file for bold is JetBrainsMono-Bold.ttf, and the font file for italics is JetBrains-Mono-Italic.ttf.

I also downloaded a TrueType font file from Microsoft YaHui. The name is YaHei.ttf. Use it to use Chinese fonts.

Font families and fonts

There are two concepts here: Font Family and Font Face.

Font Family is the name of a set of fonts, for example, in the case of the JetBrains Mono font family, this is the name of a set of fonts. Within a set of fonts, there are multiple specific fonts including regular fonts, bold fonts, italics, etc. These final fonts are called Font Faces.

When using fonts, we need to provide the name of the font family and the specific corresponding style. Combining them, we can get the actual font file used, and finally get the TrueType font data.

The examples below show how to use both fonts.

2. IFontResolver interface

PdfSharp defines how to obtain fonts through the IFontResolver interface. The interface is defined as follows. See: 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);
    }
}

The interface defines two methods.

2.1 ResolveTypeface

Get font information. Represents the specific font information that needs to be used as a FontResolverInfo object. You need to provide the font family name, whether it is bold, and whether it reveals the title.

For fonts, we need to provide a font family name to represent a type of font. Internally, we need to define a string name for each specific glyph. Defined through string constants.

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

When actually using the font internally, the glyph name is used. For glyph information, define the following string constants.

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

Inside the ResolveTypeface() method, you need to map the combination of font family name and style to the actual glyph mapping.

Find the corresponding glyph name for the corresponding font family name and the font style you want to use.

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);
}
Use system font

If the corresponding private font is not provided, the font provided by the platform is used.

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

2.2 GetFont()

Gets TrueType font data based on the font name in string format. Note that the font name is used here rather than the font family name. What is actually returned is TrueType font data.

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. Realization

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

Auxiliary class

Resources embedded in the assembly are used here.

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. Use private fonts

Register

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

Use font

2 fonts are created below for drawing.

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 = "Font: 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.
        }
    }
}