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.SHADER_DIR[source]
wolfhece.opengl.text_renderer2d._STRIDE = 5[source]
wolfhece.opengl.text_renderer2d._BYTES_PER_FLOAT = 4[source]
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 \n for multiline.

  • atlasGlyphAtlas to 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 GlyphAtlas for metrics.

  • points(N, 2) polyline vertices in world coords.

  • cum_dist(N,) cumulative distances (from get_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 as build_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]
_TEXT_GEOM_CACHE_MAX = 512[source]
_TEXT_MEASURE_CACHE_MAX = 256[source]
_program: int | None = None[source]
_locs: dict | None = None[source]
_vao: int | None = None[source]
_vbo: int | None = None[source]
_text_geom_cache: collections.OrderedDict[source]
_text_measure_cache: collections.OrderedDict[source]
classmethod get_instance() TextRenderer2D[source]

Return the singleton, creating it lazily.

_init_program()[source]

Compile and link vertex + fragment shaders.

static _compile(shader_type: int, path: pathlib.Path) int[source]

Compile a single GLSL shader from path.

_ensure_vao_vbo()[source]

Create VAO/VBO if not yet allocated.

_upload(vbo_data: numpy.ndarray)[source]

Upload vertex data to the shared VBO.

static _q(val: float, ndigits: int = 7) float[source]

Quantize a float for stable cache keys.

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 \n for 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 True font_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 True font_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.

destroy()[source]

Free GPU resources.