diff --git a/crates/typst-library/src/model/heading.rs b/crates/typst-library/src/model/heading.rs index f5e7f4f37..09984fe3c 100644 --- a/crates/typst-library/src/model/heading.rs +++ b/crates/typst-library/src/model/heading.rs @@ -1,5 +1,6 @@ use std::num::NonZeroUsize; +use ecow::EcoString; use typst_utils::NonZeroExt; use crate::diag::SourceResult; @@ -103,6 +104,16 @@ pub struct HeadingElem { /// ``` pub numbering: Option, + /// Plain-text displayed numbering. + /// + /// This field is only necessary for creating PDF bookmarks. + /// The problem is that in the export stage, we don't have access to the World/Engine etc. + /// which is needed to resolve numbers and numbering patterns (or functions) into a concrete string/content. + /// Therefore, we have to save the result before the export stage. + #[internal] + #[synthesized] + pub numbering_displayed: EcoString, + /// A supplement for the heading. /// /// For references to headings, this is added before the referenced number. @@ -202,10 +213,23 @@ impl Synthesize for Packed { } }; + let numbering_displayed = if let (Some(numbering), Some(location)) = + (self.numbering.get_ref(styles).as_ref(), self.location()) + { + Some( + self.counter() + .display_at_loc(engine, location, styles, numbering)? + .plain_text(), + ) + } else { + None + }; + let elem = self.as_mut(); elem.level.set(Smart::Custom(elem.resolve_level(styles))); elem.supplement .set(Smart::Custom(Some(Supplement::Content(supplement)))); + elem.numbering_displayed = numbering_displayed; Ok(()) } } diff --git a/crates/typst-pdf/src/outline.rs b/crates/typst-pdf/src/outline.rs index 7228f0f32..152e8bd5d 100644 --- a/crates/typst-pdf/src/outline.rs +++ b/crates/typst-pdf/src/outline.rs @@ -128,6 +128,13 @@ impl<'a> HeadingNode<'a> { let pos = gc.document.introspector.position(loc); let page_index = pos.page.get() - 1; + // Prepend the numbering to title if it exists + let title = match &self.element.numbering_displayed { + // The space should be a `h(0.3em)`, but only plain-texts are supported here. + Some(num) => format!("{num} {title}"), + None => title, + }; + if let Some(index) = gc.page_index_converter.pdf_page_index(page_index) { let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); let dest = XyzDestination::new(