Working with Font Registry

A font registry is the set of fonts that Graphics Mill can use. When you draw text, every font name in the text is looked up in a font registry. If the font is not there, the text cannot be drawn. So a font registry decides which fonts your output uses and how font names are resolved.

Font registries are represented by the following classes:

  • FontRegistry is the base class. It lets you look fonts up and create Font objects.
  • FontRegistry.Installed is a read-only registry with the fonts installed in the operating system. It is created on first use and shared by the whole application.
  • CustomFontRegistry is a registry that you fill yourself. You can add and remove fonts, add aliases, choose how conflicts are resolved, and handle missing fonts.

Many classes take a font registry. The most common one is Graphics.FontRegistry. Others are ImageGenerator.FontRegistry, GraphicsContainerRasterizer.FontRegistry, and SvgReader.FontRegistry. Methods that measure text, such as Text.GetBlackBox(FontRegistry, Single, Single), take a registry as a parameter. Use the same registry for measuring and for drawing. Otherwise the text can be measured with one font and drawn with another.

This topic covers the following:

Supported Font Formats

Graphics Mill supports vector fonts in the following formats:

  • OpenType (.otf)
  • TrueType (.ttf)
  • PostScript Type 1 (.pfb)

Bitmap fonts, such as Windows .fon fonts, are not supported. An attempt to add one throws UnsupportedFontException. Font collections (.ttc files) are not supported either.

Choosing a Font Registry

By default, Graphics.FontRegistry is the FontRegistry.Installed registry. This is convenient for a quick start. However, the installed fonts depend on the machine. A font can be missing on a server or in a container. Two machines can have different versions of the same font. As a result, the same template can produce different output on different machines.

We recommend using a CustomFontRegistry if you do not want to rely on installed fonts. Add the fonts that your application ships with, and assign the registry to every object that draws or measures text:

C#
using (var fontRegistry = new CustomFontRegistry())
using (var bitmap = new Bitmap(500, 100, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
    // Register only the fonts your application ships with.
    // Add returns one result for each font in the file.
    var comic = fontRegistry.Add(@"C:\Windows\Fonts\comic.ttf").First();
    fontRegistry.Add(@"C:\Windows\Fonts\comicbd.ttf");

    Console.WriteLine("{0}: {1}", comic.PostscriptName, comic.Status);

    // Graphics looks fonts up only in its own registry.
    graphics.FontRegistry = fontRegistry;

    DrawLine(graphics, comic.PostscriptName, "Dedicated font registry", 60);

    bitmap.Save(@"Images\Output\DedicatedFontRegistry.png");
}
Text drawn with a font from a dedicated font registry

You can add a font from a file with CustomFontRegistry.Add(String), or from a stream with CustomFontRegistry.Add(Stream). A stream is useful when fonts are kept in a database or in cloud storage. The registry copies the font data, so you can dispose the stream right after the call. When you add a font from a file, the registry keeps the path and reads the file again when the font is first used. Do not delete or change the file while the registry uses it.

If you need both the installed fonts and your own fonts, call CustomFontRegistry.Merge(FontRegistry) to copy the installed fonts into your registry first, and then add your fonts.

Important

Graphics looks fonts up only in its own registry. A font that you added to another registry stays invisible to it.

Font Names

A font can be identified in two ways:

  • Postscript name, for example TimesNewRomanPS-BoldMT. This is a single string that identifies one font. Text styles, such as CharStyle, refer to fonts by postscript name. Templates created in Adobe applications store postscript names too.
  • Family and style, for example Times New Roman and Bold. This is the name that people see in font menus. Use it when a user picks a font in your UI.

Both systems are needed, and a font registry keeps them consistent. Each postscript name resolves to one font, and each family and style pair resolves to one font. The following members work with both systems:

Font names are not case sensitive, and leading and trailing spaces are ignored. So ArialMT, arialmt, and ARIALMT are the same font. The same rule applies to aliases and to family and style names.

The following snippet looks up the same font in different ways:

C#
using (var fontRegistry = new CustomFontRegistry())
{
    fontRegistry.Add(@"C:\Windows\Fonts\timesbd.ttf");

    // The same font by its postscript name and by its family and style.
    var byPostscriptName = fontRegistry.CreateFont("TimesNewRomanPS-BoldMT", 20, 72, 72);
    var byFamilyAndStyle = fontRegistry.CreateFont("Times New Roman", "Bold", 20, 72, 72);

    Console.WriteLine(byPostscriptName.PostscriptName == byFamilyAndStyle.PostscriptName);

    // Get the postscript name for a family and style without an exception.
    string postscriptName;

    if (fontRegistry.TryGetPostscriptName("Times New Roman", "Bold", out postscriptName))
        Console.WriteLine("Times New Roman Bold is {0}", postscriptName);

    // Names are not case sensitive.
    Console.WriteLine(fontRegistry.Contains("timesnewromanps-boldmt"));

    // List the registered fonts.
    foreach (var fontInfo in fontRegistry.Fonts)
    {
        Console.WriteLine("{0} = {1} {2}", fontInfo.PostscriptName, fontInfo.Family, fontInfo.Style);
    }
}
Note

CreateFont takes the resolution of the target image. It must match the resolution of the Graphics object that draws the text. It is often easier to call Graphics.CreateFont(String, Single), which uses the registry and the resolution of that Graphics object.

Font Aliases

An alias is an extra name for a registered font. It works everywhere a postscript name works. Aliases are useful in the following cases:

  • A template refers to a font by a name that differs from the real postscript name of your font file.
  • A font was renamed in a newer version, but old documents still use the old name.
  • You want to use stable names in your templates, such as BrandHeadline, and change the actual font later.

To add an alias to a registered font, call CustomFontRegistry.AddFontAlias(String, String). To remove it, call CustomFontRegistry.RemoveFontAlias(String, String). The original postscript name remains valid. FontInfo.Aliases lists the aliases of a font.

C#
using (var fontRegistry = new CustomFontRegistry())
using (var bitmap = new Bitmap(500, 100, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
    var postscriptName = fontRegistry.Add(@"C:\Windows\Fonts\comicbd.ttf").First().PostscriptName;

    // A template refers to the font as "BrandHeadline".
    fontRegistry.AddFontAlias(postscriptName, "BrandHeadline");
    fontRegistry.AddFontAlias(postscriptName, "BrandTitle");

    foreach (var alias in fontRegistry.Fonts.First().Aliases)
    {
        Console.WriteLine("Alias: {0}", alias);
    }

    graphics.FontRegistry = fontRegistry;

    DrawLine(graphics, "BrandHeadline", "Drawn with an alias", 60);

    bitmap.Save(@"Images\Output\FontAlias.png");
}
Text drawn with a font alias

An alias must be unique in the registry. If an alias already refers to a different font, adding it throws DuplicateFontAliasException. Aliases are not case sensitive, so Brand and BRAND are the same alias. This exception is thrown regardless of the collision policies described below. An alias is an explicit instruction, so the registry never ignores it silently.

Large Font Sets and Collisions

Real font libraries often contain fonts with conflicting names. This is especially common when customers upload their own fonts. Typical problems are:

  • Identical copies of the same font under different file names.
  • The same postscript name for different fonts. For example, some font families use one postscript name for all styles, or two vendors publish different fonts under the same name.
  • The same family and style for different postscript names. For example, the OpenType and TrueType versions of one font can have different postscript names.

A CustomFontRegistry resolves these conflicts in CustomFontRegistry.Add and CustomFontRegistry.Merge. A font with exactly the same content as an already registered font is always skipped. For other conflicts, two properties define what happens:

Property Values
CustomFontRegistry.PostscriptNameCollisionPolicy defines what happens when a different font already has the same postscript name. Its type is PostscriptNameCollisionPolicy.

Skip (default) keeps the registered font and ignores the new one.

Replace removes the registered font and adds the new one.

Throw throws DuplicatePostscriptNameException.

CustomFontRegistry.FamilyStyleCollisionPolicy defines what happens when a font with a different postscript name already has the same family and style. Its type is FamilyStyleCollisionPolicy.

AddAlias (default) does not add the new font. Its postscript name becomes an alias of the registered font, so both names still work.

Skip does not add the new font. Its postscript name does not resolve to any font.

Replace removes the registered font and adds the new one.

Throw throws DuplicateFamilyStyleException.

With the default values, adding a font never throws an exception because of a conflict. Every name that could be resolved before the call can still be resolved after it.

Add and Merge return a collection of FontAddResult objects, one for each font. The FontAddResult.Status property tells you what happened. Its type is FontAddStatus. FontAddResult.PostscriptName is always the postscript name of the font you added. FontAddResult.ConflictingPostscriptName is the registered font it conflicted with.

Status Meaning
Added The font was added.
AlreadyPresent A font with the same content is already registered. Nothing changed.
Replaced The conflicting font was removed, and this font was added.
Aliased The font was not added. Its postscript name is now an alias of the conflicting font.
SkippedPostscriptConflict The font was not added because a different font has the same postscript name.
SkippedFamilyStyleConflict The font was not added because a different font has the same family and style.

The following snippet loads a folder of fonts and reports the fonts that were not added as is:

C#
using (var fontRegistry = new CustomFontRegistry())
{
    // These are the default values.
    fontRegistry.PostscriptNameCollisionPolicy = PostscriptNameCollisionPolicy.Skip;
    fontRegistry.FamilyStyleCollisionPolicy = FamilyStyleCollisionPolicy.AddAlias;

    foreach (var path in Directory.GetFiles(@"C:\Windows\Fonts", "*.ttf"))
    {
        try
        {
            foreach (var result in fontRegistry.Add(path))
            {
                switch (result.Status)
                {
                    case FontAddStatus.Added:
                    case FontAddStatus.AlreadyPresent:
                        break;

                    case FontAddStatus.Aliased:
                        Console.WriteLine("{0} now resolves to {1}", result.PostscriptName, result.ConflictingPostscriptName);
                        break;

                    default:
                        Console.WriteLine("{0} was not added: {1}, conflicts with {2}",
                            result.PostscriptName, result.Status, result.ConflictingPostscriptName);
                        break;
                }
            }
        }
        catch (FontException ex)
        {
            Console.WriteLine("{0}: cannot load the font. {1}", path, ex.Message);
        }
    }
}

Font Exceptions

All font exceptions are derived from FontException. An exception thrown by CustomFontRegistry.Add(String), Merge, or AddFontAlias never changes the registry. Merge checks all fonts before it adds any of them.

Exception When it is thrown
DuplicatePostscriptNameException A different font has the same postscript name, and the postscript name policy is Throw. The Conflicts property lists the conflicts.
DuplicateFamilyStyleException A different font has the same family and style, and the family and style policy is Throw. After Merge, the Conflicts property lists every conflict, not only the first one.
DuplicateFontAliasException An alias already refers to a different font. It is thrown regardless of the policies.
AmbiguousFontCollisionException The family and style of the new font belong to one registered font, and its postscript name belongs to another one. No policy can resolve both conflicts, so it is thrown regardless of the policies.
UnsupportedFontException The font format is not supported.
FontMissingException A font cannot be found when you create a font object or draw text. See Missing Fonts and Missing Glyphs.
FontException Other font problems, for example a damaged font file.

The following snippet uses the Throw policies to reject a set of uploaded fonts if any of them conflicts with the fonts already registered:

C#
using (var fontRegistry = new CustomFontRegistry())
{
    fontRegistry.Merge(FontRegistry.Installed);

    fontRegistry.PostscriptNameCollisionPolicy = PostscriptNameCollisionPolicy.Throw;
    fontRegistry.FamilyStyleCollisionPolicy = FamilyStyleCollisionPolicy.Throw;

    try
    {
        // All conflicts are checked before any font is added.
        // If there is a conflict, the registry does not change.
        fontRegistry.Merge(uploadedFonts);
    }
    catch (DuplicatePostscriptNameException ex)
    {
        foreach (var conflict in ex.Conflicts)
        {
            Console.WriteLine("{0} conflicts with {1}", conflict.PostscriptName, conflict.ExistingPostscriptName);
        }
    }
    catch (DuplicateFamilyStyleException ex)
    {
        foreach (var conflict in ex.Conflicts)
        {
            Console.WriteLine("{0} conflicts with {1}", conflict.PostscriptName, conflict.ExistingPostscriptName);
        }
    }
}

Missing Fonts and Missing Glyphs

When Graphics Mill needs a font, the registry searches for it in the following order:

  1. The font name, as described in Font Names.
  2. The fonts in FontRegistry.FallbackFonts. The first registered font from this list is used instead of the missing font.
  3. The CustomFontRegistry.FontMissing event. If the handler adds the font, the search is repeated once.

If the font is still not found, FontMissingException is thrown. The search runs both when you create a Font object and when you draw or measure text.

Note

Fallback fonts are checked before the event. If FallbackFonts contains a registered font, the FontMissing event is never raised.

Fallback Fonts

The FallbackFonts collection is a list of postscript names. It is used in two cases:

  • A font is missing. The first registered fallback font replaces it.
  • A glyph is missing. The font exists, but it has no glyph for some characters. For example, many Latin fonts have no Chinese, Japanese, or Korean characters. These characters are drawn with a fallback font that has them.

Names in the list that are not registered are ignored. When you remove a font from the registry, it is also removed from the list.

C#
using (var fontRegistry = new CustomFontRegistry())
using (var bitmap = new Bitmap(500, 160, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
    fontRegistry.Add(@"C:\Windows\Fonts\arial.ttf");
    fontRegistry.Add(@"C:\Windows\Fonts\malgun.ttf");

    // Arial has no Korean glyphs. They are taken from Malgun Gothic.
    fontRegistry.FallbackFonts.Add("MalgunGothic");

    graphics.FontRegistry = fontRegistry;

    DrawLine(graphics, "ArialMT", "Hello, \uC548\uB155\uD558\uC138\uC694", 60);

    // UnknownFont is not registered, so the first fallback font is used instead.
    DrawLine(graphics, "UnknownFont", "Missing font", 120);

    bitmap.Save(@"Images\Output\FontFallback.png");
}
Missing glyphs and a missing font replaced by a fallback font

FontMissing Event

The FontMissing event lets you load fonts on demand. For example, you can load a font from a font storage only when a template really uses it. The event is available only in CustomFontRegistry. The FontMissingEventArgs contain the FontMissingEventArgs.PostscriptName of the missing font and the FontMissingEventArgs.FontRegistry to add the font to:

C#
var fontRegistry = new CustomFontRegistry();

fontRegistry.FontMissing += (sender, e) =>
{
    // For example, a font storage keeps each font in a file named after its postscript name.
    var path = System.IO.Path.Combine("Fonts", e.PostscriptName + ".ttf");

    if (File.Exists(path))
        e.FontRegistry.Add(path);
};

using (var bitmap = new Bitmap(500, 100, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
    graphics.FontRegistry = fontRegistry;

    try
    {
        DrawLine(graphics, "ComicSansMS", "Loaded on demand", 60);
    }
    catch (FontMissingException ex)
    {
        Console.WriteLine(ex.Message);
    }
}

fontRegistry.Dispose();

Font Cache

Before a font can be used for drawing, its file must be parsed. Parsing takes time, so each registry keeps a cache of parsed fonts:

  • The cache belongs to a registry instance. Registries do not share it.
  • A font is parsed when it is first used, not when it is added. This keeps Add fast.
  • A new registry, including a registry filled with Merge, starts with an empty cache. However, it shares the font data with the source registry and does not copy it.
  • When you remove a font or clear the registry, the parsed font is removed from the cache.

The data of fonts added from streams is kept in memory for the lifetime of the registry. Fonts added from files are read from disk when they are parsed.

See Also

Reference

Manual