From 91a01a6774e560511f289628ef45abfc7b1dd06f Mon Sep 17 00:00:00 2001 From: aiongg Date: Sun, 11 Oct 2026 01:09:28 +0000 Subject: [PATCH] Compose: span columns in a one-column frame add no span space --- crates/compose/src/lib.rs | 9 ++++++--- crates/compose/src/tests.rs | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/crates/compose/src/lib.rs b/crates/compose/src/lib.rs index 1c9a225a8..49468961f 100644 --- a/crates/compose/src/lib.rs +++ b/crates/compose/src/lib.rs @@ -805,6 +805,9 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C // the next column. let spanning = matches!(pp.span_columns, SpanColumns::Span(n) if n != 1); span_paras[pi] = spanning; + // Span space applies where the paragraph spans more than one column; in a one-column frame + // a span changes nothing. + let spans_in = |fi: usize| spanning && cols.get(fi).is_some_and(|c| c.len() > 1); // Consecutive paragraphs with the same Split Columns settings share a split block; within // it, a paragraph moves on to the next sub-column. let split_cfg = SplitCfg::of(&pp); @@ -877,7 +880,7 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C cur.col = 0; cur.last_baseline = Some(baseline); cur.last_descent = descent; - text_above = true; + text_above = multi; } else if cur.col != 0 { cur.col = 0; cur.resume(); @@ -1164,7 +1167,7 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C baseline: l.baseline, descent: l.descent, reference: cur.last_reference, - pending: pp.space_after.max(pp.span_space_after), + pending: if spans_in(cur.fi) { pp.space_after.max(pp.span_space_after) } else { pp.space_after }, }; cur.band = Band { para: pi + 1, line0: ft.lines.len(), above: Some(above) }; } @@ -1245,7 +1248,7 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C } } } - cur.pending += if spanning || ends_split { pp.space_after.max(pp.span_space_after) } else { pp.space_after }; + cur.pending += if spans_in(cur.fi) || ends_split { pp.space_after.max(pp.span_space_after) } else { pp.space_after }; pi += 1; } notes.finish(doc, story, frames, &cols, &mut out); diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs index fdb76eaa6..3c8f292a5 100644 --- a/crates/compose/src/tests.rs +++ b/crates/compose/src/tests.rs @@ -2896,6 +2896,29 @@ fn space_before_and_after_a_span_moves_the_span_and_the_text_below() { assert!((span2 - span0).abs() < 0.01 && (below2 - below0 - 18.0).abs() < 0.01, "{below0} → {below2}"); } +#[test] +fn span_space_applies_only_where_the_paragraph_spans_columns() { + // Every paragraph spans with 12 pt span space after; the frame has `columns` columns. + let tops = |columns: u32, span: bool| { + let (mut d, sid, fid) = doc_with("One\nTwo\nThree", Rect::new(0.0, 0.0, 400.0, 400.0), ParaAttrs::default()); + d.item_mut(fid).unwrap().text_frame_mut().unwrap().options.columns = columns; + for p in &mut d.story_mut(sid).unwrap().paras { + if span { + p.para.span_columns = Some(SpanColumns::Span(0)); + } + p.para.span_space_after = Some(12.0); + } + let cs = compose_story(&d, sid, &ComposeOptions::default()); + first_lines(&cs).iter().map(|l| l.baseline).collect::>() + }; + let plain = tops(1, false); + assert_eq!(plain.len(), 3); + assert_eq!(tops(1, true), plain, "a span in a one-column frame adds no space"); + let spread = tops(3, true); + let gap = |v: &[f64], i: usize| v[i + 1] - v[i]; + assert!((gap(&spread, 0) - gap(&plain, 0) - 12.0).abs() < 0.01, "{spread:?} vs {plain:?}"); +} + /// The first line of each paragraph. fn first_lines(cs: &ComposedStory) -> Vec<&Line> { all_lines(cs).into_iter().filter(|l| l.first_in_para).collect()