How to use `set page(..)` without adding an unwanted pagebreak?

I am working on a template for a thesis in book style, meaning the chapters (headings level 1) always start on an odd page and also that the page numbering is located in the header. I also need to not number/have any header/footer for the empty pages.

My problem comes after the outline page as I am changing the page numbering from lower case roman to Arabic numbering using a set page(...) statement which automatically adds a page break. It is also important to note that I detect if a page is empty using labels added in a custom “detectable” page break function. Since the page break added by the set page(...) statement is not a detectable one, the page is numbered and has a header even if it is empty.

Here is what my code looks like :

// Footer formatting function
#let footer-numbering(format: "1", book: true) = context{
  // If empty page no footer
  if is-page-empty() {
    return
  }
  // If in book style, footer numbering only on first page of chapter
  if book {
    let i = here().page()
    if query(heading.where(level: 1)).any(it => it.location().page() == i) {
      align(center, numbering(format, counter(page).get().at(0)))
    }
  } else {
    // Numbering in footer on all pages otherwise
    align(center, numbering(format, counter(page).get().at(0)))
  }
}

// First heading level = chapters
show heading.where(level: 1): it => {
  detectable-pagebreak()
  set text(font: "IBM Plex Sans", weight: "medium", size: 20pt)
  if it.body in special-chapters {
    block(spacing: 1.5em, upper(it))
  } else {
    stack(
      dir: ttb,
      spacing: 15pt,
      align(right, text(size: 40pt, counter(heading).display())),
      line(length: 100%, stroke: .5pt),
      upper(it.body),
      line(length: 100%, stroke: .5pt)
     )
    v(2em)
  }
}

set page(footer: footer-numbering(format: "i", book: book))

// outline code is here

set page(footer: footer-numbering(book: book))
counter(page).update(1)

// start of first chapter

I am aware of this post on a similar issue but I still cannot find a solution.

Could you post a minimal but complete example that shows what you have currently? I cannot run your code as it is, and it’s also a bit hard to guess what you want with the book style parameter and the comment mentioning numbering on the first page of each chapter. Maybe it’s irrelevant to your question? Then a simple code example without all of this would help.

Sorry for this confusion, my code is pretty long so I tried to only give the relevant parts. Here is a simpler example :

// Check if a page is empty
#let is-page-empty() = {
  let page-num = here().page()
  query(<empty-page-start>)
    .zip(query(<empty-page-end>))
    .any(((start, end)) => {
      (start.location().page() < page-num
        and page-num < end.location().page())
    })
}

// Pagebreaks detectable by is-page-empty
#let detectable-pagebreak(to: "odd") = {
  [#metadata(none) <empty-page-start>]
  pagebreak(to: to)
  [#metadata(none) <empty-page-end>]
}

// Footer formatting function
#let footer-numbering(format: "1") = context{
  // If empty page no footer
  if is-page-empty() {
    return
  }
  align(center, numbering(format, counter(page).get().at(0)))
}

// First level headings = chapters
#show heading.where(level: 1): it => {
  set text(font: "IBM Plex Sans", weight: "medium", size: 20pt)
  if it.body == [Table of Contents] {
    block(spacing: 1.5em, upper(it))
  } else {
    detectable-pagebreak()
    stack(
      dir: ttb,
      spacing: 15pt,
      align(right, text(size: 40pt, counter(heading).display())),
      line(length: 100%, stroke: .5pt),
      upper(it.body),
      line(length: 100%, stroke: .5pt)
    )
    v(2em)
  }
}

#set page(footer: footer-numbering(format: "i"))

#outline(title: [Table of Contents])

#set page(footer: footer-numbering())
#counter(page).update(1)

= First Chapter

The “problem” I have is that following the rules of my university, I should not have numbering on the second page in this example since it is empty. I know this happens because of the set page(...) statements inserting page breaks that cannot be detected by the is-page-empty() function.

Thanks, it’s now clearer to me. So you have code that tests if the current page comes after the start and before the end of a page break. This happens when pagebreak(to: "odd") starts two new pages: the second page will pass this test, and that’s the blank page you want to detect. The problem is when you have set page(...) just before this page break: this starts a new page A, then the pagebreak(to: "odd") starts another (single) page B. So page A is blank but doesn’t pass the test. (It seems you figured that out already, I’m just writing it out for the record.)

What I would do is create page A myself without footer when I know that there will be a page B:

#page(footer: none, none)
#set page(footer: footer-numbering())
#counter(page).update(1)

= First Chapter

I tried doing this a bit less manual by checking if two pages are going to be created, with context if calc.odd(here().page()) { page(footer: none, none) }, but with that code the layout fails to converge.

Also linking a related GitHub issue.

It seems the following works to detect automatically if a blank page will be issued, without convergence problems:

#outline(title: [Table of Contents])
#metadata(none)<outline-end>

#context if calc.odd(locate(<outline-end>).page()) {
  page(footer: none, none)
}
#set page(footer: footer-numbering())
#counter(page).update(1)

= First Chapter

Simple but effective, thanks!

1 Like