---
title: "Measure text width in C# without System.Drawing"
url: https://recursiveai.org/onefont/cookbook/measure-text-width/
description: "Measure text width in C# from the font file, with kerning and ligatures, in pixels at any size, plus the line height, without System.Drawing or a graphics context. Runnable code with onefont."
version: "1.3.0"
---

# Measure text width in C# without System.Drawing

Shape the string, add up the cluster advances, and scale from font units to pixels with `size / face.UnitsPerEm`. The width includes the font’s kerning and ligatures, and it is the same on every platform because it comes from the font file, not from an installed font or a graphics context.

```shell
dotnet new console -o MeasureText && cd MeasureText
dotnet add package RecursiveAI.OneFont --version 1.3.0
NOTO=https://github.com/notofonts/notofonts.github.io/raw/main/fonts
curl -LO $NOTO/NotoSerif/hinted/ttf/NotoSerif-Regular.ttf
```

Put this in `Program.cs` and run `dotnet run`:

```csharp
using OneFont;

FontFace face = FontFace.Load(File.ReadAllBytes("NotoSerif-Regular.ttf"));

var noKerning = new ShapingOptions { Kerning = KerningMode.None };
foreach (string text in new[] { "Wave", "office", "Invoice total: $1,240.00" })
{
    float width = Width(text, 16), unkerned = Width(text, 16, noKerning);
    Console.WriteLine($"\"{text}\" at 16 px: {width:F2} px wide "
        + $"(without kerning {unkerned:F2} px)");
}

// The same text at other sizes: width scales linearly with the font size.
var sizes = new[] { 12f, 24f, 48f }.Select(px => Width("Wave", px).ToString("F2"));
Console.WriteLine($"\"Wave\" at 12 / 24 / 48 px: {string.Join(" / ", sizes)}");

// Height of a line, and of the ink above and below the baseline, at 16 px.
FontMetrics m = face.Metrics;
float s = 16f / face.UnitsPerEm;
Console.WriteLine($"line height {m.LineHeight * s:F2} px, "
    + $"ascender {m.Ascender * s:F2}, descender {m.Descender * s:F2}");

// Shape, add up the advances (font units), scale to pixels.
float Width(string text, float size, ShapingOptions? options = null)
{
    int units = TextShaping.ShapeRun(text, face, options: options).Sum(c => c.Advance);
    return units * size / face.UnitsPerEm;
}
```

```text
"Wave" at 16 px: 42.46 px wide (without kerning 43.58 px)
"office" at 16 px: 41.70 px wide (without kerning 41.70 px)
"Invoice total: $1,240.00" at 16 px: 174.21 px wide (without kerning 174.21 px)
"Wave" at 12 / 24 / 48 px: 31.85 / 63.70 / 127.39
line height 21.79 px, ascender 17.10, descender -4.69
```

## How it works

- **Advances.** `Advance` on a cluster is the sum of its glyphs’ advances in design units, including pair kerning with the cluster that follows. Kerning pulls “W” and “a” together, so “Wave” is narrower than its letters added up.
- **Ligatures.** “office” is measured with the font’s ffi ligature, one glyph for three letters, which is why measuring character by character from the character map gives the wrong width.
- **Scaling.** Design units times `size / UnitsPerEm` gives pixels when the size is in pixels, or points when it is in points.
- **Height.** `Metrics.LineHeight` is ascender minus descender plus line gap. The descender is negative: it is below the baseline.

## Several fonts or scripts

For text that needs more than one font, measure through a [`FontFallbackChain`](https://recursiveai.org/onefont/cookbook/font-supports-character/): each cluster has a `FaceIndex`, and each face has its own `UnitsPerEm`. Arabic, Hebrew and Indic text is measured the same way, since shaping produces the joined forms and conjuncts first.

## Limits

- **Advance width, not ink.** The width is where the next text would start. A glyph’s ink can reach past it (an italic f, for example). For the exact ink box, read the outlines’ `TightBox()`, as in [text to SVG path](https://recursiveai.org/onefont/cookbook/text-to-svg-path/).
- **Unhinted.** Advances are the font’s design advances, not snapped to whole pixels as some renderers do at small sizes.
- **One line.** To fit text into a width, see [Wrap text to a width](https://recursiveai.org/onefont/cookbook/measure-and-wrap-text/).
