From 1278957455becb2ddb155a62871a4960df842e63 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 12 Oct 2026 15:14:08 +0000 Subject: [PATCH] Text wrap: the minimum gap applies only where a wrap object reaches the column free_slot held a column to the minimum gap width (1.5 em) whenever a wrap object overlapped the line's band vertically, even if the object sat beside another column of the frame: in a frame of narrow columns, an object beside column 2 dropped column 1's lines. The minimum now applies only when the object reaches the column itself (aiongg's review on #589). Test: wrap_object_beside_one_narrow_column_leaves_the_other_alone (two 18 pt columns in a 14 pt paragraph, an object beside column 2) fails before (overset) and passes after. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa --- crates/compose/src/lib.rs | 8 +++++--- crates/compose/src/tests.rs | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/crates/compose/src/lib.rs b/crates/compose/src/lib.rs index 5ab809649..cc29bee0e 100644 --- a/crates/compose/src/lib.rs +++ b/crates/compose/src/lib.rs @@ -2080,8 +2080,8 @@ fn line_metrics( /// Widest free horizontal interval of `col` in the band, or None if blocked. A gap a wrap object /// cuts narrower than 1.5 em of `size` is skipped (a sliver beside the object); when no object -/// reaches the band the whole column is free, however narrow (a one-character frame such as an -/// article's closing tombstone). +/// reaches the column in the band the whole column is free, however narrow (a one-character frame +/// such as an article's closing tombstone, or a narrow column beside another that wraps). fn free_slot(f: &FrameSpec, col: Rect, y0: f64, y1: f64, size: f64) -> Option<(f64, f64)> { let mut free = vec![(col.x0, col.x1)]; let mut cut = false; @@ -2092,7 +2092,9 @@ fn free_slot(f: &FrameSpec, col: Rect, y0: f64, y1: f64, size: f64) -> Option<(f WrapMode::JumpToNextColumn if y1 > r.y0 && r.x0 < col.x1 && r.x1 > col.x0 => return None, WrapMode::JumpObject if overlaps_band && r.x0 < col.x1 && r.x1 > col.x0 => return None, WrapMode::BoundingBox | WrapMode::Contour if overlaps_band => { - cut = true; + // Held to the minimum width only when the object reaches this column: one beside + // another column of the frame leaves this one whole, however narrow. + cut |= r.x0 < col.x1 && r.x1 > col.x0; let mut next = Vec::new(); for (a, b) in free { if r.x1 <= a || r.x0 >= b { diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs index b0cc474c7..9e5ad3f25 100644 --- a/crates/compose/src/tests.rs +++ b/crates/compose/src/tests.rs @@ -184,6 +184,25 @@ fn narrow_frame_sets_lines_no_wrap_object_reaches() { assert!(baseline - top < 20.0, "on the first line: {baseline} (text area from {top})"); } +/// In a frame of narrow columns, a wrap object beside column 2 leaves column 1 alone: only a gap +/// the object cuts in its own column is held to the minimum width (it was dropped, as the object +/// overlapped the line's band, and the text went past column 1). +#[test] +fn wrap_object_beside_one_narrow_column_leaves_the_other_alone() { + // Two 18 pt columns (12 pt gutter): narrower than 1.5 em of the 14 pt paragraph. + let (mut d, sid) = tombstone(Rect::new(300.0, 300.0, 348.0, 340.0), Rect::new(336.0, 300.0, 380.0, 340.0)); + let fid = d.spreads[0].items.iter().find(|i| i.text_frame().is_some_and(|t| t.story == sid)).unwrap().id; + d.item_mut(fid).unwrap().text_frame_mut().unwrap().options.columns = 2; + let cs = compose_story(&d, sid, &ComposeOptions::default()); + let f = &cs.frames[0]; + assert_eq!(f.columns.len(), 2); + assert!(f.columns[0].width() < 21.0, "column 1 is narrower than 1.5 em: {}", f.columns[0].width()); + assert!(!cs.is_overset()); + let l = &f.lines[0]; + assert_eq!(l.column, 0, "set in column 1"); + assert!(l.baseline - f.columns[0].y0 < 20.0, "on its first line: {}", l.baseline); +} + #[test] fn paragraph_composer_is_no_worse_than_greedy() { // Sum of squared slack over lines (excluding last) should not exceed the greedy result.