---
title: "Read a TTF or OTF font file"
url: https://recursiveai.org/onefont/cookbook/read-font-file/
description: "Read a font’s names, style, metrics and tables in C# from a TTF, OTF or TTC file, on any platform, and tell TrueType outlines from CFF."
version: "1.3.0"
---

# Read a TTF or OTF font file

Read a font’s names, style, metrics and tables in C# from a TTF, OTF or TTC file, on any platform, and tell TrueType outlines from CFF.

`FontFace.Load` reads TrueType fonts, OpenType fonts with CFF outlines, and TrueType Collections from a byte array. It doesn’t need the font to be installed, and it doesn’t use System.Drawing or any operating system font API.

```csharp
using OneFont;

string[] files = ["NotoSans-Regular.ttf", "NotoSansCJK-Regular.ttc", "KaTeX_Main-Regular.woff2"];
string[] tables = ["glyf", "CFF ", "GSUB", "GPOS", "kern", "COLR", "fvar"];

foreach (string file in files)
{
    byte[] data = File.ReadAllBytes(file);
    try
    {
        int faces = FontFace.GetFaceCount(data);
        FontFace face = FontFace.Load(data);   // face 0; pass faceIndex for the others in a collection
        Console.WriteLine($"{file}: {faces} face(s)");
        Console.WriteLine($"  family \"{face.FamilyName}\", style \"{face.SubfamilyName}\", full \"{face.FullName}\"");
        string outlines = face.IsCff ? "CFF (cubic)" : "TrueType (quadratic)";
        Console.WriteLine($"  weight {face.Weight}, bold {face.IsBold}, italic {face.IsItalic}, outlines {outlines}");
        FontMetrics m = face.Metrics;
        Console.WriteLine($"  {m.UnitsPerEm} units/em, ascender {m.Ascender}, descender {m.Descender}");
        Console.WriteLine($"  x-height {m.XHeight}, cap height {m.CapHeight}, line gap {m.LineGap}");
        Console.WriteLine($"  tables: {string.Join(", ", tables.Where(face.HasTable).Select(t => t.Trim()))}");
        Console.WriteLine($"  name table: {face.GetTable("name").Length} bytes");
        if (faces > 1)
        {
            var names = Enumerable.Range(0, faces).Select(i => FontFace.Load(data, i).FamilyName);
            Console.WriteLine($"  all faces: {string.Join(", ", names)}");
        }
    }
    catch (FontFormatException e)
    {
        Console.WriteLine($"{file}: not readable: {e.Message}");
    }
}
```

```text
NotoSans-Regular.ttf: 1 face(s)
  family "Noto Sans", style "Regular", full "Noto Sans Regular"
  weight 400, bold False, italic False, outlines TrueType (quadratic)
  1000 units/em, ascender 1069, descender -293
  x-height 536, cap height 714, line gap 0
  tables: glyf, GSUB, GPOS
  name table: 1660 bytes
NotoSansCJK-Regular.ttc: 10 face(s)
  family "Noto Sans CJK JP", style "Regular", full "Noto Sans CJK JP"
  weight 400, bold False, italic False, outlines CFF (cubic)
  1000 units/em, ascender 1160, descender -288
  x-height 543, cap height 733, line gap 0
  tables: CFF, GSUB, GPOS
  name table: 2146 bytes
  all faces: Noto Sans CJK JP, Noto Sans CJK KR, Noto Sans CJK SC, Noto Sans CJK TC, Noto Sans CJK HK, Noto Sans Mono CJK JP, Noto Sans Mono CJK KR, Noto Sans Mono CJK SC, Noto Sans Mono CJK TC, Noto Sans Mono CJK HK
KaTeX_Main-Regular.woff2: not readable: Not an sfnt font: unknown version tag.
```

- **Names.** `FamilyName` is the typographic family when the font has one, else the legacy family. `SubfamilyName` is the style, and `FullName` and `PostScriptName` are `null` when the font has none.
- **Style.** `Weight` is the OS/2 weight class, 100 to 900. `IsBold` comes from the OS/2 selection flags, the `head` style bits or a weight of 600 or more, and `IsItalic` covers oblique faces too.
- **TrueType or CFF.** The `.ttf` and `.otf` extensions don’t decide it: the outlines do. `IsCff` is true for cubic CFF outlines and false for quadratic TrueType (`glyf`) outlines. Both kinds can carry the OpenType layout tables `GSUB` and `GPOS`, and onefont shapes with them either way.
- **Metrics.** `Metrics` holds the ascender, descender, line gap, x-height, cap height, and the underline and strikeout positions, all in design units. Divide by `UnitsPerEm` and multiply by the font size.
- **Raw tables.** `HasTable` and `GetTable` reach any table by its four-character tag, as stored in the file and not interpreted. A tag shorter than four characters is padded with spaces, so `"CFF"` finds `CFF `.
- **Collections.** `GetFaceCount` returns the number of faces (at most 256), and `Load(data, faceIndex)` reads one of them.
- **Errors.** A file that isn’t a font, or is damaged or truncated, throws `FontFormatException`. WOFF and WOFF2 are compressed wrappers that onefont doesn’t unpack, so the KaTeX `.woff2` above is refused. CFF2 fonts aren’t read either.

A face keeps a reference to the byte array and reads tables from it on demand, so leave the array unchanged while the face is in use. Faces are immutable and safe to use from several threads.

The first steps with a font are in [Getting started](https://recursiveai.org/onefont/docs/getting-started/).
