Font Management
Comprehensive font loading, caching, and discovery for all Beep WinForms controls
Overview
The FontManagement subsystem provides application-wide font loading, caching, and discovery for every Beep WinForms control. It ships a large catalog of embedded font resources while remaining fully extensible — theme packs, plugins, and consuming projects can register their own fonts at any time without modifying the core library.
Architecture
BeepFontManager
The top-level static API. All controls and painters use this class to obtain Font instances rather than creating fonts directly. Fonts are lazy-initialised and cached for the application lifetime.
Configuration Properties
| Property | Type | Default | Description |
|---|---|---|---|
DefaultFontName | string | "Segoe UI" | Name used for the DefaultFont instance |
DefaultFontSize | float | 9.0f | Point size for the DefaultFont |
DefaultFontStyle | FontStyle | Regular | Style for the DefaultFont |
AppFontName | string | "Segoe UI" | Name used for all element-specific fonts |
AppFontSize | float | 9.0f | Base point size for element fonts |
AppFontStyle | FontStyle | Regular | Base style for element fonts |
Setting any configuration property calls ResetFonts() internally, which recreates all cached element font instances.
Element Font Properties (read-only)
| Property | Size offset | Style | Use case |
|---|---|---|---|
DefaultFont | base | DefaultFontStyle | Generic fallback font |
ButtonFont | +0 | AppFontStyle | Button labels |
LabelFont | +0 | AppFontStyle | Regular labels |
HeaderFont | +2 | Bold | Section headers |
TitleFont | +4 | Bold | Window / dialog titles |
MenuFont | +0 | AppFontStyle | Menu items |
TooltipFont | −1 | AppFontStyle | Tooltip popups |
StatusBarFont | −1 | AppFontStyle | Status bar text |
DialogFont | +0 | AppFontStyle | Dialog body text |
MonospaceFont | +0 (Consolas) | AppFontStyle | Code editors, log viewers |
Discovery Lists
| Property | Source | Description |
|---|---|---|
SystemFontNames | FontListHelper | Names of all installed system fonts |
CustomFontNames | FontListHelper | Names of loaded private / embedded fonts |
AllFontNames | FontListHelper | Union of system + custom font names |
EmbeddedFontFamilies | BeepFontPaths | Family names shipping in the Controls assembly |
FontConfigurations | FontListHelper | Full FontConfiguration list for all loaded fonts |
Initialization
Call Initialize() once at application startup before any Beep controls are shown. It is idempotent — subsequent calls return immediately.
// Program.cs / Application startup
await BeepFontManager.Initialize();
// After init you can read element fonts immediately
Font btn = BeepFontManager.ButtonFont; // "Segoe UI", 9pt
Font hdr = BeepFontManager.HeaderFont; // "Segoe UI", 11pt Bold
Font code = BeepFontManager.MonospaceFont; // "Consolas", 9pt
Initialize() performs the following steps:
- Builds a
FontScanOptionsfrom built-in namespaces plus anything registered inBeepFontRegistry. - Runs
FontListHelper.LoadAllFonts(opt)on a background thread. - Creates all element font instances via
CreateDefaultFonts(). - Subscribes to
BeepFontRegistry.Changedfor incremental late-registration.
Registration API
Plugins and theme packs register their font-bearing assemblies through BeepFontManager (preferred) or directly on BeepFontRegistry.
// Register a single assembly (triggers immediate scan if already initialized)
BeepFontManager.Register(Assembly.GetExecutingAssembly());
// Register multiple assemblies at once
BeepFontManager.Register(new[] { asmA, asmB });
// Register a namespace prefix (embedded resources whose path contains this string)
BeepFontManager.Register("MyCompany.MyTheme.Fonts");
// Register a file-system directory for .ttf / .otf scanning
BeepFontManager.RegisterDirectory(@"C:\MyFonts");
BeepFontPaths
A static partial class spread across several files, each covering one font category. Every constant is the fully-qualified embedded resource name for use with Assembly.GetManifestResourceStream().
Base namespace: TheTechIdea.Beep.Winform.Controls.Fonts
Font Catalog by Category
The built-in catalog ships 300+ font variants across 30 families, organised into five categories:
Web Fonts
| Family | Variants | Notes |
|---|---|---|
Cairo | 8 (ExtraLight → Black) | Arabic / multilingual support |
Roboto | 18 (Thin → Black + italics) | Google's default UI font |
RobotoCondensed | 18 (Thin → Black + italics) | Condensed variant for tight layouts |
OpenSans | 18 + condensed variants | Humanist sans-serif |
Montserrat | 18 (Thin → Black + italics) | Geometric, poster-style |
Inter | 18pt static variants | Optimised for screen readability |
Nunito | Multiple weights | Rounded, friendly style |
Tajawal | Multiple weights | Arabic + Latin |
Oxygen | Light / Regular / Bold | KDE / Linux standard |
Monospace Fonts
| Family | Variants | Notes |
|---|---|---|
FiraCode | 5 (Light → Bold) | Ligature-enabled code font |
JetBrainsMono | 16 (Thin → ExtraBold + italics) | JetBrains IDE default |
CascadiaCode | 12 (ExtraLight → Bold + italics) | Windows Terminal default |
SpaceMono | 4 (Regular / Italic / Bold / BoldItalic) | Geometric monospace |
DejaVuSansMono | 4 (Regular / Bold / Oblique / BoldOblique) | Unicode-complete, ≈ Bitstream Vera |
Display Fonts
| Family | Notes |
|---|---|
BebasNeue | All-caps condensed, bold headers |
Orbitron | Futuristic / sci-fi UI |
Exo2 | Contemporary geometric |
Rajdhani | Devanagari + Latin, compact display |
Whitney | Humanist contemporary |
NotoColorEmoji | Full Unicode emoji coverage |
Accessibility Fonts
| Family | Notes |
|---|---|
AtkinsonHyperlegible | Designed for low-vision readers |
OpenDyslexic | Weighted bottoms to anchor letter orientation |
Lexend | Reduces visual stress, improves reading speed |
System / Miscellaneous Fonts
| Family / Constant | Notes |
|---|---|
SourceSansPro | Adobe's open-source UI font |
ComicNeue | Modernised Comic Sans alternative |
CaprasimoRegular | Decorative single-weight display font |
Consolas | Microsoft monospace (embedded copy) |
Lookup Methods
| Method | Returns | Description |
|---|---|---|
GetAllPaths() | Dictionary<string,string> | All public static string fields that end in .ttf/.otf |
GetAllFamilies() | Dictionary<string, Dictionary<string,string>> | Font paths grouped by family name; includes runtime-registered families |
GetFontFamilyNames() | List<string> | Name list of all available families |
GetFontPath(family, style) | string? | Resource path for a specific family + style combination |
ResourceExists(path) | bool | Checks if the resource is present in ResourceAssembly |
GetAvailableFontResources() | string[] | All .ttf/.otf resources from primary + all registered assemblies |
Families Inner Class
For convenient strongly-typed access each font family has a nested static class under BeepFontPaths.Families:
// Direct constant path
string path = BeepFontPaths.RobotoRegular;
// Via Families nested class (same value, grouped namespace)
string path = BeepFontPaths.Families.Roboto.Regular;
// All weights as a dictionary
var robotoWeights = BeepFontPaths.Families.Roboto.GetAllStyles();
// => { "Regular", "Bold", "Thin", "Medium", ... }
// Cairo (8 weights)
var cairoWeights = BeepFontPaths.Families.Cairo.GetAllWeights();
// Get lookup across all families
var all = BeepFontPaths.GetAllFamilies();
// => { "Roboto": {...}, "Cairo": {...}, "FiraCode": {...}, ... }
Runtime Registration
When an external assembly registers its fonts (e.g. a theme pack), call RegisterFamilyFromAssembly to make those fonts discoverable through normal path lookups:
// Scan an assembly's embedded resources under a specific namespace root
BeepFontPaths.RegisterFamilyFromAssembly(
Assembly.GetExecutingAssembly(),
"MyThemePack.Fonts");
// Omit namespaceRoot to accept all embedded .ttf/.otf resources
BeepFontPaths.RegisterFamilyFromAssembly(Assembly.GetExecutingAssembly());
After this call, GetAllFamilies() and GetFontPath() will include the new fonts from that assembly.
BeepFontRegistry
Thread-safe central registry that tracks which assemblies, namespace prefixes, and file-system directories should be scanned for font resources. Uses a ReaderWriterLockSlim for concurrent access.
Registration Methods
| Method | Description |
|---|---|
Register(Assembly) | Registers a single assembly; auto-infers namespace roots from its embedded font resources and raises Changed |
Register(IEnumerable<Assembly>) | Batch registers multiple assemblies in one write-lock |
Register(string namespacePrefix) | Registers a namespace prefix so resources containing it are treated as fonts |
RegisterFromDirectory(string) | Registers a file-system path for .ttf/.otf scanning |
RegisterFromAppDomain() | Auto-discovers all non-System/Microsoft assemblies in the current AppDomain that pass HasConventionFontResource |
Convention Discovery
An assembly is considered to contain fonts if it has at least one embedded .ttf or .otf resource whose namespace prefix ends with .Fonts. Example resource name: MyApp.Fonts.Roboto-Regular.ttf
// Check if an assembly satisfies the convention
bool hasFonts = BeepFontRegistry.HasConventionFontResource(myAssembly);
// Scan entire AppDomain automatically (useful for plugin hosts)
BeepFontRegistry.RegisterFromAppDomain();
Read-only Views
| Method | Returns |
|---|---|
GetRegisteredAssemblies() | IReadOnlyCollection<Assembly> — thread-safe snapshot |
GetRegisteredNamespaces() | IReadOnlyCollection<string> |
GetRegisteredDirectories() | IReadOnlyCollection<string> |
Changed Event
BeepFontRegistry.Changed fires whenever a new assembly, namespace, or directory is added. BeepFontManager subscribes after Initialize() to trigger incremental scans for late-registered plugins:
// After BeepFontManager.Initialize(), this is done automatically.
// You can also subscribe manually:
BeepFontRegistry.Changed += (s, e) =>
{
// Re-scan or update UI font lists
};
PrimaryAssembly Override
Set BeepFontRegistry.PrimaryAssembly to redirect BeepFontPaths.ResourceAssembly to a theme-pack assembly that ships its own font catalog and embedded fonts:
// In a theme-pack initializer
BeepFontRegistry.PrimaryAssembly = Assembly.GetExecutingAssembly();
// All BeepFontPaths resource lookups now query this assembly first
FontListHelper
The low-level engine responsible for scanning, loading, and caching fonts from all three sources: system, file-system, and embedded assembly resources.
FontScanOptions
Controls what is scanned during LoadAllFonts():
| Property | Type | Default | Description |
|---|---|---|---|
ScanSystemFonts | bool | true | Include all installed system (GDI) fonts |
Directories | List<string> | [ AppBase/Fonts ] | File-system directories to scan for .ttf/.otf |
EmbeddedNamespaces | string[] | built-in prefixes | Namespace prefixes that identify embedded font resources |
AdditionalAssemblies | List<Assembly> | [ ] | Extra assemblies to scan beyond executing assembly |
IncludeFrameworkAssemblies | bool | false | Scan System.* / Microsoft.* assemblies |
IncludeReferencedAssemblies | bool | true | Follow assembly references during scanning |
MaxReferenceDepth | int | 2 | Maximum depth for assembly reference traversal |
UseConventionDiscovery | bool | true | Auto-register assemblies that satisfy the .Fonts convention |
var options = new FontListHelper.FontScanOptions
{
ScanSystemFonts = true,
Directories = new List<string> { @"C:\MyApp\Fonts" },
EmbeddedNamespaces = new[] { "MyCompany.Fonts" },
AdditionalAssemblies = new List<Assembly> { myPluginAssembly },
IncludeReferencedAssemblies = true,
MaxReferenceDepth = 1,
UseConventionDiscovery = true
};
FontListHelper.LoadAllFonts(options);
Font Caching
An application-lifetime Dictionary<string, Font> cache is keyed by "name|size|style". Paintings call these methods on every render; the cache prevents allocation spam.
| Method | Description |
|---|---|
GetCachedFont(name, size, style) | Returns a cached Font, applying the current DpiScaleFactor if EnableDpiFontScaling is true |
GetCachedFont(name, size, dpiScale, style) | Uses an explicit DPI scale factor instead of the global one |
GetCachedFontForControl(name, size, control, style) | Reads DPI from the given Control handle and scales accordingly |
ClearFontCache() | Evicts all entries; call on theme change or memory pressure |
// In a painter's OnPaint — zero allocation after first call
Font f = FontListHelper.GetCachedFont("Roboto", 11f, FontStyle.Regular);
g.DrawString(text, f, brush, rect);
DPI Scaling
DPI scaling is off by default for backward compatibility. Enable it globally or per-control:
| Member | Description |
|---|---|
EnableDpiFontScaling | Global on/off switch (default: false) |
DpiScaleFactor | Current multiplier (default: 1.0 = 96 DPI); setting it clears the font cache |
UpdateDpiFromControl(control) | Reads the control's current DPI and updates DpiScaleFactor |
UpdateDpiFromSystem() | Reads system-wide DPI via DpiScalingHelper |
// Enable DPI-aware fonts globally (do once at startup)
FontListHelper.EnableDpiFontScaling = true;
FontListHelper.UpdateDpiFromSystem();
// Per-control scaling in a custom form
protected override void OnDpiChanged(DpiChangedEventArgs e)
{
base.OnDpiChanged(e);
FontListHelper.UpdateDpiFromControl(this);
}
Discovery Methods
| Method | Returns | Description |
|---|---|---|
GetSystemFonts() | List<FontConfiguration> | All installed InstalledFontCollection fonts; adds new entries to FontConfigurations |
GetFontFilesLocations(directory) | void | Scans a directory for .ttf/.otf and loads them into PrivateFontCollection |
GetFontNames() | List<string> | All font names (system + private) |
GetSystemFontNames() | List<string> | System-only font names |
GetPrivateFontNames() | List<string> | Private / embedded-only font names |
GetFont(name, size, style) | Font? | Creates a font from the private collection; falls back to GDI |
RegisterAndLoad(assembly) | void | Scans an assembly's embedded resources and loads discovered fonts immediately |
Usage Patterns
1. Basic Application Setup
// Program.cs
static async Task Main(string[] args)
{
// Initialize font system before creating any controls
await BeepFontManager.Initialize();
// Optionally customise the app font
BeepFontManager.AppFontName = "Roboto";
BeepFontManager.AppFontSize = 10f;
Application.Run(new MainForm());
}
2. Using Element Fonts in a Control
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Use pre-cached element fonts — no allocation
e.Graphics.DrawString("Header", BeepFontManager.HeaderFont, Brushes.Black, headerRect);
e.Graphics.DrawString("Label", BeepFontManager.LabelFont, Brushes.Gray, labelRect);
e.Graphics.DrawString("0x1A3F", BeepFontManager.MonospaceFont, Brushes.Navy, codeRect);
}
3. Loading an Embedded Font by Path
// Get the resource path constant
string path = BeepFontPaths.Families.Roboto.Bold;
// => "TheTechIdea.Beep.Winform.Controls.Fonts.Roboto.Roboto-Bold.ttf"
// Create a font from the embedded resource using BeepFontPathsExtensions
Font robotoBold = BeepFontPathsExtensions.CreateFontFromResource(path, 14f, FontStyle.Bold);
4. Plugin / Theme Pack Registration
// In a theme pack assembly's static initializer
public static class MyThemeInitializer
{
public static void Register()
{
// Preferred: register via BeepFontManager (handles both pre- and post-init)
BeepFontManager.Register(Assembly.GetExecutingAssembly());
// Optionally redirect the primary assembly so path lookups favour this pack
BeepFontRegistry.PrimaryAssembly = Assembly.GetExecutingAssembly();
}
}
// Consuming application startup:
MyThemeInitializer.Register();
await BeepFontManager.Initialize();
5. DPI-Aware Painter
// In a custom BeepControl override
protected override void OnDpiChanged(DpiChangedEventArgs e)
{
base.OnDpiChanged(e);
FontListHelper.UpdateDpiFromControl(this);
Invalidate(); // repaint with new font sizes
}
protected override void OnPaint(PaintEventArgs e)
{
// GetCachedFontForControl reads the control's current DPI
Font scaled = FontListHelper.GetCachedFontForControl("Roboto", 10f, this);
e.Graphics.DrawString(Text, scaled, Brushes.Black, ClientRectangle);
}
6. Querying Available Families
// List all family names (built-in + runtime-registered)
List<string> families = BeepFontPaths.GetFontFamilyNames();
// Get a specific path by family + style name
string path = BeepFontPaths.GetFontPath("JetBrainsMono", "SemiBold");
// Enumerate all system font names
List<string> system = BeepFontManager.SystemFontNames;
// Enumerate all loaded (private) font names
List<string> custom = BeepFontManager.CustomFontNames;
File Organisation
| File | Class / Role |
|---|---|
BeepFontManager.cs | BeepFontManager — high-level static API, element fonts, initialization, registration |
BeepFontRegistry.cs | BeepFontRegistry — thread-safe registry for assemblies, namespaces, directories and the Changed event |
BeepFontPaths.Core.cs | BeepFontPaths (partial) — base namespace constant, individual font constants, GetAllPaths(), ResourceExists(), GetAvailableFontResources() |
BeepFontPaths.Families.cs | BeepFontPaths.Families — nested classes (Cairo, Roboto, RobotoCondensed, …) with GetAllStyles() / GetAllWeights() |
BeepFontPaths.WebFonts.cs | BeepFontPaths (partial) — Cairo, Roboto, RobotoCondensed, OpenSans, Montserrat, Inter, Nunito, Tajawal, Oxygen resource paths |
BeepFontPaths.MonospaceFonts.cs | BeepFontPaths (partial) — FiraCode, JetBrainsMono, CascadiaCode, SpaceMono, DejaVuSansMono resource paths |
BeepFontPaths.DisplayFonts.cs | BeepFontPaths (partial) — BebasNeue, Orbitron, Exo2, Rajdhani, Whitney, NotoColorEmoji resource paths |
BeepFontPaths.AccessibilityFonts.cs | BeepFontPaths (partial) — AtkinsonHyperlegible, OpenDyslexic, Lexend resource paths |
BeepFontPaths.SystemFonts.cs | BeepFontPaths (partial) — SourceSansPro, ComicNeue resource paths |
BeepFontPaths.Extensions.cs | BeepFontPaths (partial) — GetAllFamilies(), GetFontPath(), RegisterFamilyFromAssembly(), runtime family dictionary |
BeepFontPaths.Refactored.cs | BeepFontPaths (partial) — additional helpers and refactored look-up utilities |
FontListHelper.cs | FontListHelper — core scanning/loading/caching engine, DPI scaling, FontScanOptions, discovery methods |
README.md | Architecture overview and quick-start guide |
Best Practices
- Call
Initialize()once at startup — preferably before any controls are created. It is asynchronous and safe toawaitinMain(). - Prefer element font properties — use
BeepFontManager.HeaderFontrather than creating anew Font()inline; element fonts are cached and consistent across the whole application. - Do not dispose fonts obtained from BeepFontManager or FontListHelper — they are owned by the caching layer and shared across all controls.
- Use
GetCachedFont()in painters — avoids per-paint GDI object creation and eliminates GDI handle leaks. - Call
ClearFontCache()only on theme change — not on every resize or repaint; it is an expensive operation that forces re-creation of all cached fonts. - Register plugins before
Initialize()when possible — if a plugin must register after init, useBeepFontManager.Register()which handles the incremental scan automatically. - Enable
EnableDpiFontScalingfor high-DPI apps — callUpdateDpiFromSystem()at startup andUpdateDpiFromControl()inOnDpiChangedoverrides. - Use the
.Fontsnamespace convention for embedded font resources in plugins — this enables auto-detection byRegisterFromAppDomain()with zero explicit registration code.