Unicode layout semantics for the terminal grid.
TermShape keeps the terminal concepts that applications need—cell ownership, grapheme-safe cursors, wrapping, truncation, selections, and direction—separate from optional font-aware glyph geometry. It is designed for CLI programs, TUI frameworks, and terminal renderers without being tied to a particular terminal, script, or renderer.
- A Rust semantic layout core with Unicode 17 grapheme segmentation.
- An independent, pure-Go semantic implementation using the same shared conformance vectors.
- Optional HarfBuzz shaping with bidi/script runs, font fallback, continuous geometry, visual cursors, and hit testing.
- An opt-in cumulative cell allocator for consumers with real measured glyph advances that need a terminal-cell projection.
- A stable C ABI for semantic and generic shaped native layout.
- A reusable Ratatui semantic adapter and a small Lip Gloss fitting adapter.
- A Ratatui visual example that presents HarfBuzz-rasterized glyph tiles in terminals with the Kitty graphics protocol.
Semantic layout and visual rendering are different jobs.
TermShape can tell an application where a cursor may stop, how text owns terminal cells, and how shaped glyph geometry relates to that text. It cannot make an unmodified terminal emulator or TUI renderer start shaping a script it does not support. A font-aware renderer must consume the shaped output itself.
Rust semantic layout:
let layout = termshape::Layout::new("கென்னை விடு");
assert_eq!(layout.width(), 10);
assert_eq!(layout.cursor_stops(), &[0, 6, 12, 18, 19, 25, 31]);
let wrapped = layout.wrap(4)?;Go semantic layout:
layout := termshape.NewLayout("ക്ഷേത്രം")
fmt.Println(layout.Width())
fmt.Println(layout.CursorStops())Font-aware layout uses the optional HarfBuzz adapter. The caller chooses its fonts and its physical terminal-cell advance; TermShape does not ship or select fonts.
use termshape::{Layout, TerminalProfile};
use termshape_harfbuzz::HarfBuzzShaper;
let shaper = HarfBuzzShaper::from_file("/path/to/font.ttf")?;
let input = shaper.shape_input("கென்னை விடு", 1000.0)?;
let semantic = Layout::with_profile("கென்னை விடு", TerminalProfile::default());
let shaped = semantic.apply_shaping(&input)?;
let cursors = semantic.visual_cursors_for_byte(&shaped, 0);
let hit = semantic.hit_test_visual(&shaped, 0, 0.5);For multiple fonts, create a HarfBuzzFontChain from loaded
HarfBuzzShapers. It selects the first font that covers each resolved
bidi/script run and reports a missing glyph instead of silently accepting a
.notdef box.
cargo run -p termshape-ratatui-example -- --termshape
go run ./examples/bubbletea --termshape--termshape is semantic integration: it prevents layout operations from
splitting graphemes, but it still leaves glyph drawing to the framework and
terminal. For actual shaped Indic glyphs in Ghostty, Kitty, or WezTerm, use the
visual example with fonts that cover the selected scripts:
cargo run -p termshape-ratatui-example -- \
--visual \
--font /path/to/NotoSansTamil.ttfAdd one --font PATH for each needed fallback font. The visual mode uses
terminal cell pixel metrics reported by the terminal; use
--cell-pixels WIDTHxHEIGHT only when that metric is unavailable. It reports
missing font coverage instead of displaying a .notdef glyph.
The Rust/Ratatui visual presenter is available for Kitty-graphics terminals. The Go/Bubble Tea integration is currently semantic-only: its fitting and grapheme-safe editing are usable, but its font-aware visual renderer is still work in progress. It does not yet provide the same shaping guarantee as Rust visual mode.
The shaped reference example creates a local HTML/SVG report. It renders real text from the supplied local font files and overlays TermShape’s continuous cluster geometry and terminal-cell ownership:
cargo run -p termshape-shaped-renderer-example -- \
--font /path/to/NotoSansTamil.ttf \
--cell-width 1000 \
--text 'கென்னை விடு'It writes target/termshape-shaped.html by default. Open that local file in a
browser that permits access to local font files.
cargo test --workspace --locked
cargo clippy --workspace --all-targets --locked -- -D warnings
go test -buildvcs=false ./go/termshape ./go/lipgloss ./examples/bubbletea
cc -std=c11 -fsyntax-only -x c -include include/termshape.h /dev/nullThe Rust and Go implementations both consume
tests/conformance/semantic-v1.json.
The core also has property tests for arbitrary Unicode and arbitrary shaped
input.
crates/termshape: semantic Rust core.go/termshape: independent Go semantic core.crates/termshape-harfbuzz: optional font-aware adapter and fallback chain.crates/termshape-render: HarfBuzz glyph-tile rasterizer and tile cache.crates/termshape-ratatui: thin semantic Ratatui adapter.crates/termshape-ffiandinclude/termshape.h: semantic native ABI.examples: Ratatui, Bubble Tea, and shaped reference examples.
See docs for architecture, limitations, integrations, and
conformance details.