Switch for roman/arabic page numbering in template file

I am creating a custom template for a book, and I would like the front matter (title page, outline, copyright, etc) to use Roman page numbers (i, ii, iii, …)

Then, the rest of the book (starting with chapter 1) should use Arabic page numbers (1, 2, 3…).

One way to achieve this is to create functions such as

#let frontmatter(body) = {
   set page(numbering: "i")
   body
}

and similarly for mainmatter(body). However, the user of the file would then have to write

#frontmatter[
   titlepage()
   #outline()
]

i.e. wrap everything inside [ ]. Similarly for the mainmatter(), all content has to be wrapped. Is this perhaps the Typst “way”?

As an alternative, I have done the following:

// template.typ
#let stat = state("p-num", "roman")

#let template(body) = {
  set page(
    numbering: (current, ..total) => context {
      if stat.get() == "roman" {
        numbering("i", current)
      } else {
        numbering("1", current)
      }
    }
  )
  body
}

#let frontmatter() = {
   stat.update("roman")
}

#let mainmatter() = {
   stat.update("arabic")
}

i.e. I use a state variable stat to keep track of the page numbering style, and use the functions frontmatter() and mainmatter() to update state. Note that, here, I do not need to wrap all the front matter content inside [ ].

This works the actual page numbers, but fails in the outline/table of contents which still displays Roman numbers, even for pages that has Arabic numberings.

For anyone wanting to test this out, here is a test file to check this. (This does not use the functions frontmatter() and mainmatter(), but the result is the same even if those are used.)

// test.typ
#import "mini.typ": *

#show: template

#outline()

#pagebreak()

#stat.update("arabic")
// #set page(numbering: "1")

= First chapter
#lorem(100)

= Second chapter
#lorem(100)

#pagebreak()

= Third chapter
#lorem(100)

So, my question is: should I stick to the function version (first one above) where I simply wrap the different matter parts in [ ], or should I try to find a fix for the problems with outline, and use the second version? In that case, does anyone know how to fix the outline so that it uses the actual page numbers for each page?

Being used to LaTeX I tend to like the state switching, and it also feels like the state variables are meant for problems like these.

That might be a matter of taste, but I vote for keeping it simple: no stateful page numbering, because you need to continue making the same change in outline and page references and so on.

What if you design the frontmatter and mainmatter (and backmatter?) functions so that they all can be used cumulatively (they overwrite each other’s settings), then it could look like this:

#show: frontmatter
= Preface

#show: mainmatter
= Heading
More stuff here

#show: backmatter
= Appendix
1 Like

This is probably the best option! I will just make sure I don’t do any irreversible stuff in any of the functions, but I do not think that is the case.

If anyone wonders, the solution with state can be used together with a outline, and one possible fix is the following:

  show outline.entry: it => {
    let loc = it.element.location()
    context {
      let state-at-element = stat.at(loc)
      let p-num = counter(page).at(loc).first()
      let formatted-page = {
        if state-at-element == "roman" {
          numbering("1", p-num)
        } else {
          numbering("1", p-num)
        }
      }
      link(loc, 
        it.indented(
          it.prefix(),
          [#it.body();#box(width: 1fr, it.fill);#formatted-page]
        )
      )
    }
  }

This checks the state stat at the position of each heading and use the correct formatting in the outline.

One thing that I forget to mention in my original post is that I want to do all of this without breaking the accessibility of the document (which is important), but I think all solutions mentioned in my post and @bluss post are valid in that aspect.

I think I will go with what you suggested @bluss. Thank you!

I have designed the bookly template using the logic described by @bluss and it works perfectly.

3 Likes