wolfhece.opengl.text_renderer2d
Shared-resource text renderer using SDF glyph atlas.
Renders text strings as per-glyph quads textured from a
GlyphAtlas.
Features:
- Pixel-size or world-size text (same dual-mode as polyline width)
- Multiline text (split on \n)
- Rotation and alignment (left / centre / right)
- Glow / outline via SDF thresholding
- Animations: pulse, wave, typewriter
Usage:
tr = TextRenderer2D.get_instance()
tr.draw_text("Hello\nWorld", x, y, mvp, viewport,
font_size=16, glow_enabled=True)
Author: HECE - University of Liege, Pierre Archambeau Date: 2026
Copyright (c) 2026 University of Liege. All rights reserved.
Module Contents
- wolfhece.opengl.text_renderer2d.build_text_vertices(text: str, atlas: wolfhece.opengl.glyph_atlas.GlyphAtlas, x: float, y: float, scale: float, rotation: float = 0.0, alignment: str = 'left', line_spacing: float = 1.2) tuple[numpy.ndarray, int][source]
Build per-glyph quad vertex data for a text string.
- Parameters:
text – Text with optional
\nfor multiline.atlas –
GlyphAtlasto use for metrics.x – Anchor X in world coordinates.
y – Anchor Y in world coordinates.
scale – World-space size of one em.
rotation – Counter-clockwise rotation in radians.
alignment –
'left','center', or'right'.line_spacing – Line spacing as a multiple of line_height.
- Returns:
(vbo_data, vertex_count)— layout is[x, y, u, v, char_idx]per vertex, 6 vertices per visible glyph (two triangles).
- wolfhece.opengl.text_renderer2d.measure_text(text: str, atlas: wolfhece.opengl.glyph_atlas.GlyphAtlas, scale: float, line_spacing: float = 1.2) tuple[float, float][source]
Measure the bounding box of text in world units.
- Returns:
(width, height)— width of the widest line, total height including line spacing.
- wolfhece.opengl.text_renderer2d._interpolate_polyline(points: numpy.ndarray, cum_dist: numpy.ndarray, s: float) tuple[float, float, float, float][source]
Interpolate position and tangent along a polyline at distance s.
- Parameters:
points –
(N, 2)vertex array.cum_dist –
(N,)cumulative distance array.s – Curvilinear abscissa to evaluate.
- Returns:
(x, y, tx, ty)— position and unit tangent.
- wolfhece.opengl.text_renderer2d.build_text_along_polyline(text: str, atlas: wolfhece.opengl.glyph_atlas.GlyphAtlas, points: numpy.ndarray, cum_dist: numpy.ndarray, scale: float, offset_along: float = 0.0, offset_perp: float = 0.0, alignment: str = 'left') tuple[numpy.ndarray, int][source]
Build per-glyph quad vertex data for text that follows a polyline.
Each character is placed at the appropriate curvilinear distance along the polyline and rotated to match the local tangent direction.
- Parameters:
text – Text string (single line).
atlas – SDF
GlyphAtlasfor metrics.points –
(N, 2)polyline vertices in world coords.cum_dist –
(N,)cumulative distances (fromget_sz()).scale – World-space size of one em.
offset_along – Shift the start of the text along the polyline (in world units). Positive = downstream.
offset_perp – Perpendicular offset from the polyline (in world units). Positive = left side.
alignment –
'left'|'center'|'right'.
- Returns:
(vbo_data, vertex_count)— same layout asbuild_text_vertices().
- wolfhece.opengl.text_renderer2d.snap_to_polyline(mouse_x: float, mouse_y: float, points: numpy.ndarray, cum_dist: numpy.ndarray) tuple[float, float, float, float, float, float][source]
Find the closest point on a polyline to a mouse position.
Uses vectorised numpy operations — no Shapely dependency.
- Parameters:
mouse_x – Mouse X in world coordinates.
mouse_y – Mouse Y in world coordinates.
points –
(N, 2)polyline vertices.cum_dist –
(N,)cumulative distances.
- Returns:
(snap_x, snap_y, curvi_dist, total_length, tangent_x, tangent_y)
- class wolfhece.opengl.text_renderer2d.TextRenderer2D[source]
Singleton text renderer: compiles shaders once, manages shared VAO/VBO.
Call
draw_text()for each piece of text to render.- _instance: TextRenderer2D | None = None[source]
- classmethod get_instance() TextRenderer2D[source]
Return the singleton, creating it lazily.
- static _compile(shader_type: int, path: pathlib.Path) int[source]
Compile a single GLSL shader from path.
- static _cache_get(cache: collections.OrderedDict, key)[source]
Get value from OrderedDict LRU cache and refresh recency.
- static _cache_set(cache: collections.OrderedDict, key, value, max_size: int)[source]
Insert value into OrderedDict LRU cache with bounded size.
- _measure_text_cached(text: str, atlas: wolfhece.opengl.glyph_atlas.GlyphAtlas, line_spacing: float) tuple[float, float][source]
Measure text with a small LRU cache (hot path in width-priority mode).
- _build_text_vertices_cached(text: str, atlas: wolfhece.opengl.glyph_atlas.GlyphAtlas, x: float, y: float, scale: float, rotation: float, alignment: str, line_spacing: float) tuple[numpy.ndarray, int][source]
Build per-glyph vertices using an LRU cache for static labels.
- draw_text(text: str, x: float, y: float, mvp: numpy.ndarray, viewport: tuple[int, int], *, font_name: str = 'arial.ttf', font_size: float = 14.0, color: tuple[float, Ellipsis] = (1.0, 1.0, 1.0, 1.0), size_in_pixels: bool = True, world_height: float | None = None, world_width: float | None = None, rotation: float = 0.0, alignment: str = 'left', line_spacing: float = 1.2, smoothing: float = 1.0, glow_enabled: bool = False, glow_width: float = 0.15, glow_color: tuple[float, Ellipsis] = (1.0, 1.0, 1.0, 0.5), anim_mode: int = 0, anim_phase: float = 0.0, anim_speed: float = 1.0)[source]
Render text at world position (x, y).
- Parameters:
text – Text string (supports
\nfor multiline).x – Anchor X in world coordinates.
y – Anchor Y in world coordinates.
mvp – 4×4 model-view-projection (column-major float32).
viewport –
(width_px, height_px).font_name – TrueType font file name.
font_size – Size in pixels (if size_in_pixels) or world units.
color –
(r, g, b, a)each in[0, 1].size_in_pixels – If
Truefont_size is screen pixels.world_height – If set, overrides font_size with this world-unit height.
world_width – If set (and world_height is
None), scales the text so its measured width matches this world-unit value.rotation – Counter-clockwise rotation in degrees.
alignment –
'left'|'center'|'right'.line_spacing – Line spacing multiplier.
smoothing – SDF edge-width multiplier (1.0 = standard AA).
glow_enabled – Enable glow / outline halo.
glow_width – SDF threshold offset for glow (e.g. 0.15).
glow_color –
(r, g, b, a)for the glow.anim_mode – 0 = none, 1 = pulse, 2 = wave, 3 = typewriter.
anim_phase – Phase in
[0, 1].anim_speed – Speed multiplier (reserved for future use).
- draw_text_along_polyline(text: str, points: numpy.ndarray, cum_dist: numpy.ndarray, mvp: numpy.ndarray, viewport: tuple[int, int], *, font_name: str = 'arial.ttf', font_size: float = 14.0, color: tuple[float, Ellipsis] = (1.0, 1.0, 1.0, 1.0), size_in_pixels: bool = True, world_height: float | None = None, world_width: float | None = None, offset_along: float = 0.0, offset_perp: float = 0.0, alignment: str = 'left', smoothing: float = 1.0, glow_enabled: bool = False, glow_width: float = 0.15, glow_color: tuple[float, Ellipsis] = (1.0, 1.0, 1.0, 0.5), anim_mode: int = 0, anim_phase: float = 0.0, anim_speed: float = 1.0)[source]
Render text along a polyline, each character following the tangent.
- Parameters:
text – Text string (single line).
points –
(N, 2)polyline vertices in world coords.cum_dist –
(N,)cumulative distances.mvp – 4×4 model-view-projection (column-major float32).
viewport –
(width_px, height_px).font_name – TrueType font file name.
font_size – Size in pixels (if size_in_pixels) or world units.
color –
(r, g, b, a)each in[0, 1].size_in_pixels – If
Truefont_size is screen pixels.world_height – If set, overrides font_size with this world-unit height.
world_width – If set (and world_height is
None), scales the text so its measured width matches this world-unit value.offset_along – Shift text start along polyline (world units).
offset_perp – Perpendicular offset (world units, +left).
alignment –
'left'|'center'|'right'.smoothing – SDF edge-width multiplier.
glow_enabled – Enable glow / outline halo.
glow_width – SDF threshold offset for glow.
glow_color –
(r, g, b, a)for the glow.anim_mode – 0 = none, 1 = pulse, 2 = wave, 3 = typewriter.
anim_phase – Phase in
[0, 1].anim_speed – Speed multiplier.
- draw_tracking_label(mouse_x: float, mouse_y: float, points: numpy.ndarray, cum_dist: numpy.ndarray, mvp: numpy.ndarray, viewport: tuple[int, int], *, format_func: Callable[[float, float], str] | None = None, font_name: str = 'arial.ttf', font_size: float = 14.0, color: tuple[float, Ellipsis] = (1.0, 1.0, 0.0, 1.0), size_in_pixels: bool = True, world_height: float | None = None, offset_perp: float = 0.0, smoothing: float = 1.0, glow_enabled: bool = True, glow_width: float = 0.2, glow_color: tuple[float, Ellipsis] = (0.0, 0.0, 0.0, 0.8), snap_radius: float | None = None)[source]
Render a label that tracks the mouse snapped to the polyline.
The label shows dynamic content (by default the curvilinear distance) and follows the mouse position projected onto the polyline.
- Parameters:
mouse_x – Mouse X in world coordinates.
mouse_y – Mouse Y in world coordinates.
points –
(N, 2)polyline vertices in world coords.cum_dist –
(N,)cumulative distances.mvp – 4×4 model-view-projection (column-major float32).
viewport –
(width_px, height_px).format_func –
(curvi_dist, total_length) -> str. Defaults to"d = {curvi:.2f} m".font_name – TrueType font file name.
font_size – Size in pixels (if size_in_pixels) or world units.
color –
(r, g, b, a)— yellow by default.size_in_pixels – If
True, font_size is screen pixels.world_height – If set, overrides font_size.
offset_perp – Perpendicular offset (world units, positive = left of polyline direction).
smoothing – SDF edge-width multiplier.
glow_enabled – Enable background halo for readability.
glow_width – Glow SDF threshold.
glow_color –
(r, g, b, a)— dark semi-transparent default.snap_radius – If set, label is hidden when the mouse is farther than this distance (in world units) from the polyline.