with the current usage of show rule, it’s like a letter is contained inside the previous letter.
imo, it looks better to wrap each letter into a letter call
#lettter(to: [Someone])[
First letter content
]
#lettter(to: [Someone else])[
Second letter content
]
By doing this, the style on page is reset after the last page of each letter, therefore a the following letter introduce a blank page if the case of odd number.
Also, the letter function can now wrap the body into something else, adding an outro or padding the letter content for instance
// small size for easier testing
#set page(width: 120pt, height: 100pt)
//https://forum.typst.app/t/how-to-reset-the-page-counter-after-every-section/1924/10
#let reset = <__reset>
#let subtotal() = {
let loc = here()
let list = query(selector(reset).after(loc))
if list.len() > 0 {
counter(page).at(list.first().location()).first() - 1
} else {
counter(page).final().first()
}
}
#let page-numbers = context {
let subtotal = subtotal()
//Dont show page-numbers if there is only one page in the letter
if subtotal == 1 {
none
} else {
numbering("1 / 1", ..counter(page).get(), subtotal)
}
}
#let letter(
to: [],
from: [Anonymous],
body,
) = {
pagebreak(weak: true)
[#metadata(none)#reset]
pagebreak(weak: true, to: "odd")
counter(page).update(1)
set page(
footer: context {
align(center, page-numbers)
},
)
// We can now add a now add an intro and outro
par(strong(to))
body
align(right)[--- #from]
}
#letter(to: [To someone], from: [Ben])[
#lorem(30)
]
// Letter 1
#letter(to: [To someone], lorem(10))
// Letter 2
#letter(to: [To another one], lorem(40))
#letter(to: [To the world], lorem(30))
In the case you still want to use show rules (because it’s easier too use), I think I make it work as follow (it look’s like a bit overengineered tho)
#set page(width: 120pt, height: 100pt)
#set heading(numbering: "1.1")
#let reset = <__reset>
#let last = <__last>
// Return the #last location for the current letter
#let find-last(location) = {
let before = query(selector(last).before(location)).last(default: none)
let reset = query(selector(reset).after(location)).first(default: none)
let after = query(selector(last).after(location)).first(default: none)
if reset == none {
// last letter
none
} else if after == none {
// last (empty) page of second to last letter
before.location()
} else if after.location().page() > reset.location().page() {
// last (empty) page (page were #last is in)
before.location()
} else {
// any other page
after.location()
}
}
// Global set page rule
#set page(
footer: context {
let last-location = find-last(here())
let letter-page-count = if last-location != none {
counter(page).at(last-location).first() - 1
} else {
counter(page).final().first()
}
let current-page = counter(page).get().first()
if letter-page-count > 1 and current-page <= letter-page-count {
align(center, [#current-page / #letter-page-count])
}
},
)
#let letter-footer = {
// Place #last label on the page following the end of the letter
pagebreak(weak: true)
[#metadata(none)#last]
// Place #reset label on the first page of the next letter
pagebreak(weak: true, to: "odd")
[#metadata(none)#reset]
counter(page).update(1)
}
#let letter(
to: [Someone],
body,
) = {
// Place footer of the previous letter
letter-footer
heading(level:2)[To #to]
body
// Note, if you use show rules, putting outro there will
// put all of them on the last page of the document
}
#counter(heading).step()
// Wrap single letter
#letter(text(oklch(60%,100%, 0deg,100%), lorem(10)))
#letter(text(oklch(60%,100%,100deg,100%), lorem(40)))
#letter(text(oklch(60%,100%,180deg,100%), lorem(30)))
#letter(text(oklch(60%,100%,240deg,100%), lorem(40)))
#counter(heading).step()
// Or wrap with the show rule
#show: letter
#text(oklch(60%,100%, 0deg,100%), lorem(10))
#show: letter
#text(oklch(60%,100%,100deg,100%), lorem(40))
#show: letter
#text(oklch(60%,100%,180deg,100%), lorem(30))
#show: letter
#text(oklch(60%,100%,240deg,100%), lorem(30))