Recursive AI

Fonts: defaults, CJK, emoji and Noto script packs

All onerender recipes

Find out which characters the bundled fonts can’t draw, add the package that covers them (CJK, emoji or one of 29 Noto script packs), and load your own font with @font-face.

OneRender never uses the fonts installed on the machine unless you pass a provider for them, so a page renders the same everywhere. RecursiveAI.OneRender.Fonts, which .Image and .Pdf bring in, has Liberation Sans, Serif and Mono and Carlito and Caladea. They also answer to Arial, Helvetica, Times New Roman, Courier New, Calibri and Cambria, with the same metrics. They cover Latin, Greek, Cyrillic and most of Hebrew.

Find what’s missing

A character no font covers is drawn as an empty box, and the font provider records which package would cover it:

C#
using OneRender.Fonts;
using OneRender.Image;
using OneRender.Pdf;
using OneRender.Text;
 
string html = """
<!DOCTYPE html>
<html lang="en"><head><title>Greetings</title>
<style>body { font: 22px/1.6 sans-serif; margin: 24px } .serif { font-family: "Times New Roman", serif } code { font-family: "Courier New", monospace }</style></head>
<body>
<p>Hello, Grüße, Привет, Γειά σου, שלום</p>
<p class="serif">Times New Roman maps to Liberation Serif. <code>Courier New</code> too.</p>
<p>日本語のテキスト, 中文, 한국어</p>
<p>مرحبا بالعالم · नमस्ते दुनिया · สวัสดีชาวโลก</p>
<p>Emoji: 🎉 👩‍💻 🇺🇸</p>
</body></html>
""";
 
TextFontProvider fonts = FontProviders.CreateDefault();
HtmlPdf.ToPdf(html, File.Create("greetings.pdf"), new HtmlPdfOptions { Fonts = fonts });
 
// Which packages would cover what the default fonts lack:
foreach (string package in fonts.Catalog.Diagnostics.Items
             .Where(d => d.Kind == TextDiagnosticKind.MissingGlyph)
             .Select(d => d.Package ?? "(no package)").Distinct())
    Console.WriteLine(package);
foreach (OptionalFontPackage p in OptionalFonts.Packages)
    Console.WriteLine($"registered: {p.PackageId}, {p.Faces.Count} faces");
 
File.WriteAllBytes("greetings.png", HtmlRender.ToImage(html, new HtmlRenderOptions { Width = 720, Height = 330 }).ToPng());

With only the default fonts, it prints the packages to add:

Text
RecursiveAI.OneRender.Fonts.Cjk
RecursiveAI.OneRender.Fonts.Noto.Arabic
RecursiveAI.OneRender.Fonts.Noto.Devanagari
RecursiveAI.OneRender.Fonts.Noto.Thai
RecursiveAI.OneRender.Fonts.Emoji

The diagnostics live on the provider you pass in Fonts, so create one per job when you want to read them. Each TextDiagnostic also has the code point and a Message.

Add the packages

Terminal
dotnet add package RecursiveAI.OneRender.Fonts.Cjk --version 1.0.0
dotnet add package RecursiveAI.OneRender.Fonts.Noto.Arabic --version 1.0.0
dotnet add package RecursiveAI.OneRender.Fonts.Noto.Devanagari --version 1.0.0
dotnet add package RecursiveAI.OneRender.Fonts.Noto.Thai --version 1.0.0
dotnet add package RecursiveAI.OneRender.Fonts.Emoji --version 1.0.0

There’s no code to change. Each package registers itself when the app starts, through a module initializer its build adds, with no reflection, so it works with NativeAOT and trimming. Run the same program again:

Text
registered: RecursiveAI.OneRender.Fonts.Cjk, 10 faces
registered: RecursiveAI.OneRender.Fonts.Noto.Arabic, 2 faces
registered: RecursiveAI.OneRender.Fonts.Noto.Devanagari, 2 faces
registered: RecursiveAI.OneRender.Fonts.Noto.Thai, 2 faces
registered: RecursiveAI.OneRender.Fonts.Emoji, 1 faces

No missing glyphs are left, and every line is drawn: Arabic shaped right to left, Devanagari conjuncts, Thai, the Japanese, Chinese and Korean text, and the emoji in color, including the ZWJ sequence and the flag. In the PDF the emoji stay text you can copy, with the color bitmaps drawn over it.

The packages

  • .Fonts.Noto.<Script>, 29 packages: Arabic, Armenian, Balinese, Bengali, Cherokee, Devanagari, Ethiopic, Georgian, Gujarati, Gurmukhi, Hebrew, Javanese, Kannada, Khmer, Lao, Malayalam, Math, Mongolian, Myanmar, Nko, Oriya, Sinhala, Symbols, Syriac, Tamil, Telugu, Thaana, Thai and Tibetan. Each has the regular and, where the release has one, bold weight.
  • .Fonts.Cjk: Noto Sans CJK in regular and bold, about 32 MB. Name a region with font-family: "Noto Sans CJK SC" (also JP, KR, TC, HK) to get its glyph forms; without a family name the Japanese forms are used, whatever the lang.
  • .Fonts.Emoji: Noto Color Emoji, about 10 MB.

These are fallback fonts: they supply only characters that the families you list and the default fonts lack. They ship the upstream files unmodified, under the SIL Open Font License, with each file’s source and SHA-256 in the package.

Your own fonts

@font-face works as in a browser, with TrueType, OpenType, WOFF and WOFF2. The file comes through the same Resources callback as everything else:

C#
using OneRender.Image;
using OneRender.Text;
 
string html = """
<style>
  @font-face { font-family: Brand; src: url("/fonts/brand.ttf") format("truetype") }
  body { margin: 16px; font: 28px Brand, sans-serif }
</style>
<p>Set in the brand font</p>
""";
TextFontProvider fonts = FontProviders.CreateDefault();
RgbaImage img = HtmlRender.ToImage(html, new HtmlRenderOptions
{
    Width = 420, Height = 80, Fonts = fonts,
    BaseUrl = "https://example.com/",
    Resources = url => url == "https://example.com/fonts/brand.ttf" ? File.ReadAllBytes("brand.ttf") : null,
});
File.WriteAllBytes("brand.png", img.ToPng());
Console.WriteLine($"{fonts.Catalog.Diagnostics.Items.Count} font diagnostics");
Text
0 font diagnostics

A font file that is missing, damaged or over a size limit becomes a diagnostic of kind FontResourceMissing or FontLoadFailed, and the text falls back to the next family.

Good to know

  • System fonts are used only through an ISystemFontProvider you pass to FontProviders.CreateDefault, and local() in @font-face asks only that provider.
  • On browser WebAssembly, WOFF2 needs decoded bytes from the host, because the platform has no Brotli.
  • Tagged PDF/UA-1 output with whole sentences in complex scripts (Arabic, Devanagari, Thai) can fail veraPDF rule 7.21.7. The text still extracts correctly.

Matching, fallback and shaping are described in Fonts and text.