Keel’s text system renders screen-space labels using freetype-py to bake glyph atlases into R8 GPU textures. Text is always drawn in UI space — it is not camera-transformed and does not scroll with the world. The system runs atDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/VKSFY/keel/llms.txt
Use this file to discover all available pages before exploring further.
Phase.POST_RENDER, after the 2D and 3D passes, so text always renders on top.
The text system requires setup_renderer_2d to have been called first (the text shader draws into the same framebuffer the 2D renderer cleared).
Setup
setup_text
FontRegistry and TextBatch, compiles the text shader, inserts them as world resources, and registers a Phase.POST_RENDER system that draws every visible TextLabel entity. Idempotent.
Requires: setup_renderer_2d(app) must have been called first. Raises RuntimeError otherwise.
Requires: freetype-py (pip install freetype-py).
TextSetup fields
The
FontRegistry inserted as a world resource. Call font_registry.load(ctx, path, size_px) to load fonts programmatically. Use font_registry.id_of(font) to get the integer ID for TextLabel.font_id.The
TextBatch renderer. Stores last_glyph_count and last_draw_calls for profiling. Not normally used directly.Loading fonts
load_font
FontRegistry. The font is baked into a 512×512 (or 1024×1024 on overflow) R8 glyph atlas covering ASCII printable + Latin-1 Supplement. Subsequent calls with the same (path, size_px) pair return the cached Font without re-baking.
Requires setup_text(app) to have been called first. Raises RuntimeError otherwise.
The Keel
App instance. The function looks up FontRegistry from app.world.File path to a
.ttf or .otf font file. Pass keel.BUILTIN_FONT to use the bundled DejaVu Sans Mono.Pixel size at which to bake the glyph atlas. Larger values produce sharper text at large
TextLabel.scale values but consume more atlas space. Raises AtlasTooSmallError if the glyphs don’t fit in a 1024×1024 atlas at the requested size.Optional alias for the font, accessible via
font_registry.get(name). Useful for referencing fonts by a logical name rather than a file path.Font object. Pass it to font_registry.id_of(font) to get the TextLabel.font_id integer.
keel.BUILTIN_FONT
Text content API
BecauseTextLabel is a NumPy structured array component and NumPy does not support variable-length strings, the text string lives in a module-level side dictionary keyed by entity ID. These four functions are the public interface to that dictionary.
set_text
entity_id. The TextBatch reads this value each frame for any entity whose TextLabel.visible is True and whose font_id is valid.
get_text
entity_id. Returns "" if set_text has never been called for this entity.
clear_text
entity_id from the side table. After this call, get_text(entity_id) returns "" and the TextBatch skips the entity. Use this when you despawn a label entity to reclaim the dictionary slot.
set_label_visible
TextLabel.visible on entity_id to True or False. When False, the TextBatch skips the entity entirely — no glyph layout, no GPU upload, no draw call for that entity. More efficient than setting TextLabel.a = 0.0 because the CPU work is also skipped.
The Font class
Font is a loaded font face at a fixed pixel size with its glyph atlas already uploaded to the GPU. You normally receive it from load_font and don’t construct it directly.
Properties
The absolute path the font was loaded from.
The pixel size passed at load time.
Pixels to advance Y by for a newline, as reported by FreeType in pixels (converted from 26.6 fixed-point).
Pixels above the baseline for the tallest glyph.
Pixels below the baseline (zero or negative).
Advance width of the space character in pixels. Used for tab expansion and as a fallback for missing glyphs.
The
GlyphAtlas holding the R8 GPU texture and per-glyph UV metrics. Access font.atlas.texture for the moderngl.Texture.font.measure(text) -> tuple[float, float]
Return (width, height) in pixels for text at this font’s size and TextLabel.scale = 1.0. Multi-line strings return the max line width and line_height × line_count. An empty string measures (0, line_height).
AtlasTooSmallError
Raised by Font.__init__ (via GlyphAtlas.build) when the requested character set does not fit in a 1024×1024 atlas. The remedy is to use a smaller size_px:
