Using private fonts with PDFsharp 1.50 beta or MigraDoc

Note: The FontResolver interface has changed between PDFsharp 1.50 beta 1 and PDFsharp 1.50 beta 2. This post describes the obsolete solution for PDFsharp 1.50 beta 1.
Sample code for PDFsharp 1.50 beta 2.

PDFsharp 1.50 beta 1 implements a new mechanism for private fonts (fonts that are not installed on the system). You simply implement IFontResolver and assign this to a global property:

// That's all it takes to register your own fontresolver
GlobalFontSettings.FontResolver = new DemoFontResolver();

The interesting part is IFontResolver and how you implement it.

The IFontResolver interface requires two methods: ResolveTypeface and GetFont.

I derived my class DemoFontResolver from PDFsharp’s class FontResolverBase. For this demo I downloaded two free font: Janitor (regular only) and Ubuntu (using regular, italic, bold, bold italic).

Here is the ResolveTypeface code:

public override FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
    // Ignore case of font names.
    var name = familyName.ToLower();

    // Deal with the fonts we know.
    switch (name)
    {
        case "ubuntu":
            if (isBold)
            {
                if (isItalic)
                    return new FontResolverInfo("Ubuntu#bi");
                return new FontResolverInfo("Ubuntu#b");
            }
            if (isItalic)
                return new FontResolverInfo("Ubuntu#i");
            return new FontResolverInfo("Ubuntu#");

        case "janitor":
            return new FontResolverInfo("Janitor#");
    }

    // We pass all other font requests to the default handler.
    // When running on a web server without sufficient permission, you can return a default font at this stage.
    return base.ResolveTypeface(familyName, isBold, isItalic);
}

The other method, GetFont, must return the TTF file in a byte array.

My implementation of GetFont looks like this:

/// <summary>
/// Return the font data for the fonts.
/// </summary>
public override byte[] GetFont(string faceName)
{
    switch (faceName)
    {
        case "Janitor#":
            return FontHelper.Janitor;

        case "Ubuntu#":
            return FontHelper.Ubuntu;

        case "Ubuntu#b":
            return FontHelper.UbuntuBold;

        case "Ubuntu#i":
            return FontHelper.UbuntuItalic;

        case "Ubuntu#bi":
            return FontHelper.UbuntuBoldItalic;
    }

    return base.GetFont(faceName);
}

The code that does the work of retrieving the fonts is hidden in a helper class. I added the fonts to the project using the “Embedded Resource” compile type. I then used the free dotPeek tool from JetBrains to find the names of the embedded resources.

/// <summary>
/// Helper class that reads font data from embedded resources.
/// </summary>
public static class FontHelper
{
    public static byte[] Janitor
    {
        get { return LoadFontData("MyFontResolver.fonts.janitor.Janitor.ttf"); }
    }

    // Tip: I used JetBrains dotPeek to find the names of the resources (just look how dots in folder names are encoded).
    // Make sure the fonts have compile type "Embedded Resource". Names are case-sensitive.
    public static byte[] Ubuntu
    {
        get { return LoadFontData("MyFontResolver.fonts.ubuntufontfamily0._80.Ubuntu-R.ttf"); }
    }

    public static byte[] UbuntuBold
    {
        get { return LoadFontData("MyFontResolver.fonts.ubuntufontfamily0._80.Ubuntu-B.ttf"); }
    }

    public static byte[] UbuntuItalic
    {
        get { return LoadFontData("MyFontResolver.fonts.ubuntufontfamily0._80.Ubuntu-RI.ttf"); }
    }

    public static byte[] UbuntuBoldItalic
    {
        get { return LoadFontData("MyFontResolver.fonts.ubuntufontfamily0._80.Ubuntu-BI.ttf"); }
    }

    /// <summary>
    /// Returns the specified font from an embedded resource.
    /// </summary>
    static byte[] LoadFontData(string name)
    {
        var assembly = Assembly.GetExecutingAssembly();

        using (Stream stream = assembly.GetManifestResourceStream(name))
        {
            if (stream == null)
                throw new ArgumentException("No resource with name " + name);

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

You can download the complete demo application as a ZIP file (about 2 MiB in size due to the embedded fonts).
Download FontResolverDemo_beta1.zip

The font resolver applies to PDFsharp and it will also be used when you create PDF files from MigraDoc.

The ZIP file contains two console applications, one demo for PDFsharp and one demo for MigraDoc.
The solution uses PDFsharp 1.50 beta from NuGet. In Visual Studio (I used the free Community Edition Visual Studio 2013) in the Solution Explorer, select Manage NuGet Packages for Solution from the context menu of the solution.

2 thoughts on “Using private fonts with PDFsharp 1.50 beta or MigraDoc”

  1. Hi Thomas,

    I am using the this form of FontResolver and it works fine. However I have changed the way my FontResolver works and I wanted to Unit Test it, but had problems with multiple settings of GlobalFontSettings.FontResolver.

    I have tried a few things, like only adding setting the FontResolver if it is null but that doesn’t seem to work on multiple runs, as GlobalFontSettings.FontResolver == null, but when I then set it I get an InvalidOperationException with the message “Must not change font resolver after is was once used.”

    Have you any suggestions on how to Unit Test this?

    1. Hi, Jon,

      Probably it will work if you turn your font resolver into a singleton. Create a single instance of the font resolver, remember that instance in a static field and use it with every test.
      I implemented my EZFontResolver today before I saw your comment. It uses a singleton. If you are not familiar with the singleton design pattern, then maybe take a look at my EZFontResolver.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.