From 19bf1f58944a42f17903869c162a33aba22b80b0 Mon Sep 17 00:00:00 2001 From: Zachary Capalbo Date: Mon, 12 Jun 2023 08:28:25 -0400 Subject: [PATCH] Clear page to Even Or Odd (#1427) Co-authored-by: Laurenz --- library/src/layout/mod.rs | 25 +++++-- library/src/layout/page.rs | 96 ++++++++++++++++++-------- tests/ref/layout/pagebreak-parity.png | Bin 0 -> 3897 bytes tests/typ/layout/pagebreak-parity.typ | 23 ++++++ 4 files changed, 112 insertions(+), 32 deletions(-) create mode 100644 tests/ref/layout/pagebreak-parity.png create mode 100644 tests/typ/layout/pagebreak-parity.typ diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index de7915251..d934c458e 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -453,8 +453,8 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { } else { shared }; - let page = PageElem::new(FlowElem::new(flow.to_vec()).pack()).pack(); - let stored = self.scratch.content.alloc(page); + let page = PageElem::new(FlowElem::new(flow.to_vec()).pack()); + let stored = self.scratch.content.alloc(page.pack()); self.accept(stored, styles)?; } Ok(()) @@ -467,17 +467,28 @@ struct DocBuilder<'a> { pages: StyleVecBuilder<'a, Content>, /// Whether to keep a following page even if it is empty. keep_next: bool, + /// Whether the next page should be cleared to an even or odd number. + clear_next: Option, } impl<'a> DocBuilder<'a> { fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool { if let Some(pagebreak) = content.to::() { self.keep_next = !pagebreak.weak(styles); + self.clear_next = pagebreak.to(styles); return true; } - if content.is::() { - self.pages.push(content.clone(), styles); + if let Some(page) = content.to::() { + let elem = if let Some(clear_to) = self.clear_next.take() { + let mut page = page.clone(); + page.push_clear_to(Some(clear_to)); + page.pack() + } else { + content.clone() + }; + + self.pages.push(elem, styles); self.keep_next = false; return true; } @@ -488,7 +499,11 @@ impl<'a> DocBuilder<'a> { impl Default for DocBuilder<'_> { fn default() -> Self { - Self { pages: StyleVecBuilder::new(), keep_next: true } + Self { + pages: StyleVecBuilder::new(), + keep_next: true, + clear_next: None, + } } } diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index a3f99d564..9789b4684 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -287,6 +287,11 @@ pub struct PageElem { /// will be created after the body has been typeset. #[required] pub body: Content, + + /// Whether the page should be aligned to an even or odd page. + /// Not part of the public API for now. + #[internal] + pub clear_to: Option, } impl PageElem { @@ -349,7 +354,13 @@ impl PageElem { regions.root = true; // Layout the child. - let mut fragment = child.layout(vt, styles, regions)?; + let mut frames = child.layout(vt, styles, regions)?.into_frames(); + + // Align the child to the pagebreak's parity. + if self.clear_to(styles).is_some_and(|p| !p.matches(number.get())) { + let size = area.map(Abs::is_finite).select(area, Size::zero()); + frames.insert(0, Frame::new(size)); + } let fill = self.fill(styles); let foreground = self.foreground(styles); @@ -375,7 +386,7 @@ impl PageElem { ); // Post-process pages. - for frame in fragment.iter_mut() { + for frame in frames.iter_mut() { tracing::info!("Layouting page #{number}"); // The padded width of the page's content without margins. @@ -446,34 +457,10 @@ impl PageElem { number = number.saturating_add(1); } - Ok(fragment) + Ok(Fragment::frames(frames)) } } -/// A manual page break. -/// -/// Must not be used inside any containers. -/// -/// ## Example { #example } -/// ```example -/// The next page contains -/// more details on compound theory. -/// #pagebreak() -/// -/// == Compound Theory -/// In 1984, the first ... -/// ``` -/// -/// Display: Page Break -/// Category: layout -#[element] -pub struct PagebreakElem { - /// If `{true}`, the page break is skipped if the current page is already - /// empty. - #[default(false)] - pub weak: bool, -} - /// Specification of the page's margins. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct Margin { @@ -641,6 +628,61 @@ cast! { v: Func => Self::Func(v), } +/// A manual page break. +/// +/// Must not be used inside any containers. +/// +/// ## Example { #example } +/// ```example +/// The next page contains +/// more details on compound theory. +/// #pagebreak() +/// +/// == Compound Theory +/// In 1984, the first ... +/// ``` +/// +/// Display: Page Break +/// Category: layout +#[element] +pub struct PagebreakElem { + /// If `{true}`, the page break is skipped if the current page is already + /// empty. + #[default(false)] + pub weak: bool, + + /// If given, ensures that the next page will be an even/odd page, with an + /// empty page in between if necessary. + /// + /// ```example + /// #set page(height: 30pt) + /// + /// First. + /// #pagebreak(to: "odd") + /// Third. + /// ``` + pub to: Option, +} + +/// Whether something should be even or odd. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] +pub enum Parity { + /// Next page will be an even page. + Even, + /// Next page will be an odd page. + Odd, +} + +impl Parity { + /// Whether the given number matches the parity. + fn matches(self, number: usize) -> bool { + match self { + Self::Even => number % 2 == 0, + Self::Odd => number % 2 == 1, + } + } +} + /// Specification of a paper. #[derive(Debug, Copy, Clone, Hash)] pub struct Paper { diff --git a/tests/ref/layout/pagebreak-parity.png b/tests/ref/layout/pagebreak-parity.png new file mode 100644 index 0000000000000000000000000000000000000000..af08bac113f00df8290f2e9d87d79302be72ed3b GIT binary patch literal 3897 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 X1Ma$;KehSoPi$;prvFyg;r_n>p$mwZ literal 0 HcmV?d00001 diff --git a/tests/typ/layout/pagebreak-parity.typ b/tests/typ/layout/pagebreak-parity.typ new file mode 100644 index 000000000..4d6ae9414 --- /dev/null +++ b/tests/typ/layout/pagebreak-parity.typ @@ -0,0 +1,23 @@ +// Test clearing to even or odd pages. + +--- +#set page(width: 80pt, height: 30pt) +First +#pagebreak(to: "odd") +Third +#pagebreak(to: "even") +Fourth +#pagebreak(to: "even") +Sixth +#pagebreak() +Seventh +#pagebreak(to: "odd") +#page[Nineth] + +--- +#set page(width: auto, height: auto) + +// Test with auto-sized page. +First +#pagebreak(to: "odd") +Third