Font resolving
Version 6.2.0
Note
This article is for PDFsharp starting from version 6.2 where the default font resolving has changed.
See here for documentation of PDFsharp 6.1 or earlier.
In this article
- Selecting a font
- Platform font resolver
- Custom font resolvers
- Setting a custom font resolver
- Invocation order
- More than one custom font resolver needed
- XPrivateFontCollection
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
.
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). Today, this behavior moved from WPF to DirectWrite, but this is only available under Windows.
Platform font resolver
PDFsharp uses a much simpler approach, depending on what build you are using (Core, GDI, or WPF). There is a built-in font resolver called the platform font resolver. If you do not provide your own custom font resolver implementation (see next section), PDFsharp directs a font request to the class PlatformFontResolver.
In the GDI and WPF builds, the selection of the requested typeface is delegated by PlatformFontResolver directly to GDI+ (System.Drawing
) or WPF (System.Windows
), respectively.
This is the easiest case.
The underlying Windows code selects the appropriate font face.
In the Core build PlatformFontResolver throws an exception by default. The reason is that the .NET version of the Core build runs on all platforms / operating systems .NET runs on and there is no general font resolving strategy on all these platforms. You can use PDFsharp e.g. under WSL2, Linux, Mac, in Docker containers, on single-board computers, mobile phones, or on platforms like Blazor, WinUI or MAUI. In all these cases you must provide your own custom font resolver as described in the next section.
Note
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 or higher.
However, if you know your application will run under Windows, you can activate two special options.
Option UseWindowsFontsUnderWindows
If you set
GlobalFontSettings.UseWindowsFontsUnderWindows = true;
in the startup code of your application, 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. |
Now the following code runs as expected.
var myFont = new XFont("Arial", 10, XFontStyleEx.Regular);
var myBoldFont = new XFont("Arial", 10, XFontStyleEx.Bold);
In case of Arial, the mapping is now done automatically in PDFsharp from the PlatformFontResolver.
It tries to retrieve the fonts from C:\Windows\Fonts
.
If this folder is not available or not accessible (e.g. under Azure), an exception is thrown.
Once set, UseWindowsFontsUnderWindows cannot be changed anymore.
In GDI or WPF build, this option has no effects and PDFsharp logs a warning if you set it.
The purpose of this option is to get you quickly started with developing and testing only. In production code we highly recommend to use a custom font resolver.
Option UseWindowsFontsUnderWsl2
If you set
GlobalFontSettings.UseWindowsFontsUnderWsl2 = true;
in the startup code of your application, the PlatformFontResolver knows about the same font families as described above if your application runs under WSL2.
PlatformFontResolver tries to retrieve this fonts from mnt/c/Windows/Fonts
.
If this folder is not available, an exception is thrown.
Once set, UseWindowsFontsUnderWsl2 cannot be changed anymore.
In GDI or WPF build this option has no effects and PDFsharp logs a warning if you set it.
The purpose of this option is to get you quickly started with developing and testing only. In production code you must use a custom font resolver, because WSL2 is usually not the production environment.
Custom font resolvers
You can override the behavior of PlatformFontResolver by setting a custom font resolver.
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 PDFsharp 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.