Fonts: loading, sizing, wrapping and fallbacks
A complete C# example with the built-in font, FontFile, SystemFont and FontVariation; quality and export settings.
Run without an external file
Add UiSampleStyle.cs and FontsDemo.cs to a C# project. Attach FontsDemo to a separate level's Control root, build and run with F6.
Two labels contain Latin, Cyrillic and a combining accent. Their sizes differ; the larger uses a synthetically emboldened FontVariation. The lower long label wraps as the window narrows. The multilingual sample string is intentionally identical in both versions.
FontsDemo.cs
using Reckfell;
public partial class FontsDemo : Control
{
public Label Small { get; private set; } = null!;
public Label Large { get; private set; } = null!;
public FontVariation Bold { get; private set; } = null!;
public Label Wrapped { get; private set; } = null!;
public override void _Ready()
{
UiSampleStyle.Prepare(this);
var margin = new MarginContainer(); AddChild(margin); margin.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect, margin: 24);
var column = new VBoxContainer(); margin.AddChild(column);
Small = new Label { Text = "Reckfell — Привет! Cafe\u0301", AutowrapMode = TextServer.AutowrapMode.WordSmart };
Large = new Label { Text = Small.Text, AutowrapMode = TextServer.AutowrapMode.WordSmart };
column.AddChild(Small); column.AddChild(Large);
Small.AddThemeFontSizeOverride("font_size", 16); Large.AddThemeFontSizeOverride("font_size", 28);
Wrapped = new Label
{
Text = UiSampleStyle.Text("Длинная надпись переносится в пределах доступной ширины. Размер шрифта задаётся отдельно от файла.", "A longer label wraps within the available width. Font size is configured separately from the font file."),
AutowrapMode = TextServer.AutowrapMode.WordSmart
};
column.AddChild(Wrapped); SetFont(Small.GetThemeFont("font"));
}
private void SetFont(Font font)
{
Theme.DefaultFont = font;
Bold = new FontVariation { BaseFont = font, VariationEmbolden = 1.2f };
Large.AddThemeFontOverride("font", Bold);
}
public Error LoadProjectFont(string path)
{
if (!path.StartsWith("asset://") || !FileAccess.FileExists(path)) return Error.FileNotFound;
var font = ResourceLoader.Load<FontFile>(path);
if (font is null) return Error.FileUnrecognized;
SetFont(font); return Error.Ok;
}
public Error LoadExternalFont(string path)
{
const ulong maxBytes = 16UL * 1024 * 1024;
if (!FileAccess.FileExists(path)) return Error.FileNotFound;
byte[] data;
using (var file = FileAccess.Open(path, FileAccess.ModeFlags.Read))
{
if (file is null) return FileAccess.GetOpenError();
ulong length = file.GetLength();
if (length == 0 || length > maxBytes) return Error.InvalidData;
data = file.GetBuffer((long)length);
if ((ulong)data.LongLength != length) return Error.InvalidData;
}
var font = new FontFile(); font.SetData(data);
SetFont(font); return Error.Ok;
}
public void UseSystemFont(string[] names) => SetFont(new SystemFont { FontNames = names });
}Load your font
| Source | Action |
|---|---|
| Project font | Add a TTF/OTF, wait for import, then call LoadProjectFont with a asset:// path |
| External dynamic file at runtime | LoadExternalFont limits reading to 16 MiB, checks byte count and assigns bytes through FontFile.SetData |
| OS-installed family | UseSystemFont with family names; availability/results differ between devices |
| Bitmap font | Import a BMFont .fnt or a font image; configure its grid/character set for the source |
Set size on the Control through Theme Overrides → Font Sizes or Theme.SetFontSize, not by copying the TTF for each size. Fonts/Default Font select a resource. This build's built-in fallback is Open Sans SemiBold; a project theme/font can replace it. Include required font and fallback resources for reproducible exports: SystemFont does not package OS-installed fonts into a game.
The external loader above rejects missing, empty, oversized or short-read files before replacing the font. 16 MiB is this example’s limit rather than an engine format limit. Error.Ok means bytes were accepted: this is not a validator for arbitrary fonts. Check the format, required characters and rendered result; untrusted user uploads require separate input validation.
LoadDynamicFont is a different path: it reads the file and resets options itself, but in 1.0.0 returns Error.Ok without validating decoding. That result alone is insufficient. This example bounds reading beforehand; SetData on a new resource does not perform that reset.
Glyph coverage and shaping
A missing glyph needs an appropriate fallback. Assign Font.Fallbacks in the intended order and test real language strings, punctuation, accents and emoji. HasChar helps with one character but does not prove whole-script, ligature or emoji-sequence support. System fallback depends on the device. For icon fonts, retain a stable code-point/icon mapping and check file versions.
.NET string length counts UTF-16 units, not visible graphemes. RTL, combining marks, ligatures and IME require TextServer/shaping rather than manual letter positioning. Enable wrapping and allocate width before checking alignment/TextDirection. Scaling the entire Control is not a substitute for font size.
Quality and font features
| Setting | Use / limitation |
|---|---|
| Antialiasing | Smoothing choice; test disabled smoothing/integer positions for pixel fonts |
| Hinting | Fits a dynamic font to a pixel grid, particularly at small sizes |
| SubpixelPositioning | Positioning precision; affects caching and moving small text |
| Mipmaps | Help downscaling and consume memory; cannot repair a low-resolution bitmap source |
| MSDF, MsdfSize, MsdfPixelRange | Scaling/outlines within configured ranges; check small sizes and artifacts |
| Control outline/shadow | Theme entries set color, outline size and shadow offsets; allow space before clipping edges |
| Variable font | FontVariation.VariationOpentype sets supported axes. Use the numeric key TextServerManager.GetPrimaryInterface().NameToTag("wght") and ranges from GetSupportedVariationList. The raw string "wght" can fail to change the selected cache in 1.0.0 |
| Faux bold/italic | VariationEmbolden/VariationTransform synthesize a style rather than supplying a genuine bold/italic face |
| Spacing / baseline | FontVariation adjusts spacing/offset; recheck line and control heights |
| OpenType features | Set supported tags through OpentypeFeatures; ligature/number behavior depends on the font. Separate from FontVariation.VariationOpentype axes |
| Color emoji | Require coverage and a supported color format; check target platforms |
| Prerendering | Import settings can prebuild required glyphs/sizes, trading first-display work for size/memory |
Dynamic import formats include TTF/OTF/WOFF/WOFF2; FontFile and advanced import settings expose options. Bitmap quality is bounded by source images and scale. Do not prebuild every size/axis combination without checking memory.
A new FontFile has MsdfPixelRange = 16, MsdfSize = 48 and SubpixelPositioning = Auto. After LoadDynamicFont, these reset to 14, 128 and Disabled respectively. Configure quality options after loading. MSDF does not support hinting; Oversampling is bypassed for MSDF and positive FixedSize. Exact FontFile settings.
Project settings and diagnosis
gui/theme/custom_font selects the shared resource; gui/theme/default_font_* controls built-in font quality and gui/theme/default_theme_scale scales the theme. Respect restart/reimport requirements. The example retains the previous font on checked read failures; successfully read file content needs separate validation.
For missing-glyph boxes, check coverage/fallbacks. For clipped text, check width, wrapping, minima, clipping and outlines. For changed export appearance, check bundled resources and SystemFont use. For first-display stalls, inspect cached glyphs/sizes/import rather than only game FPS. The article's checks use a private Windows TTF to exercise loading; the font file is not distributed with the example.