Note: The FontResolver interface has changed between PDFsharp 1.50 beta 1 and PDFsharp 1.50 beta 2. This post describes the new solution for PDFsharp 1.50 beta 2 and later versions.
PDFsharp 1.50 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.
Please note: the IFontResolver is the recommended way to use private fonts with the GDI, WPF, or Silverlight builds of PDFsharp/MigraDoc.
Another note: I implemented a generic font resolver called EZFontResolver. Implementing IFontResolver in your code gives you full control – but using EZFontResolver can save your time if you only have standard requirements with respect to fonts. Read about EZFontResolver.
The IFontResolver interface requires two methods: ResolveTypeface and GetFont.
My class DemoFontResolver implements the IFontResolver interface. It has no baseclass and does not implement other interfaces. 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 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 PlatformFontResolver.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 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 null; }
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.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.
Differences between beta 1 and beta2:
With beta 1 the font resolver had to be derived from class FontResolverBase and had to override two methods. With beta 2 it has to implement the IFontResolver interface. The methods simply lose the word “override”. Instead of returning “base.ResolveTypeface” for unresolved fonts, you now can call “PlatformFontResolver.ResolveTypeface”.
It only takes a minute or two to update a font resolver that worked with beta 1 for use with beta 2.
Thanks for some excellent work, it’s good to see that the project is still active and improving!
I’m having difficulty getting private fonts / the font resolver to work in an ASP.NET MVC project. Using pretty much exactly your code I get the error “Font ‘Ubuntu’ cannot be found.” – this happens whether running locally or in the cloud.
If you or someone from your team would be so kind as to help, I have also included a detailed question on Stack Overflow here:
http://stackoverflow.com/questions/32726223/pdfsharp-migradoc-font-resolver-for-embedded-fonts-system-argumentexception
Let’s use StackOverflow to solve that issue. Thanks for your feedback.