How to display chapter numbers in PDF bookmarks


image
The generated outline is chapter numbered in the table of contents. Why is there no chapter number in the PDF bookmark. I tried a lot of solutions, but It didn’t work. I customize the style of level 1 heading, but I think that don’t affect.
show heading.where(level:1) : it => {

let titleNumber = counter(heading).at(it.location()).first()
let titleNumberStr = ""
if titleNumber > 0{titleNumberStr = str(titleNumber)}
let newTitle = titleNumberStr + it.body
pagebreak(weak: true)
stack(
dir:ttb,
bg-image,
place(bottom + right,
    dx: -0.5em,  
    dy: -0.5em, 
    text(fill: white, weight: "bold",newTitle)
    )
)

}

Hi, just as information, Typst does not natively support this yet, it’s tracked in this issue: https://github.com/typst/typst/issues/2416 (For people interested in this: please add a :+1: reaction on the issue, comments there are not necessary)

With that said, maybe there are workarounds.

1 Like

I come up with this workaround with help from OrangeX4 and others. Are there any edge cases I might be missing? If not, I’ll post it to #2416.

Update: Done.

Proof of concept

Displaying a counter requires a context. Therefore, it will not appear in bookmarks (Contextual content doesn't appear in PDF document title and bookmarks/outline · Issue #3424 · typst/typst · GitHub).

However, if the whole heading is put in a big context, then it will.

#set heading(numbering: "1.1")

= This counter is not included in the bookmark: #context counter(heading).display()

#context [
  = But this one is included: #counter(heading).display()
]

图片

Real workaround

// Remove original headings from bookmarks. We will add new ones later.
#set heading(bookmarked: false)

#show heading: it => if it.numbering == none {
  // This heading has been processed. Keep it untouched.
  it
} else {
  let (numbering, body, ..args) = it.fields()

  // Render the numbering manually
  let numbered-body = block({
    counter(heading).display(numbering)

    h(0.3em) // Space in the real document
    box(width: 0em, hide(" ")) // Space in the bookmark
    // You can remove the above spaces if you meet https://github.com/typst/typst/issues/5778.

    body
  })

  // Revert non-idempotent show rules.
  // https://github.com/typst/typst/blob/22a57fcf5c52cc0e961368a6d6fb7e9a82312b48/crates/typst-library/src/model/heading.rs#L309
  set text(size: 1em / 1.4) if it.depth == 1
  set text(size: 1em / 1.2) if it.depth == 2

  // Add our bookmarked heading
  heading(..args, bookmarked: true, numbering: none, numbered-body)
}


#set heading(numbering: "1.1")

= A

== B

= C

#counter(heading).update(0)
= D

2 Likes

I think that’s a smart approach.

  • A bug fix: we need to remove the label from fields, then references to labelled headings will work
  • I explored a derived solution that creates parallel heading elements, visible ones and hidden bookmarked ones (idea from Andrew’s comment here: Different heading text for outline/bookmark and displaying · Issue #1889 · typst/typst · GitHub). Relying on a show: none heading to be part of the bookmarks maybe still feels like exploiting a quirk that is likely to be fixed sooner or later… I don’t know which way to prefer, it’s a matter of style or judgment.
Code, yours but lightly modified
// Remove original headings from bookmarks. We will add new ones later.
#set heading(bookmarked: false)

#outline()

#show heading: it => if it.numbering == none {
  // This heading has been processed. Keep it untouched.
  it
} else {
  let (numbering, body, ..args) = it.fields()
  let _ = args.remove("label", default: none)

  // Render the numbering manually
  let numbered-body = block({
    counter(heading).display(numbering)
    [ ] // space in the bookmark
    body
  })

  // regular heading
  it

  // Add our bookmarked, hidden heading
  show heading: none
  heading(..args, outlined: false, bookmarked: true, numbering: none, numbered-body)
}


#set heading(numbering: "1.1")

= A

== B <section>

= C

See section @section

#pagebreak()

#counter(heading).update(0)
= D
1 Like