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 000000000..af08bac11 Binary files /dev/null and b/tests/ref/layout/pagebreak-parity.png differ 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