diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs index 8e73c3b3a..4ef90753f 100644 --- a/crates/typst-library/src/layout/page.rs +++ b/crates/typst-library/src/layout/page.rs @@ -177,7 +177,8 @@ pub struct PageElem { /// How to [number]($func/numbering) the pages. /// - /// If an explicit `footer` is given, the numbering is ignored. + /// If an explicit `footer` (or `header` for top-aligned numbering) is + /// given, the numbering is ignored. /// /// ```example /// #set page( @@ -192,6 +193,11 @@ pub struct PageElem { /// The alignment of the page numbering. /// + /// If the vertical component is `top`, the numbering is placed into the + /// header and if it is `bottom`, it is placed in the footer. Horizon + /// alignment is forbidden. If an explicit matching `header` or `footer` is + /// given, the numbering is ignored. + /// /// ```example /// #set page( /// margin: (top: 16pt, bottom: 24pt), @@ -202,6 +208,15 @@ pub struct PageElem { /// #lorem(30) /// ``` #[default(Align::Center.into())] + #[parse({ + let spanned: Option>> = args.named("number-align")?; + if let Some(Spanned { v, span }) = spanned { + if matches!(v.y, Some(GenAlign::Specific(Align::Horizon))) { + bail!(span, "page number cannot be `horizon`-aligned"); + } + } + spanned.map(|s| s.v) + })] pub number_align: Axes>, /// The page's header. Fills the top margin of each page. @@ -372,25 +387,40 @@ impl PageElem { let fill = self.fill(styles); let foreground = self.foreground(styles); let background = self.background(styles); - let header = self.header(styles); let header_ascent = self.header_ascent(styles); - let footer = self.footer(styles).or_else(|| { - self.numbering(styles).map(|numbering| { - let both = match &numbering { - Numbering::Pattern(pattern) => pattern.pieces() >= 2, - Numbering::Func(_) => true, - }; - Counter::new(CounterKey::Page) - .display(Some(numbering), both) - .aligned(self.number_align(styles)) - }) - }); let footer_descent = self.footer_descent(styles); + let numbering = self.numbering(styles); + let number_align = self.number_align(styles); + let mut header = self.header(styles); + let mut footer = self.footer(styles); - let numbering_meta = FrameItem::Meta( - Meta::PageNumbering(self.numbering(styles).into_value()), - Size::zero(), - ); + // Construct the numbering (for header or footer). + let numbering_marginal = numbering.clone().map(|numbering| { + let both = match &numbering { + Numbering::Pattern(pattern) => pattern.pieces() >= 2, + Numbering::Func(_) => true, + }; + + let mut counter = + Counter::new(CounterKey::Page).display(Some(numbering), both); + + // We interpret the Y alignment as selecting header or footer + // and then ignore it for aligning the actual number. + if let Some(x) = number_align.x { + counter = counter.aligned(Axes::with_x(Some(x))); + } + + counter + }); + + if matches!(number_align.y, Some(GenAlign::Specific(Align::Top))) { + header = header.or(numbering_marginal); + } else { + footer = footer.or(numbering_marginal); + } + + let numbering_meta = + FrameItem::Meta(Meta::PageNumbering(numbering.into_value()), Size::zero()); // Post-process pages. for frame in frames.iter_mut() { diff --git a/docs/guides/page-setup.md b/docs/guides/page-setup.md index 8efcb2cdf..b396a383b 100644 --- a/docs/guides/page-setup.md +++ b/docs/guides/page-setup.md @@ -34,7 +34,7 @@ document or in your template. "iso-b7", header: rect[Header], footer: rect[Footer], - number-align: top + center, + number-align: center, ) #rect(fill: rgb("#565565")) diff --git a/tests/ref/layout/page-number-align.png b/tests/ref/layout/page-number-align.png new file mode 100644 index 000000000..b05ca4545 Binary files /dev/null and b/tests/ref/layout/page-number-align.png differ diff --git a/tests/typ/layout/page-number-align.typ b/tests/typ/layout/page-number-align.typ new file mode 100644 index 000000000..0e9b2bc94 --- /dev/null +++ b/tests/typ/layout/page-number-align.typ @@ -0,0 +1,25 @@ +// Test page number alignment. + +--- +#set page( + height: 100pt, + margin: 30pt, + numbering: "(1)", + number-align: top + right, +) + +#block(width: 100%, height: 100%, fill: aqua.lighten(50%)) + +--- +#set page( + height: 100pt, + margin: 30pt, + numbering: "[1]", + number-align: bottom + left, +) + +#block(width: 100%, height: 100%, fill: aqua.lighten(50%)) + +--- +// Error: 25-39 page number cannot be `horizon`-aligned +#set page(number-align: left + horizon)