Struct Font
A typeface at a size: the em in canvas units (pixels on a raster, points on a PDF page). Scales the face's font-unit metrics and turns text into a positioned GlyphRun.
public readonly record struct Font : IEquatable<Font>
- Implements
- Inherited Members
- Extension Methods
Examples
// A caption on a photo: white text with a dark shadow, its baseline 24 pixels above the bottom.
using Raster<Rgba8> photo = Raster.Create(640, 360, new Rgba8(90, 120, 160, 255));
var font = new Font(Typeface.FromFile(FontFile), 28);
using (RasterCanvas<Rgba8> canvas = photo.CreateCanvas())
{
var origin = new Vector2(24, photo.Height - 24);
canvas.DrawText("Lofoten, March", origin + new Vector2(2, 2), font, new Rgba8(0, 0, 0, 160));
canvas.DrawText("Lofoten, March", origin, font, Rgba8.White);
}
Remarks
There is no DPI: a 12-unit font on a raster is 12 pixels per em, and the canvas transform scales it like any other drawing. Use FromPoints(Typeface, float, float) to size from points at a resolution.
default(Font) has no typeface: its Typeface is null, its Scale 0, and the
members that need the face (metrics, advances, shaping and measuring) throw InvalidOperationException. Create
fonts with the constructor or FromPoints(Typeface, float, float).
Constructors
Font(Typeface, float)
Initializes a font.
public Font(Typeface typeface, float size)
Parameters
Exceptions
- ArgumentNullException
typefaceis null.- ArgumentOutOfRangeException
sizeis negative, infinite or NaN.
Properties
AllowSynthesis
Gets whether Synthesis is applied. Defaults to true.
public bool AllowSynthesis { get; init; }
Property Value
ColorGlyphs
Gets whether color glyphs (COLR, sbix, CBDT) draw in color; false draws every glyph's outline in the paint. Defaults to true.
public bool ColorGlyphs { get; init; }
Property Value
Edging
Gets how glyph edges become pixels on a raster. Defaults to Antialias.
public FontEdging Edging { get; init; }
Property Value
Exceptions
- ArgumentOutOfRangeException
The value is not defined.
EffectiveTypeface
Gets the face glyphs are drawn and measured from: Typeface, or with OpticalSizing on a face with an opsz axis it does not set, the face at opsz = Size. A PDF writer embeds this face.
public Typeface EffectiveTypeface { get; }
Property Value
Exceptions
- InvalidOperationException
The font is
default(Font), which has no typeface.
EmbeddedBitmaps
Gets whether the face's embedded black-and-white or gray bitmaps (the EBLC and EBDT tables) are used, as Windows uses them: only at a size of exactly a strike's pixels per em, for the glyphs the strike holds. Those glyphs are drawn from the bitmap instead of the outline (aliased, whatever the edging, and only under a scale-and-translate transform at that size on a raster), and with grid-fitted Metrics they take the strike's advances. Defaults to false; faces without strikes are not affected.
public bool EmbeddedBitmaps { get; init; }
Property Value
Examples
// Text that looks like System.Drawing's Graphics.DrawString with TextRenderingHint.ClearTypeGridFit: the font's own hints
// and grid-fitted advances, GDI+'s ClearType filter, sampling and gamma.
Font label = new Font(face, 16).WithGdiPlusLook();
using (RasterCanvas<Rgb8> canvas = button.CreateCanvas(CanvasOptions.GdiPlus))
{
float width = label.MeasureText("Start the course").Advance;
canvas.DrawText("Start the course", new Vector2(MathF.Round((200 - width) / 2), 26), label, Rgba8.White);
}
// A different Graphics.TextContrast (0 to 12) is a different text gamma: 1 + contrast / 10.
CanvasOptions contrast8 = CanvasOptions.GdiPlus with { TextGamma = 1 + (8 / 10f) };
Embolden
Gets how far outlines grow on every side, in canvas units at this size (pixels on a raster): 0.15 makes each stem 0.3 pixels heavier. Defaults to 0. Applied as synthetic bold is, with round joins, on top of it; advances do not change. Drawn, measured as ink and returned by glyph paths.
public float Embolden { get; init; }
Property Value
Examples
// A little more weight without a bold face: outlines grow 0.2 pixels on each side, advances stay as they are.
var heavier = new Font(face, 14) { Embolden = 0.2f };
// Darkening for thin stems only: it fades out as the face's stems grow past 1.667 pixels.
var caption = new Font(face, 9) { StemDarkening = true };
using (RasterCanvas<Rgba8> canvas = image.CreateCanvas())
{
canvas.DrawText("Slightly heavier", new Vector2(8, 24), heavier, Rgba8.Black);
canvas.DrawText("A darkened caption", new Vector2(8, 48), caption, Rgba8.Black);
}
Exceptions
- ArgumentOutOfRangeException
The value is negative, infinite or NaN.
Extents
Gets the face's extents at this size, y up.
public FontExtents Extents { get; }
Property Value
Exceptions
- InvalidOperationException
The font is
default(Font), which has no typeface.
GdiPlusAscent
Gets how far below a line's top GDI+ (System.Drawing's DrawString) puts the baseline, at this size: the cell ascent GDI+ reports (FontFamily.GetCellAscent: the OS/2 Windows ascent, or the typographic one when the font sets USE_TYPO_METRICS). For a grid-fitted font (GridFitted with Full, as WithGdiPlusLook() sets) it is the whole pixels ClearTypeGridFit puts the first line's baseline below the line's rounded top: the cell ascent rounded, or the font's VDMX maximum at the size rounded to whole pixels per em when that is larger, as measured from GDI+.
public float GdiPlusAscent { get; }
Property Value
Examples
// Centred in a box as DrawString with StringFormat { Alignment = Center, LineAlignment = Center } puts it.
Font label = new Font(face, 16).WithGdiPlusLook();
var box = new RectF(0, 0, 200, 36);
float baseline = GdiPlusText.Baseline(label, box, VerticalAlign.Center);
using (RasterCanvas<Rgb8> canvas = button.CreateCanvas(CanvasOptions.GdiPlus))
{
float width = label.MeasureText("Start the course").Advance;
canvas.DrawText("Start the course", new Vector2(MathF.Round((box.Width - width) / 2), baseline), label, Rgba8.White);
}
// The ascent it used: whole pixels for a grid-fitted font.
float ascent = label.GdiPlusAscent;
Exceptions
- InvalidOperationException
The font is
default(Font), which has no typeface.
Hinting
Gets how outlines are fitted to the pixel grid on a raster. Defaults to None.
public FontHinting Hinting { get; init; }
Property Value
Exceptions
- ArgumentOutOfRangeException
The value is not defined.
HintingTarget
Gets the rasterizer Full hints for and GridFitted measures with. Defaults to GdiPlus.
public TrueTypeTarget HintingTarget { get; init; }
Property Value
Examples
// The font's own TrueType instructions, run for GDI+, with its hinted advances: glyphs on whole pixels, spacing as Windows
// lays out ClearType text. Together with GDI+'s filter, sampling and gamma (1 + TextContrast/10 = 1.4), this is the closest
// Egami gets to System.Drawing's ClearTypeGridFit.
var label = new Font(face, 16)
{
Hinting = FontHinting.Full,
HintingTarget = TrueTypeTarget.GdiPlus,
Metrics = FontMetrics.GridFitted,
Edging = FontEdging.Lcd,
};
var gdiPlus = new CanvasOptions
{
PixelGeometry = PixelGeometry.RgbHorizontal,
LcdFilter = LcdFilter.Gdi,
LcdSampling = LcdSampling.Gdi,
TextGamma = 1.4f,
};
using (RasterCanvas<Rgb8> canvas = button.CreateCanvas(gdiPlus))
{
// Measuring uses the same hinted advances, so centring is exact.
float width = label.MeasureText("Start the course").Advance;
canvas.DrawText("Start the course", new Vector2(MathF.Round((200 - width) / 2), 26), label, Rgba8.White);
}
Exceptions
- ArgumentOutOfRangeException
The value is not defined.
Kerning
Gets whether pair kerning is applied when shaping. Defaults to true.
public bool Kerning { get; init; }
Property Value
LineHeight
Gets the distance from one baseline to the next at this size.
public float LineHeight { get; }
Property Value
Exceptions
- InvalidOperationException
The font is
default(Font), which has no typeface.
Metrics
Gets where advances come from: Design (the default) or GridFitted, the hinted advances at the font's size rounded to a whole number of pixels per em, with whole-pixel glyph origins when drawn. Shaping, measuring and layout all follow it, so measured text always matches drawn text. Subpixel origins do not apply when it is not Design.
public FontMetrics Metrics { get; init; }
Property Value
Examples
// The font's own TrueType instructions, run for GDI+, with its hinted advances: glyphs on whole pixels, spacing as Windows
// lays out ClearType text. Together with GDI+'s filter, sampling and gamma (1 + TextContrast/10 = 1.4), this is the closest
// Egami gets to System.Drawing's ClearTypeGridFit.
var label = new Font(face, 16)
{
Hinting = FontHinting.Full,
HintingTarget = TrueTypeTarget.GdiPlus,
Metrics = FontMetrics.GridFitted,
Edging = FontEdging.Lcd,
};
var gdiPlus = new CanvasOptions
{
PixelGeometry = PixelGeometry.RgbHorizontal,
LcdFilter = LcdFilter.Gdi,
LcdSampling = LcdSampling.Gdi,
TextGamma = 1.4f,
};
using (RasterCanvas<Rgb8> canvas = button.CreateCanvas(gdiPlus))
{
// Measuring uses the same hinted advances, so centring is exact.
float width = label.MeasureText("Start the course").Advance;
canvas.DrawText("Start the course", new Vector2(MathF.Round((200 - width) / 2), 26), label, Rgba8.White);
}
Exceptions
- ArgumentOutOfRangeException
The value is not defined.
OpticalSizing
Gets whether a variable face's optical size axis follows Size: Auto (the default) sets opsz to the size in canvas units, clamped to the axis, unless the typeface sets opsz itself.
public OpticalSizing OpticalSizing { get; init; }
Property Value
Exceptions
- ArgumentOutOfRangeException
The value is not defined.
Palette
Gets the palette color glyphs draw with. Defaults to Default.
public FontPalette Palette { get; init; }
Property Value
Exceptions
- ArgumentNullException
The value is null.
Scale
Gets the factor from font units to canvas units: Size over units per em.
public float Scale { get; }
Property Value
Size
Gets the em size in canvas units.
public float Size { get; }
Property Value
StemDarkening
Gets whether small text is darkened: outlines grow by half the darkening amount of FreeType's documented default CFF curve on every side, the amount set by how wide the face's vertical stems are in device pixels (0.4 pixel for stems up to 0.5 pixel, 0.275 from 1 to 1.667, none from 2.333, linear between), so thin stems keep their weight where antialiasing would wash them out. Defaults to false. It follows the device size, so it applies when drawing on a raster and not to glyph paths or ink bounds.
public bool StemDarkening { get; init; }
Property Value
Examples
// A little more weight without a bold face: outlines grow 0.2 pixels on each side, advances stay as they are.
var heavier = new Font(face, 14) { Embolden = 0.2f };
// Darkening for thin stems only: it fades out as the face's stems grow past 1.667 pixels.
var caption = new Font(face, 9) { StemDarkening = true };
using (RasterCanvas<Rgba8> canvas = image.CreateCanvas())
{
canvas.DrawText("Slightly heavier", new Vector2(8, 24), heavier, Rgba8.Black);
canvas.DrawText("A darkened caption", new Vector2(8, 48), caption, Rgba8.Black);
}
Subpixel
Gets whether glyph origins keep quarter-pixel x positions on a raster (four cached phases; y snaps to whole pixels), or snap to whole pixels as in 2.7.1. Defaults to true. Measuring never depends on it.
public bool Subpixel { get; init; }
Property Value
Synthesis
Gets the styling the face lacks and matching asked for: bold outsets the outline (em/24 at 9 pixels per em down to em/32 at 36 and above, advances unchanged) and italic slants it 14 degrees. Drawn, measured as ink and returned by glyph paths unless AllowSynthesis is off.
public FontSynthesis Synthesis { get; init; }
Property Value
Typeface
Gets the face; null only for default(Font).
public Typeface Typeface { get; }
Property Value
Methods
FromPoints(Typeface, float, float)
Creates a font sized in points at a resolution: points * dpi / 72 canvas units per em.
public static Font FromPoints(Typeface typeface, float points, float dpi)
Parameters
typefaceTypefaceThe face.
pointsfloatThe size in points.
dpifloatThe canvas units per inch, such as 96 for a screen or 72 for a PDF page.
Returns
- Font
The font.
Exceptions
- ArgumentOutOfRangeException
The size is negative, infinite or NaN, or
dpiis not positive.
GetAdvance(ushort)
Gets a glyph's advance at this size.
public float GetAdvance(ushort glyph)
Parameters
glyphushortThe glyph id.
Returns
- float
The advance in canvas units.
Exceptions
- InvalidOperationException
The font is
default(Font), which has no typeface.
GetGlyphExtents(ushort)
Gets the box around a glyph's ink at this size, y up from the baseline, synthetic bold and italic included.
public GlyphExtents GetGlyphExtents(ushort glyph)
Parameters
glyphushortThe glyph id.
Returns
- GlyphExtents
The box in canvas units.
Exceptions
- InvalidOperationException
The font is
default(Font), which has no typeface.
MeasureText(ReadOnlySpan<char>, ShapingOptions?)
Measures text as Shape(string, ShapingOptions?) lays it out, as MeasureText(string, ShapingOptions?) does, without making a string of it: a slice of a larger text or a buffer you reuse.
public TextMeasure MeasureText(ReadOnlySpan<char> text, ShapingOptions? options = null)
Parameters
textReadOnlySpan<char>The text.
optionsShapingOptionsThe shaping options, as for Shape(string, ShapingOptions?); span ranges index
text.
Returns
- TextMeasure
The widest line's advance, the exact ink bounds, the line count and the vertical metrics.
Examples
// Measure each word of a longer text in place: a span is measured without making a string of it.
ReadOnlySpan<char> text = "Measure every word of this sentence";
float widest = 0;
foreach (Range word in text.Split(' '))
{
widest = Math.Max(widest, font.MeasureText(text[word]).Advance);
}
Exceptions
- ArgumentException
optionsasks for fallback or a span in another font.- InvalidOperationException
The font is
default(Font), which has no typeface.
MeasureText(string, ShapingOptions?)
Measures text as Shape(string, ShapingOptions?) lays it out, so a measured width always equals the drawn advance.
public TextMeasure MeasureText(string text, ShapingOptions? options = null)
Parameters
textstringThe text.
optionsShapingOptionsThe shaping options, as for Shape(string, ShapingOptions?).
Returns
- TextMeasure
The widest line's advance, the exact ink bounds, the line count and the vertical metrics.
Examples
// A label shrunk until it fits a 200-pixel button, then centred on it.
const string label = "Download the full report";
var box = new RectF(20, 20, 200, 40);
var font = new Font(face, 24);
while (font.MeasureText(label).Advance > box.Width - 16 && font.Size > 8)
{
font = new Font(face, font.Size - 1);
}
TextMeasure size = font.MeasureText(label);
var origin = new Vector2(
box.X + ((box.Width - size.Advance) / 2),
box.Y + ((box.Height - size.Height) / 2) + size.Ascent);
using Raster<Rgba8> image = Raster.Create(240, 80, Rgba8.White);
using (RasterCanvas<Rgba8> canvas = image.CreateCanvas())
{
canvas.FillRoundRect(new RoundRect(box, 8), new Rgba8(30, 90, 200, 255));
canvas.DrawText(label, origin, font, Rgba8.White);
}
Exceptions
- ArgumentNullException
textis null.- ArgumentException
optionsasks for fallback or a span in another font.- InvalidOperationException
The font is
default(Font), which has no typeface.
Shape(string, ShapingOptions?)
Turns text into a glyph run with the full shaper (Shape(string, Font, ShapingOptions?)): ligatures,
kerning, marks and the script rules of the font's GSUB and GPOS, bidi order, one glyph run for every line. \n,
\r\n, \r, U+0085, U+2028 and U+2029 start a new line one LineHeight lower at x = 0 (a vertical
line one LineHeight to the left); other control characters give no glyph; characters the face lacks give .notdef.
public GlyphRun Shape(string text, ShapingOptions? options = null)
Parameters
textstringThe text.
optionsShapingOptionsThe direction, language, script, features and spans, or null for Default. One run has one font, so the options may not ask for fallback or span fonts; draw such text from Shape(string, Font, ShapingOptions?).
Returns
- GlyphRun
The run, with the text and a cluster per glyph; positions are relative to the first baseline's left end, y down.
Examples
// A caption on a photo: white text with a dark shadow, its baseline 24 pixels above the bottom.
using Raster<Rgba8> photo = Raster.Create(640, 360, new Rgba8(90, 120, 160, 255));
var font = new Font(Typeface.FromFile(FontFile), 28);
using (RasterCanvas<Rgba8> canvas = photo.CreateCanvas())
{
var origin = new Vector2(24, photo.Height - 24);
canvas.DrawText("Lofoten, March", origin + new Vector2(2, 2), font, new Rgba8(0, 0, 0, 160));
canvas.DrawText("Lofoten, March", origin, font, Rgba8.White);
}
Exceptions
- ArgumentNullException
textis null.- ArgumentException
optionsasks for fallback or a span in another font.- InvalidOperationException
The font is
default(Font), which has no typeface.
WithGdiPlusLook()
This font as System.Drawing draws ClearTypeGridFit text: the font's TrueType instructions for GDI+
(Full, GdiPlus), GridFitted advances, LCD
edging and the face's EmbeddedBitmaps. Draw it on a canvas with Egami.Drawing's CanvasOptions.GdiPlus.
public Font WithGdiPlusLook()
Returns
- Font
The font with those settings.
Examples
// Text that looks like System.Drawing's Graphics.DrawString with TextRenderingHint.ClearTypeGridFit: the font's own hints
// and grid-fitted advances, GDI+'s ClearType filter, sampling and gamma.
Font label = new Font(face, 16).WithGdiPlusLook();
using (RasterCanvas<Rgb8> canvas = button.CreateCanvas(CanvasOptions.GdiPlus))
{
float width = label.MeasureText("Start the course").Advance;
canvas.DrawText("Start the course", new Vector2(MathF.Round((200 - width) / 2), 26), label, Rgba8.White);
}
// A different Graphics.TextContrast (0 to 12) is a different text gamma: 1 + contrast / 10.
CanvasOptions contrast8 = CanvasOptions.GdiPlus with { TextGamma = 1 + (8 / 10f) };