Table of Contents

Struct Font

Namespace
Egami.Fonts
Assembly
Egami.Fonts.dll

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

typeface Typeface

The face.

size float

The em size in canvas units, finite and at least 0.

Exceptions

ArgumentNullException

typeface is null.

ArgumentOutOfRangeException

size is negative, infinite or NaN.

Properties

AllowSynthesis

Gets whether Synthesis is applied. Defaults to true.

public bool AllowSynthesis { get; init; }

Property Value

bool

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

bool

Edging

Gets how glyph edges become pixels on a raster. Defaults to Antialias.

public FontEdging Edging { get; init; }

Property Value

FontEdging

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

Typeface

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

bool

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

float

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

FontExtents

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

float

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

FontHinting

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

TrueTypeTarget

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

bool

LineHeight

Gets the distance from one baseline to the next at this size.

public float LineHeight { get; }

Property Value

float

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

FontMetrics

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

OpticalSizing

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

FontPalette

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

float

Size

Gets the em size in canvas units.

public float Size { get; }

Property Value

float

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

bool

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

bool

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

FontSynthesis

Typeface

Gets the face; null only for default(Font).

public Typeface Typeface { get; }

Property Value

Typeface

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

typeface Typeface

The face.

points float

The size in points.

dpi float

The 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 dpi is not positive.

GetAdvance(ushort)

Gets a glyph's advance at this size.

public float GetAdvance(ushort glyph)

Parameters

glyph ushort

The 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

glyph ushort

The 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

text ReadOnlySpan<char>

The text.

options ShapingOptions

The 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

options asks 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

text string

The text.

options ShapingOptions

The 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

text is null.

ArgumentException

options asks 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

text string

The text.

options ShapingOptions

The 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

text is null.

ArgumentException

options asks 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) };