# Text, GUI, grutil & particles This cluster covers the engine's "2D-facing" content generators and helpers: the text rendering pipeline (`panda/src/text` + its FreeType wrapper `panda/src/pnmtext`), the C++ GUI widget layer (`panda/src/pgui`, the foundation under DirectGui), a grab-bag of graphics utilities (`panda/src/grutil`: MeshDrawer, GeoMipTerrain, MovieTexture, FrameRateMeter, LineSegs, etc.), the legacy C++ particle engine (`panda/src/particlesystem`), and the nonlinear projection/lens-distortion system (`panda/src/distort`). Almost everything here is a `PandaNode` subclass (or produces `GeomNode`s) that lives in the 2-D or 3-D scene graph and is driven by the normal cull/draw traversal. The pieces interlock: `TextNode` provides labels for `PGItem` widgets and for `FrameRateMeter`; `pgui` hooks the cull traversal to register mouse regions; `distort` wraps any camera setup at render time. They share Panda's standard machinery — `CycleData`/`PipelineCycler` for thread-safe state, `Geom`/`GeomVertexData` for geometry, and `RenderState`/`RenderAttrib` for appearance. ## text **What it is.** The text assembly and rendering system. Given a Unicode string, a `TextFont`, and a `TextProperties` (color, alignment, shadow, wordwrap, etc.), it lays out glyphs into rows and emits `Geom` geometry that can be parented into the 2-D or 3-D scene graph. `TextNode` is the public entry point; the heavy lifting (layout, wordwrap, kerning, bidi-ish property runs, harfbuzz shaping) happens in `TextAssembler`. **Central abstraction & inheritance.** `TextNode` (`panda/src/text/textNode.h`) multiply-inherits `public PandaNode, public TextEncoder, public TextProperties` — so a TextNode *is* both a scene-graph node and a bundle of text properties, which is why you set color/alignment directly on it. It can be used two ways (documented in the header): parent it into the graph and it renders itself like a hidden `GeomNode`, or keep it detached and call `generate()` to get a fresh ordinary node of baked geometry each time. **Key classes and roles.** - `TextNode` — `panda/src/text/textNode.{h,cxx}`. Owns frame/card decoration (`set_frame_*`, `set_card_*`), `max_rows`/overflow handling, and the `FlattenFlags` enum (`FF_light`/`FF_medium`/`FF_strong`/`FF_dynamic_merge`) controlling post-assembly flattening. - `TextFont` — `panda/src/text/textFont.{h,cxx}`. Abstract base (`TypedReferenceCount`, `Namable`). Defines the `RenderMode` enum (`RM_texture`, `RM_wireframe`, `RM_polygon`, `RM_extruded`, `RM_solid`, `RM_distance_field`) and the pure-virtual `get_glyph(int character, CPT(TextGlyph) &)`. - `DynamicTextFont` — `panda/src/text/dynamicTextFont.{h,cxx}`. `public TextFont, public FreetypeFont`; rasterizes glyphs on the fly from TTF/OTF via FreeType (guarded by `#ifdef HAVE_FREETYPE`). Packs glyphs into `DynamicTextPage` textures. - `StaticTextFont` — `panda/src/text/staticTextFont.{h,cxx}`. Wraps a pre-built egg/bam model whose geometry already contains the glyphs (no FreeType needed). - `DynamicTextPage` / `DynamicTextGlyph` — `panda/src/text/dynamicTextPage.{h,cxx}`, `dynamicTextGlyph.{h,cxx}`. A `DynamicTextPage` *is a* `Texture`; it allocates rectangular slots (`slot_glyph()`, `find_hole()`, `garbage_collect()`) and holds the rasterized bitmaps. This is the glyph cache. - `TextGlyph` / `GeomTextGlyph` — `panda/src/text/textGlyph.{h,cxx}`, `geomTextGlyph.{h,cxx}`. One renderable glyph: either a textured quad (`set_quad`/`has_quad`/`get_quad`) or full geometry (`set_geom`). `GeomTextGlyph` is a `Geom` specialization (`: public Geom`) that holds a reference to a `TextGlyph` to maintain the per-glyph geom usage count, so dynamic glyphs can be recycled safely once no geoms reference them. - `TextAssembler` — `panda/src/text/textAssembler.{h,cxx}`. Not normally used directly; `TextNode` delegates layout to it. Handles `set_wtext`/`set_wsubstr`, wordwrap, `dynamic_merge`, multiline mode, and (when built with harfbuzz) complex-script shaping. - `TextProperties` / `TextPropertiesManager` — `panda/src/text/textProperties.{h,cxx}`, `textPropertiesManager.{h,cxx}`. The formatting bag and a registry mapping names to property sets, used by the in-line `\1name\1...\2` push/pop escape sequences inside text strings. - `TextGraphic` — `panda/src/text/textGraphic.{h,cxx}`. Lets you embed arbitrary geometry (icons) inline in a run of text. - `FontPool` — `panda/src/text/fontPool.{h,cxx}`. Filename → shared `TextFont` cache (analogous to `TexturePool`). - `default_font.cxx` — the compiled-in fallback font (`cmss12`), used when no font is set so text always renders. **How it plugs in.** `TextNode` is a `PandaNode`, so it participates directly in cull/draw. The output geometry uses standard `Geom`/`RenderState`; for `RM_texture` fonts the glyph quads share the `DynamicTextPage` textures. Downstream consumers: `pgui` (`PGButton`/`PGEntry` labels), `grutil`'s `FrameRateMeter` and `SceneGraphAnalyzerMeter` (both subclass `TextNode`), and DirectGui/OnscreenText in `direct/`. **Where to start.** To change layout/wordwrap/shaping, read `TextAssembler::assemble_text` and `assemble_paragraph` in `textAssembler.cxx` (the harfbuzz path is at `#if defined(HAVE_HARFBUZZ) && defined(HAVE_FREETYPE)` around line 1482). To change frame/card geometry or the public API, start in `textNode.cxx` (`do_rebuild`, `do_measure`, `generate`). For glyph caching/atlas behavior, `dynamicTextFont.cxx` (`make_glyph`, `slot_glyph`) and `dynamicTextPage.cxx`. **Gotchas / rationale (community).** - Rebuilding a TextNode every frame is a classic perf trap: the docs page [Too Many Text Updates](https://docs.panda3d.org/1.10/python/optimization/performance-issues/too-many-text-updates) recommends not regenerating geometry each frame and exploiting the cached glyph pages. Forum threads on animating per-glyph geometry confirm you must reach into the generated `Geom`s rather than re-assemble ([t/31288](https://discourse.panda3d.org/t/31288), [t/29882](https://discourse.panda3d.org/t/29882)). - Signed-distance-field text (`RM_distance_field`) gives crisp text at any scale and was added across `pnmtext`/`text`; see commit [f4f7df99](https://github.com/panda3d/panda3d/commit/f4f7df9949a04a27332d5b0c3839908a0423342e) ("Add signed-distance-field text rendering to DynamicTextFont and egg-mkfont") and forum [t/24063](https://discourse.panda3d.org/t/24063). For crispness at large sizes you typically raise the font's `point_size`/`pixels_per_unit` rather than scale the node ([t/28009](https://discourse.panda3d.org/t/28009)). - `set_glyph_scale` interacts subtly with mixed-size runs ([t/4680](https://discourse.panda3d.org/t/4680)). **Config (`config_text.cxx`).** `text_flatten`, `text_dynamic_merge`, `text_kerning`, `text_use_harfbuzz`, `text_anisotropic_degree`, `text_texture_margin`, `text_poly_margin`, `text_page_size` (atlas page dimensions), `text_small_caps`/`text_small_caps_scale`, `text_default_font`, `text_tab_width`, the in-line escape keys (`text_push_properties_key`, `text_pop_properties_key`, `text_soft_hyphen_key`, `text_soft_break_key`, `text_embed_graphic_key`), `text_hyphen_ratio`, `text_max_never_break`, `text_default_underscore_height`, and the texture sampler defaults `text_minfilter`/`text_magfilter`/`text_wrap_mode`/`text_quality_level`/`text_render_mode`. ## pnmtext **What it is.** The thin, shared FreeType-2 wrapper. It abstracts loading a font face, setting size/scaling, and rasterizing glyphs — the common substrate used by *both* `text/DynamicTextFont` (glyphs → scene-graph geometry) and `PNMTextMaker` (glyphs → pixels in a `PNMImage`). Keeping this layer separate is why DynamicTextFont can inherit it without dragging in scene-graph code. **Central abstraction & inheritance.** `FreetypeFont` (`panda/src/pnmtext/freetypeFont.h`) inherits `Namable` and wraps a `FreetypeFace`. It exposes point size, pixels-per-unit, pixel size, scale factor, native antialias, and a `WindingOrder` enum (for outline/polygon extraction). `DynamicTextFont` and `PNMTextMaker` are its two concrete derivations. **Key classes and roles.** - `FreetypeFont` — `freetypeFont.{h,cxx}`. `load_font()` (from filename or in-memory buffer), size/scale management, glyph slot acquisition, and the bitmap/outline extraction used to build glyphs. The whole module is gated on FreeType being present. - `FreetypeFace` — `freetypeFace.{h,cxx}`. A reference-counted, mutex-guarded wrapper around the raw `FT_Face` so a single decoded face can be *shared between copied font instances*. Holds the static `FT_Library` and `acquire_face()/release_face()` to safely set the active char size/DPI. The `Mutex _lock` matters: FreeType faces are not reentrant. - `PNMTextMaker` — `pnmTextMaker.{h,cxx}`. `public FreetypeFont`; renders strings straight into a `PNMImage` with its own `Alignment` enum (`A_left`/`A_right`/`A_center`), fg/interior colors, and an interior-fill flag. Used for offline/CPU text (e.g. baking labels into textures). - `PNMTextGlyph` — `pnmTextGlyph.{h,cxx}`. A single rasterized glyph as a pixel buffer, with `place()` to blit it into a target image. **How it plugs in.** Purely a service layer — it has no scene-graph dependencies and depends on `pnmimage` plus the system FreeType (and optionally harfbuzz). `text/DynamicTextFont` consumes its glyph bitmaps to fill `DynamicTextPage` atlases; `PNMTextMaker` is used by tooling and anyone rendering text to images. **Where to start.** `freetypeFont.cxx` (`load_font`, `get_glyph`/`render_glyph`, and the SDF/outline paths). For face sharing and thread-safety, `freetypeFace.cxx` (`acquire_face`/`release_face`, `initialize_ft_library`). **Gotchas / rationale.** The `FreetypeFace` mutex exists precisely because FreeType's `FT_Face` carries mutable per-call state (active size). The pnmtext/text split is the seam through which SDF rendering was added (commit [f4f7df99](https://github.com/panda3d/panda3d/commit/f4f7df9949a04a27332d5b0c3839908a0423342e) touches `freetypeFont.{I,cxx}`). **Config (`config_pnmtext.cxx`).** `text_point_size`, `text_pixels_per_unit`, `text_scale_factor`, `text_native_antialias` — the defaults inherited by every `FreetypeFont`. ## pgui **What it is.** The C++ GUI widget framework — the foundation that Python's DirectGui sits on. It provides mouse-interactive 2-D widgets (buttons, text entries, sliders, scroll frames, wait bars) as `PandaNode` subclasses. Each widget owns a `PGMouseWatcherRegion` (a screen rectangle) and a set of "state" subgraphs; the current state's subgraph is what gets rendered. Interaction is wired through `MouseWatcher` events. **Central abstraction & inheritance.** `PGItem` (`panda/src/pgui/pgItem.h`) `: public PandaNode` is the base of every widget. It defines the full interaction protocol as virtuals — `enter_region`/`exit_region`/`within_region`/`without_region`, `focus_in`/`focus_out`, `press`/`release`/`keystroke`/`candidate`/`move` — plus `activate_region()` and a `cull_callback()`. The class must be parented beneath a `PGTop` for any of this to fire. **Key classes and roles.** - `PGItem` — `pgItem.{h,cxx}`. Base widget: holds the `PGMouseWatcherRegion`, the state subgraphs, frame style, optional sound, and a `PGItemNotify` callback hook. Guarded by a `LightReMutex` for thread-safety. - `PGTop` — `pgTop.{h,cxx}`. The required root of any GUI subgraph under render2d. Its `cull_callback()` swaps in a `PGCullTraverser` and clears/repopulates the `MouseWatcher` region set every cull, forcing depth-first, left-to-right 2-D draw order. Holds the `MouseWatcher` and a `PGMouseWatcherGroup`. - `PGCullTraverser` — `pgCullTraverser.{h,cxx}`. A `CullTraverser` specialization that carries the owning `PGTop` and a running `_sort_index` so each `PGItem` it visits can register its region with the correct sort. - `PGButton` — `pgButton.{h,cxx}`. `: public PGItem`; `State` enum `S_ready`/`S_depressed`/`S_rollover`/`S_inactive`, `setup(label, bevel)` or per-state NodePaths, click-button binding, and emits `"-