mittens-engine 0.7.0

A Vulkan and OpenXR scene engine with ECS, reactive signals, and Meow Meow scripting
# Text System

This document describes how `TextComponent` is expanded into glyph renderables, how styling is resolved, and what the current lifecycle is.

For the general `TextureComponent` behavior used by glyph atlases and ordinary renderables, see
[docs/spec/texture.md](docs/spec/texture.md).

## 1) Behavior

### Topology produced by `TextComponent`

When a `TextComponent` is registered, the `TextSystem` expands it into a subtree of per-glyph components:

- `TextComponent` (root)
  - For each non-whitespace glyph:
    - `TransformComponent` (glyph local offset in **glyph units**)
      - `RenderableComponent` (quad)
        - `UVComponent` (per-vertex UVs for the glyph)
        - `TextureComponent` (font atlas URI)
        - Optional: `TextureFilteringComponent`
        - Optional: `EmissiveComponent`
        - Optional: `RaycastableComponent`
  - Optional: `TextShadowComponent` influences extra shadow quads per glyph

Notes:

- Whitespace characters (`' '`, `'\t'`) do **not** spawn quads for performance; they only advance the cursor.
- Newlines (`'\n'`) move the cursor to the next row.
- Non-ASCII characters map to `'?'` in the atlas.

### Layout and coordinate system

- Each glyph advances by 1.0 in local X (`col += 1`), and rows go downward in local Y (`row += 1` → glyph y becomes negative).
- A `TextComponent`’s glyph transforms are positioned in **glyph-local units**; you typically scale the subtree with a parent `TransformComponent`.

### Wrapping

Wrapping is governed by `TextComponent` fields:

- `wrap_at`: maximum line length in glyph columns (0 disables wrap)
- `word_wrap`: if true, wrapping prefers wrap opportunities (spaces/tabs and `word_wrap_tokens`) and avoids breaking words
- `word_wrap_tokens`: additional string tokens that are treated as wrap opportunities

Important behavior:

- In `word_wrap` mode, wrapping occurs only if a wrap opportunity was previously encountered on that line; long words may exceed `wrap_at`.

### Font atlas selection

By default, the font atlas URI is:

- `assets/textures/font_system.dds`

You can override the atlas **per text** by attaching an immediate `TextureComponent` child to the `TextComponent` root.

### Filtering

If the `TextComponent` has an immediate `TextureFilteringComponent` child, that filtering choice is copied to each glyph renderable.

### Emissive

If the `TextComponent` has an immediate `EmissiveComponent` child, that value is copied to each glyph renderable.

### Raycastable

If the `TextComponent` has an immediate `RaycastableComponent` child, that value is copied to each glyph renderable.

### Shadow

If the `TextComponent` has an immediate `TextShadowComponent` child, each visible glyph spawns additional shadow quad(s) behind it:

- Shadow quads are offset and scaled per `TextShadowComponent`.
- Shadow quads can optionally be expanded into two passes when `shadow.scale > 1.0`.
- Shadow glyph quads use a per-glyph color override at spawn-time.

### Color and other material properties

`TextSystem` itself does not “own” color. Glyph colors are handled by `RenderableSystem` style resolution.

Current color inheritance rule (important):

- A renderable’s color is taken from the nearest ancestor node that has an **immediate child** `ColorComponent`.
- In other words: **attach `ColorComponent` as a child of `TextComponent` (or another ancestor node), not by making `TextComponent` a child of a `ColorComponent`.**

Example pattern:

- `TextComponent` (text_id)
  - `ColorComponent` (black)
  - ...glyphs...

This matches `RenderableSystem::inherited_color_for_renderable()` which searches for “immediate ColorComponent children” up the ancestry chain.

## 2) Lifecycle (and improvement ideas)

### Lifecycle today

1. **Build authoring**
   - You create a `TextComponent` and attach it somewhere under a transform hierarchy.
   - Optional style components may be attached as immediate children of the `TextComponent` root (texture override, filtering, emissive, raycastable, shadow, color).

2. **Registration**
   - When the world processes initialization/registration, `RegisterText` triggers `TextSystem::register_text`.
   - If the `TextComponent` is not built, glyph subtrees are created and the component is marked built.

3. **Initialization of spawned glyphs**
   - After glyphs are spawned, the engine initializes the component tree below the `TextComponent` root (idempotently).

4. **Rendering**
   - `RenderableSystem` and `TextureSystem` register the spawned renderables/textures.
   - Color is resolved via `RenderableSystem` style inheritance and/or explicit registration (`RegisterColor`).

### Updating text content

- Changing the string typically happens via `SetText`, which triggers a rebuild of the glyph subtree.

### Updating material/style after build (current state)

- **Color:** works well if you attach a `ColorComponent` under the `TextComponent` root. Updating that `ColorComponent` via `SetColor` triggers `RegisterColor`, which can propagate to descendant glyph renderables.
- **Filtering / emissive / raycastable:** currently treated as **build-time copied style**. Changing the root component after glyphs are built does not automatically update existing glyph renderables.

If you want live updates today, you generally need to either:

- Rebuild the text (content change path), or
- Mutate the per-glyph components (e.g., each glyph’s `TextureFilteringComponent` / `EmissiveComponent`) and re-register them.

### Potential improvements (features + performance)

**Style propagation / live updates**

- Add an explicit “style propagation” operation for text (e.g., re-run `RegisterText` to apply style deltas to existing glyphs, without rebuilding content).
- Unify inherited-style semantics across systems:
  - Extend `RenderableSystem` to support inherited `EmissiveComponent` and `TextureFilteringComponent` (similar to how color/opacity/cutout behave), so a single root-level component can affect all descendant glyph renderables.
  - Consider making `TextureSystem` support inherited filtering for a subtree, or add a dedicated text-level filtering override that is applied by `TextSystem` and can be re-applied.

**Incremental rebuilds**

- Avoid full rebuild on small edits by diffing the old/new strings and only touching the changed range.
- Pool glyph nodes (reuse transforms/renderables) to reduce churn and allocations.

**Performance and batching**

- Keep caching per-character quad meshes (UV override cache already exists) and consider caching full glyph renderable templates.
- Allow packing glyph instances into a single mesh (instanced quads) to reduce draw calls.

**Layout features**

- Support explicit alignment/anchoring in `TextComponent` (center, right, baseline), rather than relying on external transforms to approximate.
- Add optional glyph metrics / kerning (atlas metadata) for improved typography.

**Missing rendering features**

- Outline / SDF fonts
- Per-span styling (rich text)
- Proper multiline measurement API (to avoid heuristic centering)