How to dynamically label headers in outlines with a counter

Hi. There is no perfect solution that is short, because headings only have generally one counter. So you either hack together a proper heading with custom counter, or use a figure with custom kind. First provides a Document Outline, but second gives an option to uniquely label questions.

A crucial info that you don’t say is: will there be other headings and which heading will have numbering enabled. Also if there will be nested structure in the outline.

heading + counter hack:

#let question-counter = counter("question")
#let question() = question-counter.step() + [= Question<question>]

#show <question>: it => {
  show: block
  it.body
  [~]
  question-counter.display()
}

#show outline.entry: it => {
  if it.element.func() != heading or it.element.level != 1 { return it }
  if not it.element.has("label") or it.element.label != <question> { return it }
  let h = it.element
  let inner = {
    h.body
    [~]
    numbering("1", ..question-counter.at(h.location()))
    [ ]
    box(width: 1fr, it.fill)
    [ ]
    it.page()
  }
  it.indented(it.prefix(), inner)
}

#outline()

= Normal heading

#question()

#question()

#question()

figure (+ heading for ease of styling):

#let question(label: none) = {
  show figure: none
  [#figure(kind: "question", supplement: "Question")[]#label]
  let number = context counter(figure.where(kind: "question")).display()
  heading(outlined: false)[Question #number]
}

#outline(target: selector.or(heading, figure.where(kind: "question")))

= Normal heading

#question()

#question(label: <this-question>)

This is @this-question.

#question()

Or with a hack that fixes the over-long space:

#let question(label: none) = {
  show figure: none
  [#figure(kind: "question", supplement: "Question")[]#label]
  let number = context counter(figure.where(kind: "question")).display()
  heading(outlined: false)[Question #number]
}

#show outline.entry: it => {
  show h.where(amount: 0.5em.to-absolute()): none
  it
}

#outline(target: selector.or(heading, figure.where(kind: "question")))

= Normal heading

#question()

#question(label: <this-question>)

This is @this-question.

#question()

For some reason, the fix for https://typst-doc-cn.github.io/clreq/#too-wide-spacing-between-heading-numbering-and-title doesn’t work. But converting em to absolute units works.

Context

1 Like