Recursive AI

Read a TTF or OTF font file

All onefont recipes

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.

C#
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.