Files
ghostty-rs/docs/rewrite/notes/ghostty-font-F2.md
T
ddidderr 7b408257c4 feat(font): add run iterator, shared grid set and config integration
Completes the font engine port:
- shaper/run.rs ports run.zig: the RunIterator over terminal rows that
  splits text runs at style/face boundaries, selection and cursor,
  handles graphemes and ligatures; Shaper::shape_run shapes a run.
- shared_grid_set.rs ports SharedGridSet.zig: grids keyed by a config
  derived key, handed out as Arc<SharedGrid>.
- Font-related types now have one canonical definition in ghostty-config
  (Variation, Descriptor, CodepointMap, metric modifiers, freetype load
  flags, synthetic style, shaping break) which the font crate re-exports.
  MetricModifier::apply_i32 now saturates to -i32::MAX like Zig.
- The 23 terminal-driven harfbuzz shaper tests are ported, printing via
  Screen until Terminal lands.

Test Plan:
- cargo test -p ghostty-font (157 passed, 3 ignored - also skipped in
  Zig); Devanagari/Chakma/Bengali tests return early without system fonts
- cargo test -p ghostty-config (255 passed)
2026-10-11 22:38:55 +02:00

5.0 KiB

ghostty-font — F2 (config integration, run iterator, SharedGridSet, terminal-dependent shaper tests)

Done

  • Deps ghostty-config, ghostty-terminal re-added (+ dev-dep ghostty-vt for SGR/Rgb in tests).
  • Type unification (canonical definitions live in ghostty-config, font re-exports):
    • face::{Variation, VariationId} = ghostty_config::font_types (VariationId(u32); VariationId::init(b"wght"), .0, .str()).
    • discovery::Descriptor = config's; Descriptor::toFcPattern is now the free fn discovery::to_fc_pattern(&Descriptor).
    • codepoint_map::{CodepointMap, Entry} = config's CodepointMap/CodepointMapEntry (range: [u32; 2]).
    • metrics::Modifier = ghostty_config::metric_modifier::MetricModifier (type alias; ModifierParseError and Modifier::format_entry/parse_cli removed, use parse/parse_cli_value and the config ConfigField impl). ModifierSet/Key stay in font (+ new Key::ALL). Config's apply_i32 out-of-range now yields -i32::MAX like Zig (only config edit besides doc comment in font_types.rs).
    • config_types.rs is now only re-exports (FreetypeLoadFlags, FontSyntheticStyle, FontShapingBreak, FontStyle, GraphemeWidthMethod); face::FreetypeLoadFlags re-exports config's.
    • INTEGRATION_TODO: removed the three font lines; config line narrowed to clipboard map / CursorStyle.
  • shaper/run.rs (run.zig): TextRun, RunCell, RunOptions, RunIteratorHook trait (impl for harfbuzz + noop shapers), RunIterator. Full logic ported: invisible-cell skipping, right trim, selection/cursor splitting, spacer skipping, "fl/fi/st" ligature splits, style change splitting (bg ignored), font-style/presentation selection, grapheme font search (index_for_cell), U+FFFD / space fallback, Kitty placeholder, relative-cluster position-independent hash. shape.rs re-exports TextRun/RunIterator/RunOptions/RunIteratorHook.
  • shared_grid_set.rs (SharedGridSet.zig): SharedGridSet (new, ref_ (Zig ref), deref(&Key), count), DerivedConfig::new(&Config), Key (descriptors, style offsets, codepoint map, metric modifiers, font size, freetype flags; hashcode-equality like the Zig map), collection building incl. discovery, variation-retry, built-in JetBrains Mono / Nerd symbols / emoji fallbacks. Grids are Arc<SharedGrid>; the set drops its Arc at ref-count 0. discovery::Fontconfig is now Clone (Arc inside) so the resolver shares the set's discoverer.
  • harfbuzz::Shaper::shape_run(&SharedGrid, &TextRun) = Zig shape(run) (takes the grid write lock to get the face).

Tests

  • SharedGridSet.zig: 5/5 (Key, Key different font points, Key different font DPI, SharedGridSet + extra key_descriptors_and_modifiers).
  • All 23 deferred harfbuzz tests ported (in shaper/harfbuzz.rs), plus F1's terminal-free ones kept. Font crate: 157 pass, 3 ignored. Ignored (always error.SkipZigTest upstream, bodies ported from the commented-out Zig): shape_tai_tham_vowels_position_differs_from_advance, shape_tai_tham_letters_run_offset_y_differs_from_zero, shape_javanese_ligatures.
  • Tests needing a discovered system font (Devanagari + Bengali = "Arial Unicode MS", Chakma = "Noto Sans Chakma", Tibetan = "Noto Serif Tibetan") return early (Zig SkipZigTest) when the font is missing; only Tibetan is installed on this machine (verified running); Devanagari/Chakma/Bengali are therefore not exercised here.
  • Removed duplicate tests that moved to config: F1's variation_id_* in face.rs and formatconfig_* in metrics.rs (still covered by ghostty-config tests); adapted modifier_parse_errors.

Deviations / TODO(integration)

  • Terminal/RenderState do not exist (usable) in ghostty-terminal yet, so the tests use a small Term helper over Screen (shaper/test_util.rs): test_write_string for text, own grapheme-cluster attach via ghostty_unicode::grapheme_break for tests that enable grapheme_cluster mode, Screen::set_attribute for SGR, cursor_right for \e[5C. RunCell::from_page_row(page, y) replaces RenderState cells (TODO(integration): switch to the terminal render-state cell type). Once Terminal is final the tests can be rewritten to drive it with the VT stream.
  • TextRun has no grid field (keeps it Copy); the iterator's RunOptions.grid is &SharedGrid. RunIterator::next(&mut shaper) takes the shaper (hooks) per call instead of storing it, so the shaper can shape between calls.
  • Run hash uses std DefaultHasher (Zig: Wyhash) — only used as a cache key.
  • KITTY_PLACEHOLDER (0x10EEEE) is a local const (TODO(integration): terminal kitty graphics unicode module).
  • macOS Apple Emoji branch in the collection builder is omitted (Linux only); discovery is always available.
  • SharedGridSet::ref_ holds the set lock while building the collection (as Zig).
  • shared_grid::CellSize still stands in for renderer::CellSize.
  • Quality gate: cargo build/test ok; clippy with -D warnings for ghostty-font passes with --no-deps (the dependency ghostty-terminal currently fails clippy in its in-progress terminal.rs, not my crate); fmt applied.