Font System
NeoFlux uses FreeType for glyph rasterization and a single grayscale texture atlas for efficient text rendering. Widgets reference fonts by name; the FontManager resolves that name to a file path.
Supported formats
The scanner (FontManager::ScanDirectory) recognizes three TrueType / OpenType file extensions:
.ttf— TrueType.otf— OpenType (CFF).ttc— TrueType Collection
Each discovered file is registered under its filename stem (the file name without extension). For example NotoSansSC-Regular.ttf becomes the font name NotoSansSC-regular (lookups are case-insensitive). The first font scanned becomes the default font used when a widget does not call SetFont().
The assets/fonts/ convention
The recommended project layout keeps user-supplied fonts out of source control next to the executable:
my_app/
├── assets/
│ └── fonts/
│ ├── NotoSansSC-Regular.ttf
│ └── Roboto-Bold.ttf
└── ...Add a CMake POST_BUILD step that copies the whole assets/fonts/ tree next to the produced binary, so the working-directory lookup finds it:
add_custom_command(TARGET my_app POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets/fonts
$<TARGET_FILE_DIR:my_app>/assets/fonts)After the build, the layout next to the executable is:
build/bin/
├── my_app.exe
└── assets/
└── fonts/
├── NotoSansSC-Regular.ttf
└── Roboto-Bold.ttfRun the executable from its bin directory so the default search path ./assets/fonts/ resolves:
.\build\bin\my_app.exeCurrent built-in scan path
In this release the renderer (TgfxRenderer::InitFonts) scans thirdparty/fonts/ plus ../thirdparty/fonts/ and ../../thirdparty/fonts/ relative fallbacks, rather than ./assets/fonts/. Either place your fonts in thirdparty/fonts/ for the bundled demos, or use the assets/fonts/ + POST_BUILD convention above for your own targets and point FontManager::ScanDirectory("assets/fonts") at it. Supported formats and by-stem naming are identical either way.
Using fonts
Default font
If a Text / Button widget does not specify a font, the first discovered font is used.
auto text = std::make_shared<Text>("Hello"); // uses default fontExplicit font
Select a font by its filename stem (case-insensitive):
auto text = std::make_shared<Text>("Hello");
text->SetFont("NotoSansSC-Regular"); // loads NotoSansSC-Regular.ttfSize, color, alignment
text->SetFontSize(24.0F);
text->SetTextColor({.r = 255, .g = 0, .b = 0, .a = 255}); // red
text->SetAlignment(HAlign::kCenter); // within boundsHow rendering works
- FreeType init — the renderer initializes one
FT_Libraryat startup. - Scan —
FontManager::ScanDirectory(dir)walks the directory and maps lowercase stems to absolute file paths. - Lazy face load — the first time a glyph is needed, the matching
FT_Faceis loaded and cached. - Rasterize + atlas — glyph bitmaps are uploaded into one grayscale texture atlas; each glyph is drawn as a textured quad.
- Color — the fragment shader samples the atlas alpha channel and multiplies by the widget's text color.
No fonts = no text
If the scan directory does not exist or contains no font files, the renderer logs No fonts found ... Text rendering will be disabled and draws no text. Always ship at least one font.
CJK and Unicode
Text accepts a UTF-8 std::string. For CJK content use a font that covers the required glyphs (e.g. Noto Sans SC):
auto text = std::make_shared<Text>(u8"你好世界");
text->SetFont("NotoSansSC-Regular");Best practices
- Ship only the weights/subsets you need to keep memory and download size down.
- Reference fonts by stable stem name; renaming a file renames the font.
- Keep
assets/fonts/out of git (large binaries) and copy it at build time.
Comments