How to always have a multiple of four as the number of pages

Hi,
I’m trying to add blank pages at the end of my document so that the final number of pages always is a multiple of four (I’m making a template to print booklets), and I have a weird issue.
I tried doing the following:

#let template(
  doc,
) = {
  set page(numbering: "1")

  doc

  context {
    let num = counter(page).get().first()
    while calc.rem(num, 4) != 0 {
      page()[]
      num += 1
    }
  }
}

#show: template

#lorem(1000)

It is not working and give weird results. But by trying different things in order to understand the issue, I realized that adding some content in the context before the loop makes it behave as expected (but with unwanted content added). For example:

 #let template(
  doc,
) = {
  set page(numbering: "1")

  doc

  context {
    [some content]
    let num = counter(page).get().first()
    while calc.rem(num, 4) != 0 {
      page()[]
      num += 1
    }
  }
}

#show: template

#lorem(1000)

I don’t understand the issue here, could someone try to explain it to me ? Am I doing this the wrong way ?
Thanks !

Hello,

Good question and I’m not sure why it wouldn’t work without the content. The web app sometimes paniced, sometimes it didn’t. What you can do is use states:


#let multiple-of = 4
#let rest-pages = state("rest-pages", 0)

#context {
  let num = here().position().page
  let rem = calc.rem(num, multiple-of)
  if rem != 0 {
    rest-pages.update(multiple-of - rem)
  }
}

#context[
  #for i in range(rest-pages.final()) [ // or .get() also works
    #pagebreak()
  ]
]

The issue is, that your provided code seems to panic with a could not converge after 5 attempts, meaning the document couldn’t be completed or “reach a stable state”, since probably new pages were added continuously with no sign of stopping.

I was able to solve it by separating the rest page calculations and the empty page insertion into their own #context. This way the amount of pages is calculated first, and then the pages are inserted.

It doesn’t look very nice and there are probably cleaner ways, but this works. Hope this helps.

I think the reason why this happens is because, for some reason, the context block gets placed at the top of the new page, if one gets created. The solution by @Electron_Wizard provides a good answer, but if you want a solution that avoids using states you can place some metadata in the document (which is not visible), and run your page number calculations relative to the location of said metadata, like so:

#let template(
  doc,
) = {
  set page(numbering: "1")

  doc
  
  [#metadata("document end")<end>]
  
  context {
    let num = counter(page).at(label("end")).first()
    for _ in range(4 - num) {
      page[]
    }
  }
}
1 Like

The location in a context block is associated with the first element inside it. This usually makes sense, because when doing something like

Hello #context rect[#here().position()]

you don’t want the position to be the one right after “Hello”, but instead the position of the rectangle (which is a block and therefore not on the same line).

In the case here, the first element of the context block is a new page, and the position of the new page is naturally on that page, thus the counter in the original post is off by one. You could probably solve this simply by subtracting one from num initially so that the off-by-one error is compensated, but the other solutions given should also do the trick.


Two notes on the solution given by @aarnent:

  • The metadata isn’t actually necessary, as it’s enough to just label an empty content block like [#[] <end>]

  • The for loop only works like that for documents with up to 4 pages. To fix that, either use a while loop as in the original post, or do some modulus calculation as in

    for _ in range(calc.rem-euclid(4 - num, 4)) { .. }
    
2 Likes

Thanks for all those replies ! I ended up with :

  [#[] <end>]
  context {
    let num = counter(page).at(label("end")).first()
    for _ in range(calc.rem-euclid(4 - num, 4)) {
      page[]
    }
  }

Which seems to work. :blush:
I don’t know which answer to mark as solution, so I’ll mark the last one.
Thanks again to everyone !

1 Like