Font resolving
Version 6.1.0
Note
This article is for PDFsharp up to version 6.1. In version 6.2 the build-in font resolving works a bit different.
See here for the latest documentation.
In this article
- Selecting a font
- Custom font resolvers
- Setting a custom font resolver
- Invocation order
- More than one custom font resolver needed
- XPrivateFontCollection
- What’s new in 6.2
This article describes the way PDFsharp selects fonts.
Selecting a font
Before you can draw text with PDFsharp, you have to create an XFont object like this:
var myFont = new XFont("Arial", 10, XFontStyleEx.Regular);
var myBoldFont = new XFont("Arial", 10, XFontStyleEx.Bold);
This code creates two fonts, one for Arial and one for Arial bold.
What happens more precisely is a mapping from your request of a typeface to an actual font face.
This is exactly what a font resolver does in PDFsharp.
It maps a request of a typeface e.g. Arial bold to the font face bytes of the file arialbt.ttf
.
In case of Arial the mapping is done automatically in PDFsharp from the platform font resolver.
This is a built-in font resolver depending on the build that makes your code work immediately at least under Windows and Linux.
At first glance, selecting a font looks easy, but it is not. If you are curious how Windows Presentation Foundation (WPF) does it, take a look at WPF Font Selection Model (© Microsoft 2006).
PDFsharp uses a much simpler approach, depending on what build you are using (Core, GDI, or WPF).
In the GDI and WPF builds, the selection of the requested typeface is delegated directly to GDI+ (System.Drawing
) or
WPF (System.Windows
), respectively.
This is the easiest case. The underlying Windows code selects the appropriate type face.
In the Core build of PDFsharp the concept of font resolvers is used. If nothing else is configured, the font selection is done by the built-in PlatformFontResolver. Although the Core build based on .NET Framework can only run on Windows, neither GDI nor WPF code is used. It behaves identical to the Core build based on .NET 6.
The PlatformFontResolver knows about the following font families.
Family | Font faces | Remarks |
---|---|---|
Arial | regular, bold, italic, bold italic | A font representative for a san-serif typeface. |
Times New Roman | regular, bold, italic, bold italic | A font representative for a serif typeface. |
Courier New | regular, bold, italic, bold italic | A font representative for a mono-spaced typeface. |
Verdana | regular, bold, italic, bold italic | A font representative for a former Microsoft standard font. |
Lucida Console | regular | A font representative for a mono-spaced console typeface. If needed, bold and italic are simulated. |
Symbol | regular | A font representative for a symbol typeface. If needed, bold and italic are simulated. |
If a typeface is requested which is not in this table, an exception is thrown. To prevent this, you can provide a custom font resolver.
There is no guarantee that the PlatformFontResolver will always find those fonts. Under Windows, it searches the Windows Fonts folder, but if access to this folder is restricted, no fonts can be found. Under Linux it searches for those fonts and some known substitutes in typical font folders of Linux distributions, but again there is no guarantee that any fonts can be found. Under MacOS, Raspberry Pi, Xamarin, and other platforms, most likely no fonts will be found.
So the best bet for production applications is the implementation of a font resolver that comes with all the fonts needed by that application.
The PlatformFontResolver is still useful for your first steps with PDFsharp or MigraDoc when writing some test code that executes under Windows or Linux.
Custom font resolvers
A custom font resolver is a class that implements the IFontResolver interface. This interface has the following functions and uses the class FontResolverInfo.
public interface IFontResolver : …
{
FontResolverInfo? ResolveTypeface(string familyName, bool bold, bool italic);
byte[]? GetFont(string faceName);
}
public class FontResolverInfo
{
public FontResolverInfo(string faceName,
bool mustSimulateBold, bool mustSimulateItalic)
…
}
A custom font resolver must implement two functions. ResolveTypeface is called by PDFsharp when a typeface must be mapped to a face name. It returns an instance of FontResolverInfo, or null, if no appropriate font was found. FontResolverInfo contains beside the name of the font face two additional style simulation flags to indicate that this style must be simulated by PDFsharp. Note that only italic style simulation is implemented. GetFont is called by PDFsharp with the face name gotten from ResolveTypeface to retrieve the physical bytes of the font face.
PDFsharp calls each function for a specific request only once and caches the results. GetFont is invoked only with a face name returned from a previous call of ResolveTypeface. Therefore in your custom font resolver you can use as a face name whatever you want.
Setting a custom font resolver
The class GlobalFontSettings has two optional static properties FontResolver and FallbackFontResolver to set a custom font resolver. The first property sets your custom font resolver. If set, it is called by PDFsharp when you try to create an XFont for a typeface the first time. The second property sets an alternate font resolver. It is invoked when no matching font face was found before. PDFsharp provides the class FailsafeFontResolver as an example of a font resolver that always resolves to a default font if no better match was found.
Both font resolver properties must, if set, be set only once. They also cannot be set anymore after one single XFont was created. When a custom font resolver is set, the platform font resolver is not automatically invoked by PDFsharp.
Even if it is less important to set a custom font resolver in GDI+ or WPF builds, you can do it. It works the same way as in the Core builds.
In production code a custom font resolver should be set at program start.
However, in development scenarios, especially for unit tests, PDFsharp provides the function GlobalFontSettings.ResetFontManagement
.
It empties all internal caches and allows to set other custom font resolvers without a restart of the process.
After this function was invoked, a previously crated XFont cannot be used anymore and must be recreated.
An internal generation counter ensures that an exception is thrown if you use an outdated XFont object.
Invocation order
The following describes the order PDFsharps uses to invoke font resolvers.
If neither a custom nor a fallback font resolver is set, PDFsharp calls the platform font resolver. If it returns null, an exception is thrown.
If a custom font resolver is set, PDFsharp calls it first. If it returns null, PDFsharp calls the fallback font resolver if one is set. If it also returns null (or is not set), an exception is thrown.
If only a fallback custom resolver is set, PDFsharp first calls the platform font resolver. If it returns null, the fallback font resolver is called. If it also returns null, an exception is thrown.
That means, if you set a custom font resolver, the platform font resolver is never called automatically.
However, you can call the static function PlatformFontResolver.ResolveTypeface
from within your custom code and return the resolver info to PDFsharp if it is not null.
Note that this call does not invoke the fallback font resolver.
In case your custom font resolver returns the result of the platform font resolver
PDFsharp will never call your GetFont function with a face name from this result.
You must not invoke PlatformFontResolver.ResolveTypeface
from your custom font resolver in case it is set as the fallback font resolver. Doing so raises an exception.
More than one custom font resolver needed
PDFsharp provides only one property to set a custom font resolver. However, it is very easy to write your own code that implements IFontResolver and iterates over a list of font resolvers until one does not return null.
XPrivateFontCollection
There was the class XPrivateFontCollection in older versions of PDFsharp. This class is outdated and if you have used it, replace it with a custom font resolver.
What’s new in 6.2
To make life easier for developers, the PlatformFontResolver in PDFsharp 6.1 retrieves a set of fonts like Arial or Times New Roman from the Windows fonts directory.
This works if your application runs under Windows, but has drawbacks under different platforms.
For example: Your target platform is Linux and you develop under Windows using WSL2.
The code works fine during development because PlatformFontResolver retrieves the fonts from mnt/c/Windows/Fonts
on the developers machine, but crashes in a Docker container because no fonts are available here.
Therefore we changed this behavior. See updated version of this article.