inkferro-core 0.1.0

Layout, text measurement, ANSI render, and frame-diff engine for inkferro — a Rust-backed, byte-for-byte drop-in for the ink terminal UI library.
Documentation
//! Layout engine trait and Taffy backend.
//!
//! # Design decisions
//!
//! ## Trait surface (plan adaptation)
//! The plan listed `set_measure(id, …)` as part of the trait.  Taffy 0.10's
//! measure story is a *closure* passed to `compute_layout_with_measure` rather
//! than a per-node callback registered up-front (see `taffy_tree.rs:905-922`
//! and the `measure.rs` example).  Putting `set_measure` on the *trait* would
//! force every backend to own a registry, but the *signature* of the stored
//! function is backend-specific.  Therefore:
//!
//!   - The **trait** has `set_measure(id, Box<dyn MeasureFn>)` where
//!     `MeasureFn` is a type alias for the concrete closure shape that taffy
//!     needs.  Any yoga-ffi backend that has the same terminal-cell measure
//!     contract can implement the same trait.  If a future backend needs a
//!     different shape, the trait gets a second method; that is the narrowest
//!     breaking change possible.
//!   - `TaffyEngine` owns a `HashMap<u32, Box<dyn MeasureFn>>` keyed by dom
//!     node id.  `calculate` passes a single closure to
//!     `compute_layout_with_measure` that dispatches into that map.
//!   - M1-4 wires real measurement by calling `set_measure` and does NOT need
//!     to touch the trait definition.
//!
//! ## Rounding (yoga-compatible pixel-grid post-pass)
//! `render-node-to-output.ts:129-130` uses the yoga-computed values directly
//! as array column/row indices, with no `Math.round`/`floor`/`ceil`:
//!
//! ```text
//! const x = offsetX + yogaNode.getComputedLeft();   // line 129
//! const y = offsetY + yogaNode.getComputedTop();    // line 130
//! ```
//!
//! Ink trusts yoga's own pixel rounding, which yoga applies *after* the flex
//! solve in `roundLayoutResultsToPixelGrid` (yoga 3.2.1
//! `yoga/algorithm/PixelGrid.cpp`).  Taffy 0.10 also rounds when
//! `use_rounding = true`, but with a *different* rule (cumulative
//! round-half-away in `compute::round_layout`, taffy `compute/mod.rs:219`).
//! That difference produces ±1 divergences against the ink oracle on tie-breaks
//! (e.g. a 3.5-cell leading space: yoga floors a text node's position to 3,
//! taffy rounds to 4) and on text-node sizing.
//!
//! We therefore (M1-7):
//!
//!   1. Disable taffy's rounding (`TaffyTree::disable_rounding()` in
//!      `TaffyEngine::new`); `tree.layout()` then returns unrounded floats.
//!   2. After `compute_layout_with_measure`, run a faithful port of
//!      `roundLayoutResultsToPixelGrid` / `roundValueToPixelGrid`
//!      (`taffy_engine.rs` `round_node` / `round_value_to_pixel_grid`),
//!      specialized to `pointScaleFactor == 1.0`, in f64 with yoga's
//!      `inexactEquals` epsilon (`0.0001`).  The recursion carries *unrounded*
//!      absolute offsets; positions round from the parent-relative value;
//!      dimensions round as `round(absRight) - round(absLeft)`.  Nodes with a
//!      registered measure fn are treated as yoga `NodeType::Text` (floored, not
//!      rounded down, so glyphs are never truncated).
//!   3. Store the rounded, parent-relative integer rects keyed by dom id;
//!      `computed()` returns from that map (falling back to the live taffy
//!      layout only for a node that was never laid out).  `x`/`y` are `i32`
//!      (parent-relative, can be negative with margins); `width`/`height` are
//!      `u16` (terminal cells fit).

mod engine;
mod taffy_engine;

pub use engine::{LayoutEngine, MeasureFn, Rect};
pub use taffy_engine::TaffyEngine;