Text Pipeline Overview
ftui-text is a full-stack text pipeline that takes bytes and produces
renderable, styled, display-width-correct units — without knowing
anything about ANSI, Cells, or the terminal. Everything in this crate
is a pure function of its input; the render layer consumes the outputs.
The pipeline, stage by stage
bytes (your source: file, network, tick)
│
▼
┌──────────────────────────────────────────────────────────┐
│ 1. Rope storage │
│ rope.rs — balanced tree of chunks, O(log n) edits │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 2. Editor (optional) │
│ editor.rs — cursor, selection, undo/redo, clipboard │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 3. Normalization │
│ normalization.rs — NFC / NFD / NFKC / NFKD │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 4. Script segmentation │
│ script_segmentation.rs — TextRun per script/direction │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 5. Bidi │
│ bidi.rs — UAX#9 runs, visual ↔ logical mapping │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 6. Shaping │
│ shaping.rs — LRU-cached glyph layout, OpenType │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 7. Width cache │
│ width_cache.rs — per-grapheme cell width lookup │
└──────────────────────────────────────────────────────────┘
│
▼
Segments, Spans, Text (styled, measured, shaped)
│
▼
ftui-render (out of scope for this crate)You don’t have to use every stage. The rope alone is useful as a dictionary-free editable string; the width cache alone is useful for any ANSI-emitting program that needs correct column math for emoji.
Where each stage is documented
Balanced tree of 512–2048-char chunks. O(log n) insert, delete, slice.
RopeCursor, selection, grapheme-aware motion, undo coalescing, clipboard.
EditorNFC / NFD / NFKC / NFKD. When to use which.
NormalizationUAX #9 runs, visual ↔ logical mapping, paragraph direction.
BiDiScript-specific glyph positioning, LRU cache, generation-based invalidation.
ShapingGraphemeId, FxHash width cache, ASCII fast-path.
The styled atomic units that cross into rendering.
Spans and segmentsThe three-Cow philosophy
Every user-facing type in this crate — Rope::slice(range),
Segment<'a>, Line<'a>, Span<'a> — uses std::borrow::Cow<'a, str>
so static strings never allocate and dynamic strings allocate exactly
once:
// Zero-allocation
Span::raw("Status: ")
// One allocation
Span::raw(format!("Status: {count}"))That discipline runs top-to-bottom. Even the rope’s slice borrows
from the underlying chunks when the range lives in a single chunk, and
only allocates when the range crosses chunks.
Independent of the render layer
ftui-text does not import ftui-render. A width, a shaped run, a
BiDi segment — none of these know about Cells, Buffers, or ANSI.
The boundary is deliberate: the same text pipeline is used by the
terminal backend, the web backend, the snapshot tester, and any future
renderer (PDF, image) with zero rewrites.
Memory discipline
- Rope chunking keeps edits local; no full-buffer rebalance.
- LRU caches bound memory for width and shaping; evictions are O(1).
SmallVecinlining keeps the hot vectors (control codes in aSegment, font features in a shaping key) stack-resident in the common case.- Generation counters on the shaping cache allow lazy invalidation when a font changes — no need to walk the cache.