diff --git a/crates/compose/src/lib.rs b/crates/compose/src/lib.rs index cd0e7cbd..6d4c4bb9 100644 --- a/crates/compose/src/lib.rs +++ b/crates/compose/src/lib.rs @@ -576,8 +576,11 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C .and_then(|t| t.chars().next_back()) .and_then(break_start); if let Some(t) = story.para_table(pi) { + let start = after_break.unwrap_or(pp.start_paragraph); if cur.last_baseline.is_some() { - cur.start(after_break.unwrap_or(pp.start_paragraph), &cols, frames, doc); + cur.start(start, &cols, frames, doc); + } else { + cur.start_at_top(start, frames, doc); } let at_top = cur.last_baseline.is_none(); let start_at = (cur.fi, cur.col); @@ -796,17 +799,22 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C StartParagraph::NextColumn => next_column(cur), other => cur.start(other, &cols, frames, doc), }; - // Paragraph start options. - if let Some(start) = after_break - && cur.last_baseline.is_some() - { - begin_at(&mut cur, start); + // Paragraph start options. At the top of a frame there is nothing to break away from, but + // an odd or even page start still has to land on a page of that parity. + if let Some(start) = after_break { + if cur.last_baseline.is_some() { + begin_at(&mut cur, start); + } else { + cur.start_at_top(start, frames, doc); + } } if force_col[pi] { next_column(&mut cur); } if cur.last_baseline.is_some() { begin_at(&mut cur, pp.start_paragraph); + } else { + cur.start_at_top(pp.start_paragraph, frames, doc); } if spanning && cur.fi < frames.len() { // Close the band: the paragraph goes below the deepest of its columns, which are @@ -1983,6 +1991,20 @@ impl Cursor { StartParagraph::NextEvenPage => self.next_page(frames, doc, Some(false)), } } + /// A start option for a paragraph at the top of a frame: there is nothing to break away from, + /// but an odd or even page start still has to land on a page of that parity, so a paragraph + /// at the top of an even page that asks for the next odd page moves on (and vice versa). + fn start_at_top(&mut self, start: StartParagraph, frames: &[FrameSpec], doc: &Document) { + let want_odd = match start { + StartParagraph::NextOddPage => true, + StartParagraph::NextEvenPage => false, + _ => return, + }; + let Some(page) = frames.get(self.fi).and_then(|f| f.page) else { return }; + if (doc.page_number(page) % 2 == 1) != want_odd { + self.next_page(frames, doc, Some(want_odd)); + } + } /// Move to the next frame on a later page, one with an odd page number (`odd` true) or an /// even one (false) when asked. Frames without a page (parent pages) qualify; with no frame /// left, the rest of the story is overset. diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs index 7a0e790d..f6f5c9ad 100644 --- a/crates/compose/src/tests.rs +++ b/crates/compose/src/tests.rs @@ -595,6 +595,29 @@ fn break_characters_end_their_paragraph_and_keep_page_parity() { assert_eq!(cs.overset_at, Some(text.find("four").unwrap())); } +/// A paragraph that starts at the top of a frame isn't pushed on by Next Page, but Next Odd Page +/// (or Next Even Page) still moves it when that frame is on a page of the other parity. +#[test] +fn odd_and_even_page_starts_keep_parity_at_the_top_of_a_frame() { + use designcraft_doc::StartParagraph; + use designcraft_doc::story::PAGE_BREAK; + let pages_of = |start: StartParagraph| { + let mut d = Document::new(&NewDocument { pages: 4, facing_pages: false, primary_text_frame: true, ..Default::default() }); + let sid = d.settings.primary_story.unwrap(); + // "two" begins at the top of page 2, after the page break that ends "one". + d.story_mut(sid).unwrap().insert(0, &format!("one{PAGE_BREAK}\ntwo\nthree")); + d.story_mut(sid).unwrap().paras[1].para.start_paragraph = Some(start); + let cs = compose_story(&d, sid, &ComposeOptions::default()); + // Frame i is on page i + 1. + cs.frames.iter().enumerate().flat_map(|(fi, f)| f.lines.iter().map(move |l| (l.para, fi + 1))).collect::>() + }; + // Already at the top of an even page: Next Page and Next Even Page leave "two" where it is. + assert_eq!(pages_of(StartParagraph::NextPage), vec![(0, 1), (1, 2), (2, 2)]); + assert_eq!(pages_of(StartParagraph::NextEvenPage), vec![(0, 1), (1, 2), (2, 2)]); + // Next Odd Page takes it to page 3, leaving page 2 empty. + assert_eq!(pages_of(StartParagraph::NextOddPage), vec![(0, 1), (1, 3), (2, 3)]); +} + #[test] fn line_before_a_break_character_is_a_last_line() { // As in InDesign: column, frame and page breaks end the paragraph's last line (set with the