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:
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:
Graphics Mill supports vector fonts in the following formats:
.otf).ttf).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.
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:
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");
}
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.
Graphics looks fonts up only in its own registry. A font that you added to another registry stays invisible to it.
A font can be identified in two ways:
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:
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);
}
}
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.
An alias is an extra name for a registered font. It works everywhere a postscript name works. Aliases are useful in the following cases:
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.
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");
}
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.
Real font libraries often contain fonts with conflicting names. This is especially common when customers upload their own fonts. Typical problems are:
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:
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);
}
}
}
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:
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);
}
}
}
When Graphics Mill needs a font, the registry searches for it in the following order:
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.
Fallback fonts are checked before the event. If FallbackFonts contains a registered font, the FontMissing event is never raised.
The FallbackFonts collection is a list of postscript names. It is used in two cases:
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.
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");
}
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:
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();
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 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.