Is it possible to make level-1 headings to work as running heads?

Say you have a document like this one:

Page 1:

= A

Lorem ipsum dolor sit amet...

= B

Lorem ipsum dolor sit amet...

Page 2:

Lorem ipsum dolor sit amet...

= C

Lorem ipsum dolor sit amet...

== X

Lorem ipsum dolor sit amet...

== Y

Lorem ipsum dolor sit amet...

= D

Lorem ipsum dolor sit amet...

Page 3:

Lorem ipsum dolor sit amet...

= E

Lorem ipsum dolor sit amet...

= F

Lorem ipsum dolor sit amet...

Is it possible to put the first level-1 heading on each page to its header, so that the header of the 1st page will be β€œA”, the header of the 2nd page will be β€œC”, and the header of the 3rd page will be β€œE”?

Usually I would recommend the hydra package for something like that, but it seems that it doesn’t support using the first heading on a page. Instead, it only supports using the last heading of the previous page(s), or the last heading of the current page.

You can still implement what you want by running the query yourself without the package, which can then look something like this:

#set page(header: context {
  // Find first level-1 heading on this page (if any).
  let h = query(heading.where(level: 1).after(here()))
    .filter(h => h.location().page() == here().page())
    .at(0, default: none)

  if h != none {
    // Create number from counter value and numbering.
    let number = if h.numbering != none {
      numbering(h.numbering, ..counter(heading).at(h.location()))
    }

    // Display number and heading body (with space between).
    [#number #h.body]
  }
})
2 Likes

@Eric Thanks a lot. Works perfecly, though there is one minor issue:

Currently, if a page doesn’t have a level-1 heading, its header is blank. Whereas what I tried to achieve is that in such a case it should β€œborrow” the level-1 heading from the nearest previous page.

#set page(header: context {
  // Find first level-1 heading on this page (if any).
  let h = query(heading.where(level: 1).after(here()))
    .filter(h => h.location().page() == here().page())
    .at(0, default: none)

  if h != none {
    // Create number from counter value and numbering.
    let number = if h.numbering != none {
      numbering(h.numbering, ..counter(heading).at(h.location()))
    }

    // Display number and heading body (with space between).
    [#number #h.body]
  }
})

// --

= A

#lorem(2000)

= B

#lorem(500)

= C

#lorem(2000)

= D

#lorem(500)

Here:

  • the header of the pages 1-3 should be β€œA”
  • the header of 4th page should be "B
  • the header of the pages 5-6 should be β€œC”
  • the header of the pages 7-8 should be β€œD”

In that case, you could run another query if the first one didn’t yield any results:

let h = query(heading.where(level: 1).after(here()))
  .filter(h => h.location().page() == here().page())
  .at(0, default: {
    // Fall back to last previous heading.
    query(heading.where(level: 1).before(here())).at(-1, default: none)
  })
1 Like

@Eric , maybe you could you help once more?

Here is a code I use to put page numbers in the outer top corners: that is, in the top left corner of the left page and in the top right corner of the right page.

#set page(header: context {
  let page = counter(page).get().first()
  align(if calc.odd(page) { right } else { left })[#page]
})

Which gives:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β•₯────────────┐
β”‚          1 β”‚ β”‚ 2          β•‘          3 β”‚
β”‚            β”‚ β”‚            β•‘            β”‚
β”‚            β”‚ β”‚            β•‘            β”‚
β”‚            β”‚ β”‚            β•‘            β”‚
β”‚            β”‚ β”‚            β•‘            β”‚
β”‚            β”‚ β”‚            β•‘            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β•¨β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Could you show how to combine it with your solution, so that for left page the header will be something like

<Page number> #h(2em) <Level-1 heading>

and for the right page something like

<Level-1 heading> #h(2em) <Page number>

That is:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β•₯────────────┐
β”‚    Lorem 1 β”‚ β”‚ 2 Lorem    β•‘    Lorem 3 β”‚
β”‚            β”‚ β”‚            β•‘            β”‚
β”‚            β”‚ β”‚            β•‘            β”‚
β”‚            β”‚ β”‚            β•‘            β”‚
β”‚            β”‚ β”‚            β•‘            β”‚
β”‚            β”‚ β”‚            β•‘            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β•¨β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
1 Like

Sure, you can store the previous output in a variable and use a stack with either ltr or rtl direction to show it next to the page. To align to the left or right of the page, use a 1fr filler in the stack:

#set page(header: context {
  let h = query(...)

  let h = if h != none {
    ...
    [#number #h.body]
  }

  // Get logical page number.
  let numbering = if page.numbering != none { page.numbering } else { "1" }
  let page = counter(page).display(numbering)

  // Align right or left, based on physical page number.
  stack(
    dir: if calc.odd(here().page()) { rtl } else { ltr },
    [#page], 2em, h, 1fr
  )
})
3 Likes

That’s also what I want! Your question help me a lot! Maybe it should be marked as an example for the category of making a book.

1 Like