Keel’s text system renders glyphs in screen space using freetype-py. Each font is baked into a single R8 texture atlas at load time (using a shelf-packing algorithm), so the per-frame cost is only one draw call per uniqueDocumentation 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.
(font, colour) pair. Text does not move with the 2D or 3D camera — it lives in UI space and is always drawn on top of the game world.
Prerequisites
Text rendering requires the 2D renderer to be active first, because the text pass draws into the framebuffer thatsetup_renderer_2d clears each frame.
setup_text
Phase.POST_RENDER system and returns a TextSetup dataclass:
| Field | Type | Description |
|---|---|---|
font_registry | FontRegistry | Cache of loaded fonts keyed by (path, size_px). |
text_batch | TextBatch | The screen-space renderer that issues draw calls. |
Loading Fonts
load_font
path, sets the pixel size, bakes a glyph atlas, and returns a Font object. Subsequent calls with the same (path, size_px) pair return the cached Font without re-uploading.
| Parameter | Description |
|---|---|
app | The App instance (used to access the GL context and FontRegistry). |
path | Absolute or relative path to a TTF/OTF file, or keel.BUILTIN_FONT. |
size_px | Pixel height of the rendered glyphs. Larger values use more atlas space. |
name | Optional string alias for later lookup via font_registry.get(name). |
The built-in font
keel.BUILTIN_FONT is a string path pointing to the bundled DejaVu Sans Mono font. It is available immediately after import without any asset files:
Font IDs
FontRegistry assigns a stable integer ID to each loaded font in load order. The TextLabel.font_id field stores this integer:
FontRegistry.id_of(font) method returns the integer for a Font object, and get_by_id(n) reverses the lookup.
The TextLabel Component
TextLabel must be spawned with a Transform2D that provides the screen-space position. The Transform2D.x and Transform2D.y values are interpreted as pixel coordinates in UI space — not in world space.
Setting Text Content
BecauseTextLabel is a numpy-backed component, it cannot store a variable-length string directly. Text content lives in a module-level side dict keyed by entity ID. Use the API functions to manage it:
set_text
get_text
clear_text
When an entity with a
TextLabel is despawned, call clear_text(entity_id) to remove the corresponding entry from the side dict. If you skip this step, the dict will retain the string indefinitely (a memory leak for long-running sessions).set_label_visible
Show or hide aTextLabel without despawning the entity:
world.set(entity_id, keel.TextLabel, visible=False).
Screen-Space Layout
Text is rendered in UI space with these conventions:y = 0is the top of the screen.- Y grows downward —
y = 100is 100 pixels from the top. - The
Transform2D.yposition is the text baseline — the invisible horizontal line that glyphs sit on. Descenders (like the bottom of “g” and “p”) hang below it.
Newlines and Tabs
The layout engine handles\n and \t characters:
\n— moves the pen toorigin_xand advancesyby oneline_height.\t— advances the pen byspace_advance * 4(four spaces wide).
Glyph Atlas Details
EachFont bakes a single R8 (single-channel) ModernGL texture using a shelf packer. The default character set covers ASCII printable characters (32–126) plus the full Latin-1 Supplement (160–255). The atlas starts at 512×512 pixels and automatically doubles to 1024×1024 if the character set does not fit. If it still does not fit at 1024×1024, AtlasTooSmallError is raised — use a smaller size_px.
The atlas stores coverage only; colour is applied per-draw-call via the u_color shader uniform. One atlas therefore works for any tint of the same font.
Full Example
Call setup_renderer_2d, then setup_text
Text needs the 2D framebuffer clear that
setup_renderer_2d provides. Always call setup_renderer_2d first.Load a font with load_font
Use
keel.BUILTIN_FONT to avoid needing any font file on disk. The return value is a Font object; its index in the FontRegistry is the integer you put in TextLabel.font_id.Spawn Transform2D + TextLabel
Place
Transform2D.y at approximately font.size_px pixels from the top edge so glyphs are not clipped.Call set_text to set content
Without a
set_text call the label renders nothing. Update the string any time — it takes effect on the next frame.