I’ve seen multiple topics on how to achieve different page numbering for first page, frontmatter, and main content, but these all presume that you can add the appropriate commands at the appropriate places in the document flow.
Since I am using a Typst template (via Quarto), I had to figure out how to do this. I thought I’d share it with others, in case it is helpful.
Template requirements for the footer and page numbering:
No footer on first page
From page 2 till the page with “Introduction”: Roman numerals, starting with i.
From Introduction till the end (incl. backmatter): Hindu-Arabic numerals, starting with 1.
In page.typ (since I’m using Quarto’s division of “template partials”), I added the following footer:
#set page(
footer: context {
// find page with label "introduction".
let intro-page = locate(<introduction>).page()
let current-page = here().page()
set text(10pt)
// no footer on first page
if current-page == 1 {return none}
// reset page counter on the page before the Introduction
if current-page == (intro-page -1) [
#counter(page).update(0) // this needs to be 0, not 1
]
// roman numerals for frontmatter
if current-page < intro-page [
$short-title$ #h(1fr) #counter(page).display("i")
]
else [
$short-title$ #h(1fr) #counter(page).display("1")
]
}
)
That looks neat. For page numbers to use the same corresponding styles in the outline, I think you need to insert set page(numbering: "i") (and "1") in the right places in the document, those can’t be incorporated into this page footer scheme?
I had not included the frontmatter in the ToC, so I didn’t notice the problem until you pointed it out.
This revised version (written with help from Grok) fixed the mismatch between the page numbering and the ToC by putting numbering outside of the footer function.
#let intro-start-page = state("intro-start-page", none) // store final intro page after layout
#set page(
numbering: (n) => {
// locate gives the final layout page of <introduction>, not the current page
let intro-loc = locate(<introduction>)
intro-start-page.update(intro-loc.page()) // needed because early passes are unstable
let intro-page = intro-start-page.get()
let current-page = here().page() // page we are numbering right now
if intro-page == none or current-page < intro-page { numbering("i", n) }
else { numbering("1", n) }
},
footer: context {
let current-page = here().page()
let intro-page = locate(<introduction>).page()
// update(0) so next page becomes 1 (Typst increments before display)
if current-page == intro-page - 1 { block(counter(page).update(0)) }
if current-page == 1 { return none } // no footer on title page
set text(10pt)
align(left)[ $short-title$ #h(1fr) #counter(page).display() ]
},
)