Sample resolvers
Version 6.1.0
In this article
- Segoe WP font resolver
- Failsafe font resolver
- Unit test font resolver
- Samples font resolver
- Substituting font resolver
This article presents some custom font resolvers coming with PDFsharp for demonstration and testing purposes.
Segoe WP font resolver
Segoe WP is a font family with 6 type faces coming from an old Windows Phone SDK. The type faces are pretty, well-designed, and their font files are much smaller than their Segoe UI Windows counterparts. Anyway, this is for demonstration how to write a font resolver only. We do not recommend to use these fonts in your production code. Instead use it as a starting point in your projects where you have no immediately access to font files, like in a Blazor WebAssembly app or within a Docker container.
How it works
The implementation checks first whether the family name starts with segoe wp
.
If so, it tries to find an appropriate type face.
Because none of the type faces are italic, italic is always simulated.
That means, PDFsharp simulates the italic style by skewing characters 20°.
If the family name does not start with segoe wp
the platform font resolver is called.
Here is the source of the Segoe WP font resolver.
using PdfSharp.Fonts;
namespace PdfSharp.Snippets.Font
{
/// <summary>
/// Maps font requests for a SegoeWP font to a set of 6 specific font files.
/// These 6 fonts faces are embedded as resources in the WPFonts assembly.
/// </summary>
public class SegoeWpFontResolver : IFontResolver
{
// ReSharper disable InconsistentNaming
/// <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
/// SegoeWpFontResolver ignores case.
/// </summary>
public static class FamilyNames
{
// This implementation considers each font face as its own family.
public const string SegoeWPLight = "segoe wp light";
public const string SegoeWPSemilight = "segoe wp semilight";
public const string SegoeWP = "segoe wp";
public const string SegoeWPSemibold = "segoe wp semibold";
public const string SegoeWPBold = "segoe wp bold";
public const string SegoeWPBlack = "segoe wp black";
}
/// <summary>
/// The internal names that uniquely identify the font faces.
/// Used in the first parameter of the FontResolverInfo constructor.
/// </summary>
static class FaceNames
{
// Used in the first parameter of the FontResolverInfo constructor.
public const string SegoeWPLight = "SegoeWPLight";
public const string SegoeWPSemilight = "SegoeWPSemilight";
public const string SegoeWP = "SegoeWP";
public const string SegoeWPSemibold = "SegoeWPSemibold";
public const string SegoeWPBold = "SegoeWPBold";
public const string SegoeWPBlack = "SegoeWPBlack";
}
// ReSharper restore InconsistentNaming
/// <summary>
/// Selects a physical font face based on the specified information
/// of a required typeface.
/// </summary>
/// <param name="familyName">Name of the font family.</param>
/// <param name="isBold">Set to <c>true</c> when a bold font face
/// is required.</param>
/// <param name="isItalic">Set to <c>true</c> when an italic font face
/// 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 lowercaseFamilyName = familyName.ToLowerInvariant();
// Looking for a Segoe WP font?
if (lowercaseFamilyName.StartsWith("segoe wp", StringComparison.Ordinal))
{
// Bold simulation is not yet implemented in PDFsharp.
const bool simulateBold = 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 (lowercaseFamilyName)
{
case FamilyNames.SegoeWPLight:
// Just for demonstration use 'Semilight' if bold is requested.
if (isBold)
goto case FamilyNames.SegoeWPSemilight;
faceName = FaceNames.SegoeWPLight;
break;
case FamilyNames.SegoeWPSemilight:
// Do not care about bold for semilight.
faceName = FaceNames.SegoeWPSemilight;
break;
case FamilyNames.SegoeWP:
// Use font 'Bold' if bold is requested.
if (isBold)
goto UseSegoeWPBold;
faceName = FaceNames.SegoeWP;
break;
case FamilyNames.SegoeWPSemibold:
// Do not care about bold for semibold.
faceName = FaceNames.SegoeWPSemibold;
break;
case FamilyNames.SegoeWPBold:
// Just for demonstration use font 'Black' if bold is requested.
if (isBold)
goto case FamilyNames.SegoeWPBlack;
UseSegoeWPBold:
faceName = FaceNames.SegoeWPBold;
break;
case FamilyNames.SegoeWPBlack:
// Do not care about bold for black.
faceName = FaceNames.SegoeWPBlack;
break;
default:
return null;
}
// Tell PDFsharp the effective face name and whether italic should be
// simulated. Since Segoe WP typefaces do not contain any italic font
// always simulate italic if it is requested.
return new FontResolverInfo(faceName, simulateBold, isItalic);
}
// Return null means that the typeface cannot be resolved and PDFsharp forwards
// the typeface request depending on PDFsharp build flavor and operating system.
// Alternatively forward call to PlatformFontResolver.
return PlatformFontResolver.ResolveTypeface(familyName, isBold, isItalic);
}
/// <summary>
/// Gets the bytes of a physical font face with specified face name.
/// </summary>
/// <param name="faceName">A face name previously retrieved by ResolveTypeface.</param>
/// <returns>
/// The bits of the font.
/// </returns>
public byte[]? GetFont(string faceName)
{
// Note: PDFsharp never calls GetFont twice with the same face name.
// Note: If a typeface is resolved by the PlatformFontResolver.ResolveTypeface
// you never come here.
// Return the bytes of a font.
return faceName switch
{
FaceNames.SegoeWPLight => PdfSharp.WPFonts.FontDataHelper.SegoeWPLight,
FaceNames.SegoeWPSemilight => PdfSharp.WPFonts.FontDataHelper.SegoeWPSemilight,
FaceNames.SegoeWP => PdfSharp.WPFonts.FontDataHelper.SegoeWP,
FaceNames.SegoeWPSemibold => PdfSharp.WPFonts.FontDataHelper.SegoeWPSemibold,
FaceNames.SegoeWPBold => PdfSharp.WPFonts.FontDataHelper.SegoeWPBold,
FaceNames.SegoeWPBlack => PdfSharp.WPFonts.FontDataHelper.SegoeWPBlack,
// PDFsharp never calls GetFont with a face name that was not returned
// by ResolveTypeface.
_ => null // Comes here if faceName is from another font resolver.
};
}
}
}
The assembly PdfSharp.WPFonts
contains the 6 font files as embedded resources.
You find it in the PDFsharp
repository.
Failsafe font resolver
The FailsafeFontResolver is a font resolver that maps all requests of any typeface to a style matching Segoe WP font. You can use such a font resolver as the fallback font resolver or in a new project to just get it up and running at first.
Here is the source code.
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using PdfSharp.Logging;
using PdfSharp.Fonts;
namespace PdfSharp.Snippets.Font
{
/// <summary>
/// This font resolver maps each request to a valid font face of the SegoeWP fonts.
/// </summary>
public class FailsafeFontResolver : IFontResolver
{
public FontResolverInfo? ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
string typefaceName =
$"{familyName}{(isBold ? " bold" : "")}{(isItalic ? " italic" : "")}";
// Use either SegoeWP or SegoeWPBold.
var result = SegoeWpFontResolver.ResolveTypeface(
isBold
? SegoeWpFontResolver.FamilyNames.SegoeWPBold
: SegoeWpFontResolver.FamilyNames.SegoeWP,
false, isItalic);
Debug.Assert(result != null);
// No use of LoggerMessages here because this code is only for
// demonstration purposes.
PdfSharpLogHost.FontManagementLogger.LogWarning(
$"{typefaceName} was substituted by a SegoeWP font.");
return result;
}
public byte[]? GetFont(string faceName)
{
PdfSharpLogHost.FontManagementLogger.LogInformation($"Get font for '{faceName}'.");
return SegoeWpFontResolver.GetFont(faceName);
}
static readonly SegoeWpFontResolver SegoeWpFontResolver = new();
}
}
Note that the FailsafeFontResolver calls the SegeoWPFontResolver. This is safe even if you use it as a fallback font resolver. A fallback font resolver must not call the platform font resolver. It’s safe because the typeface request from the FailSafeFontResolver to the SegoeWPFontResolver can always be resolved. Therefore the platform font resolver is never invoked by the SegoeWPFontResolver.
Unit test font resolver
The UnitTestFontResolver provides fonts we need in unit tests, e.g. an emoji font.
The source code is not linked here, but can be found in the PDFsharp
repository on GitHub.
This font resolver was specifically written for PDFsharp unit tests.
Because we want to run unit test under all platform, we uploaded all used font files to our assets repository. You must invoke
.\dev\download-assets.ps1
to download the fonts onto your developer machine.
Samples font resolver
The SamplesFontResolver provides fonts we need in e.g. the PDFsharp.Samples
repository.
It is very much the same as the UnitTestFontResolver but may be maintained differently in future releases.
The source code is not linked here, but can be found in the PDFsharp
repository on GitHub.
This font resolver was specifically written for PDFsharp sample code to make it work on all platforms.
Because we want to run samples under all platforms, we uploaded all used font files to our assets repository. You must invoke
.\dev\download-assets.ps1
in the PDFsharp
and the PDFsharp.Samples
repositories to download the fonts onto your developer machine.
To make it very clear: The sample code runs only within the solution because it requires the assets
folder.
If you extract sample code to your project you must provide a custom font resolver
Substituting font resolver
The SubstitutingFontResolver is based on the former PlatformFontResolver of PDFsharp. Under Windows and WSL2, it searches fonts in the Windows fonts folder.
It contains code that substitutes Windows fonts under Linux with alternate ones. To achieve this, it searches common font folder of some popular Linux distributions. When your application requests Arial, the font FreeSans may be used instead. When your application requests Times New Roman, the font FreeSerif may be used instead.
Warning: Initially, this code was included in the PlatformFontResolver. We removed it there because it may have consequences: It works always on Windows computers, it works sometimes on Linux computers, it never works in MacOS computers. With other words, it was a good hack to get started, but nothing you want in your production code.
So, for production code, write a FontResolver that handles all the fonts you need. Include all required fonts or make sure to download the fonts after start.
Note
As of PDFsharp 6.2.0, the SubstitutingFontResolver is part of the Samples repository.