Every non-final system of a multi-system region now stretches its horizontal slack so its ink fills the content width, instead of sitting at its natural left-aligned width. ENGRAVER_VERSION 7 → 8 (any wrapping score's baked geometry differs; a single-system score is unchanged — its only system is ragged-right). Casting bakes each system by a Placement: a vertical dy plus a horizontal affine world_x = a·x + b (rigid = a:1, b:dx). A justified system spreads the slack linearly (a = 1 + extra/span). The map is CLAMPED to the slot-source range: affine within it, rigid slope-1 beyond it (bearing overhangs, staff lines drawn to the ink edge), so the mapped ink extremes agree exactly with the per-slot deltas and the ink spans exactly [left_margin, left_margin + content_width] (no over/under- shoot). Slot-relative like the E1 remap: glyphs translate by the map at their SLOT's source (intra-slot offsets survive), spanning strokes map endpoints through the affine (they stretch), rigid-width strokes (stems/ledgers, via owning_glyph) track their slot, slur control points map through the affine. Not justified: a region's last system (ragged-right by convention), a degenerate span, or a system already at/over width (never compressed into overlap). Quality consequences (honest): system_break_penalty collapses to ~0 (the point); the width-uniformity axes rise as the full non-final system contrasts with the ragged last line (ten-measure casting_off ~0.45 → ~0.80) — a metric-semantics follow-up noted in DECISIONS. Tests retargeted: the widow rebalance now shows in measure distribution not baked widths; the floor-column test moved to a synthetic vector (robust to fixture values). Goldens regenerated (view_box widens to the content width; same 147 primitives, no collisions). 939 tests, clippy 0, docs -D warnings, conformance 8/8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| Cargo.toml | ||
| DECISIONS.md | ||
| README.md | ||
README.md
epiphany-engrave
Agent I's engraving constraint solver (spec Chapter 9): turns a
ConstrainedLayoutIR into a ResolvedLayoutIR with real geometry. It is the
production-side replacement for epiphany-layout-ir's interface-only
StubSolver; the two live in separate crates so the spec's core/product boundary
stays sharp.
Status: Minimal tier, with casting-off
Engraverruns a deterministic horizontal spacing pass (the first axis of the two-pass spring layout): each spring slot is placed left-to-right by a collision-aware advance derived from real glyph bearings.- A casting-off pass (Phase 3's layout track) then breaks the spaced line
into systems at measure boundaries (greedy first-fit against a
PageGeometry— default A4 portrait at an 8 mm staff), stacks systems vertically at the vertical-band model's inter-system gap, assigns pages by content height, and populates the realResolvedPage/ResolvedSystemtree. Every position is baked into a single y-up world frame (pages stacked vertically), so the SVG renderer and hit-testing consume the flat glyph/stroke lists unchanged. - The IR's declared constraints are evaluated — geometric families against
the pre-casting spaced frame, break constraints against the final break
structure (hard breaks are always honoured; a pathological soft break is
skipped and recorded as an
IrOverridedecision). Chosen breaks are recorded asEngravingDecisions withSynthesisKind::EngravedBreaktargets, attributed to the user override that requested them when one did. - It reports
SolverTier::Minimal: hard constraints (break family included) satisfied, no optimality claim — the quality-metric vector stays the honest all-worst placeholder until the Quality Metric Catalog lands.
Deferred: the vertical soft-spring solve, per-system justification/stretch,
optimal break search, widow/orphan control, and casting-off caching. See
DECISIONS.md.
use epiphany_engrave::Engraver;
use epiphany_layout_ir::{ConstraintSolver, SolverConfig};
let report = Engraver::default().solve(&constrained_ir, &SolverConfig::default());
assert!(report.satisfied_hard_constraints);
let resolved = report.layout; // real pages/systems; hand to epiphany-render-svg