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.
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}"); }
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
HasGlyphandGetGlyphIndextake a code point (anint), not achar. Characters outside the Basic Multilingual Plane, such as most emoji, are twochars in a .NET string, so walk the text withEnumerateRunes.- Glyph 0 is the font’s missing-glyph box.
HasGlyphis true when the character map gives any other glyph. SelectFacereturns the index of the first face in the chain with a glyph for a code point, or 0 when no face has one.ShapeRunon 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
HasMissingGlyphset. 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
COLRor 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.