typst

Show current heading number and body in page header


I'm trying to show the current heading number and body in the page header, for example:

2.1.6 Some subsection title

I've played around endlessly with combinations of selector(), query() and counter() to absolutely no avail.

For example:

#set page(
  header: context {
    if counter(page).get().first() > 1 [ // skip on first page
      #query(selector(heading.where(level: 1)).before(here())).first().depth.
      #query(selector(heading.where(level: 2)).before(here())).first().depth.
      #query(selector(heading.where(level: 3)).before(here())).first().depth
      #query(selector(heading.where(level: 3)).before(here())).first().body // Name of the current section of level 3

      #counter(heading.where(level: 3)).get().first()
    ]
  },
)

But this only returns 1.2.3 Title of first subsection. I cannot figure out how to make it actually show the correct headings.

I feel like this should be very simple, but somehow I'm finding it extremely convoluted.


Solution

  • The trick is that the heading element will query the heading counter for its complete depth, so you'll need to do that too. You can use a selector like the ones in your question in the counter constructor.

    #context {
      let selector = selector(heading).before(here())
      let level = counter(selector)
      level.display()
    }
    

    You can render the counter's content using its display method. You can pass a numbering pattern to display to format this. If you now want to use the headings' numbering style here without hardcoding it, you can query the heading itself using the same selector:

    #context {
      let selector = selector(heading).before(here())
      let level = counter(selector)
    
      // This will return an array with all headings
      // before the current position, we only need the
      // last one, though.
      let headings = query(selector)
    
      if headings.len() == 0 {
        return
      }
    
      let heading = headings.last()
    
      level.display(heading.numbering)
    }
    

    Put together with the heading's contents in a header, a snippet you may want to use may look like this:

    #set page(header: context {
      let selector = selector(heading).before(here())
      let level = counter(selector)
      let headings = query(selector)
    
      if headings.len() == 0 {
        return
      }
    
      let heading = headings.last()
    
      level.display(heading.numbering)
      h(1em)
      heading.body
    })
    
    #set heading(numbering: "1.")