From 3bc1280668aa8b471dce92c327273a50cbe7a83f Mon Sep 17 00:00:00 2001 From: "Y.D.X. (Gitpod)" <73375426+YDX-2147483647@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:18:06 +0000 Subject: [PATCH 1/2] Include numbering in PDF bookmark Resolves #2416 --- crates/typst-library/src/model/heading.rs | 24 +++++++++++++++++++++++ crates/typst-pdf/src/outline.rs | 7 +++++++ 2 files changed, 31 insertions(+) diff --git a/crates/typst-library/src/model/heading.rs b/crates/typst-library/src/model/heading.rs index 0f2a1d338..dbc2a5e36 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 f73822c4b..2031b1d83 100644 --- a/crates/typst-pdf/src/outline.rs +++ b/crates/typst-pdf/src/outline.rs @@ -129,6 +129,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(ref 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( From 3a96fe70d885c99173a1cd4a67ac95cfcb7f4373 Mon Sep 17 00:00:00 2001 From: "Y.D.X." <73375426+YDX-2147483647@users.noreply.github.com> Date: Tue, 22 Jul 2025 22:12:42 +0800 Subject: [PATCH 2/2] Migrate to Rust 2024 Follows #6637 --- crates/typst-pdf/src/outline.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/typst-pdf/src/outline.rs b/crates/typst-pdf/src/outline.rs index d8f25af62..152e8bd5d 100644 --- a/crates/typst-pdf/src/outline.rs +++ b/crates/typst-pdf/src/outline.rs @@ -131,7 +131,7 @@ impl<'a> HeadingNode<'a> { // 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(ref num) => format!("{num} {title}"), + Some(num) => format!("{num} {title}"), None => title, };