From 05901d3ec1d9c6d5c5c339ec68c49d38ffb3e8d9 Mon Sep 17 00:00:00 2001 From: aiongg Date: Sat, 24 Oct 2026 04:39:34 +0000 Subject: [PATCH] Baseline grid: find the page under a grouped frame from its spread position A group's transform can move a frame onto another page; the page lookup used the frame's bounds without its parents' transforms, so the grid took the wrong page's top margin. --- crates/compose/src/lib.rs | 9 +++++---- crates/compose/src/tests.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/crates/compose/src/lib.rs b/crates/compose/src/lib.rs index f6b7a605c..49f9428c1 100644 --- a/crates/compose/src/lib.rs +++ b/crates/compose/src/lib.rs @@ -686,17 +686,18 @@ pub fn frame_specs(doc: &Document, sid: StoryId) -> Vec { r.is_finite() && r.x0 < a.x1 && r.x1 > a.x0 && r.y0 < a.y1 && r.y1 > a.y0 }); } + // The frame's center on its spread: a group around it can move it to another page. + let center_x = loc.as_ref().map_or(item.bounds(), |l| doc.parent_xf(l).transform_rect_bbox(item.bounds())).center().x; let (page_name, page, left_page) = match (spread_ref, spread) { (Some(designcraft_doc::SpreadRef::Doc(si)), Some(sp)) => { - let c = item.bounds().center(); - let pi = sp.page_at_x(c.x).unwrap_or(0); + let pi = sp.page_at_x(center_x).unwrap_or(0); let abs = doc.first_page_of_spread(si) + pi; (Some(doc.page_name(abs)), Some(abs), sp.pages.get(pi).is_some_and(|p| p.side == designcraft_doc::PageSide::Left)) } (Some(designcraft_doc::SpreadRef::Parent(_)), Some(sp)) => { let prefix = sp.parent.as_ref().map(|p| p.prefix.clone()).unwrap_or_else(|| "A".into()); // The side of the parent page under the frame, so a folio there aligns to the spine of its own page. - let under = sp.page_at_x(item.bounds().center().x).and_then(|pi| sp.pages.get(pi)); + let under = sp.page_at_x(center_x).and_then(|pi| sp.pages.get(pi)); let left = under.is_some_and(|p| p.side == designcraft_doc::PageSide::Left); (Some(prefix), None, left) } @@ -704,7 +705,7 @@ pub fn frame_specs(doc: &Document, sid: StoryId) -> Vec { }; let section_marker = page.map(|abs| section_marker(doc, abs)); // The page under the frame, and its bounds and margins in inner space (bounding boxes). - let under = spread.and_then(|sp| sp.page_at_x(item.bounds().center().x).and_then(|pi| sp.pages.get(pi))); + let under = spread.and_then(|sp| sp.page_at_x(center_x).and_then(|pi| sp.pages.get(pi))); let page_rect = under.map(|pg| (inv.transform_rect_bbox(pg.bounds()), inv.transform_rect_bbox(pg.margin_rect()))); let g = &doc.settings.baseline_grid; let (inc, start) = tf.options.baseline_grid.unwrap_or((g.increment, under.map_or(g.start, |pg| g.origin(pg)))); diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs index e761639b7..e07e7707d 100644 --- a/crates/compose/src/tests.rs +++ b/crates/compose/src/tests.rs @@ -7254,6 +7254,32 @@ fn baseline_grid_relative_to_the_top_margin_starts_at_each_pages_margin() { assert_eq!(lines.len(), 41); } +/// A group's transform moves a frame from the left page of a spread to the right one: the grid +/// starts at the right page's top margin. +#[test] +fn baseline_grid_follows_a_grouped_frame_to_its_page() { + use designcraft_doc::{Content, GridRelative, Item, ItemId, Shape}; + let mut d = Document::new(&NewDocument { pages: 3, facing_pages: true, margins: designcraft_doc::Margins::uniform(36.0), ..Default::default() }); + Arc::make_mut(&mut d.spreads[1]).pages[1].margins.top = 48.0; + d.settings.baseline_grid.start = 0.0; + d.settings.baseline_grid.increment = 13.0; + d.settings.baseline_grid.relative_to = GridRelative::TopMargin; + let lid = d.default_layer(); + let para = ParaFormat { para: ParaAttrs { grid_align: Some(GridAlign::AllLines), ..Default::default() }, ..Default::default() }; + let (fid, sid) = d.add_text_frame(SpreadRef::Doc(1), Rect::new(36.0, 36.0, 336.0, 582.0), lid, LOREM, para).unwrap(); + let sp = Arc::make_mut(&mut d.spreads[1]); + let at = sp.items.iter().position(|it| it.id == fid).unwrap(); + let frame = sp.items.remove(at); + let right = sp.pages[1].x; + let mut g = Item::new(ItemId(d.alloc()), lid, Shape::Group, designcraft_geom::PathData::default()); + g.content = Content::Group { items: vec![frame] }; + g.xf = designcraft_geom::Affine::translate((right, 0.0)); + Arc::make_mut(&mut d.spreads[1]).items.push(Arc::new(g)); + let cs = compose_story(&d, sid, &ComposeOptions::default()); + let first = cs.frames[0].lines[0].baseline; + assert!((first - 61.0).abs() < 1e-6, "{first}"); +} + #[test] fn cache_recomposes_when_the_grid_reference_or_a_top_margin_changes() { use designcraft_doc::GridRelative;