Recursive AI

Check if a font supports a character

All onefont recipes

Find out in C# whether a font has a glyph for a character, list the characters it lacks, and fall back to other fonts cluster by cluster.

A font supports a character when its Unicode character map gives it a glyph. HasGlyph answers that for one code point. For text with several scripts, a FontFallbackChain picks a face for each cluster and shapes it with that face’s own tables.

C#
using System.Text;
using OneFont;
 
FontFace noto = FontFace.Load(File.ReadAllBytes("NotoSans-Regular.ttf"));
FontFace dejavu = FontFace.Load(File.ReadAllBytes("DejaVuSans.ttf"));
FontFace cjk = FontFace.Load(File.ReadAllBytes("NotoSansCJK-Regular.ttc"), faceIndex: 0);
 
string text = "Café ✓ 東京 😀 क";
 
// One character: HasGlyph takes a code point, so surrogate pairs (emoji) need Rune, not char.
Console.WriteLine($"Noto Sans has 'é': {noto.HasGlyph('é')}, has '✓': {noto.HasGlyph(0x2713)}");
 
// Every character a font lacks.
var missing = text.EnumerateRunes().Where(r => !Rune.IsWhiteSpace(r) && !noto.HasGlyph(r.Value));
Console.WriteLine($"Noto Sans lacks: {string.Join(" ", missing.Select(r => $"U+{r.Value:X4}"))}");
 
// Pick a face for each character, then shape through the chain.
var chain = new FontFallbackChain([noto, dejavu, cjk]);
Console.WriteLine($"'✓' comes from face {chain.SelectFace(0x2713)}, '東' from face {chain.SelectFace('東')}");
foreach (ShapedCluster c in chain.ShapeRun(text).Where(c => !char.IsWhiteSpace(text[c.Start])))
{
    string note = c.HasMissingGlyph ? ", missing: no face has it" : "";
    Console.WriteLine($"  {text.Substring(c.Start, c.Length)}  {chain.Faces[c.FaceIndex].FamilyName}{note}");
}
Text
Noto Sans has 'é': True, has '✓': False
Noto Sans lacks: U+2713 U+6771 U+4EAC U+1F600 U+0915
'✓' comes from face 1, '東' from face 2
  C  Noto Sans
  a  Noto Sans
  f  Noto Sans
  é  Noto Sans
  ✓  DejaVu Sans
  東  Noto Sans CJK JP
  京  Noto Sans CJK JP
  😀  DejaVu Sans
  क  Noto Sans, missing: no face has it
  • HasGlyph and GetGlyphIndex take a code point (an int), not a char. Characters outside the Basic Multilingual Plane, such as most emoji, are two chars in a .NET string, so walk the text with EnumerateRunes.
  • Glyph 0 is the font’s missing-glyph box. HasGlyph is true when the character map gives any other glyph.
  • SelectFace returns the index of the first face in the chain with a glyph for a code point, or 0 when no face has one.
  • ShapeRun on the chain chooses a face for each grapheme cluster by its first character. Invisible characters (controls, zero width spaces and joiners, soft hyphens, bidi controls) take the face of the cluster before them. Each stretch of one face is shaped with that face’s GSUB, GPOS and GDEF tables.
  • A character no face has stays with the first face, and its cluster has HasMissingGlyph set. Here none of the three fonts has Devanagari.
  • DejaVu Sans has an outline glyph for 😀, so the chain uses it. onefont doesn’t draw color glyphs: a color emoji font’s COLR or bitmap tables are available only as raw bytes.
  • A chain holds at most 32 faces.

A glyph in the character map is not the whole story for complex scripts: a font can have every letter of a script and still lack the conjuncts or contextual forms a word needs. For those, see Shape complex scripts.