How to correctly display page numbering in a custom footer (with and without total number of pages)?

I’m trying to add a variable to my template where the user can set a custom page numbering schema (Customize Page Numbering Format · Issue #18 · DannySeidel/typst-dhbw-template · GitHub). Due to the template using multiple different numbering styles, I want to enable the user to customize all sections.

I have a preface part that uses Roman numbering like this I. After that, the template shows the main part, which uses Arabic numbering like this 1 / 1. Lastly, the template uses Latin letters for the appendix section like this a. Because the main section provides the total number of pages, I had to define a custom footer because the page counter does not provide the number of pages at a specific point in the document.

Currently, I create the footer as shown below:

  set page(footer: context align(
    numbering-alignment,
    numbering(
     "1 / 1",
      ..counter(page).get(),
      ..counter(page).at(<numbering-main-end>),
    ),
  ))

By using counter(page).at(<numbering-main-end>), I get the number of pages at this label. Because the user should be able to set the style for all three sections, I have now created labels at the end of all three sections. The problem is that if the user defines a style that does not contain the total number of pages, I still pass in the counter, and the number is still displayed. If I leave out the total count and the user defines a style with the total number of pages displayed, the total number of pages is shown as 1, as the data is missing.

How would I solve this problem? Maybe I could conditionally pass in the total number of pages only if it is needed? But how?

Typst internally already handles this case for the default footer (or header) by checking if the given numbering pattern has two or more counting symbols. You can apply the same logic manually in your footer:

#set page(footer: context {
  // Handle the case when page.numbering is not set by
  // falling back to the default "1" numbering pattern.
  let page-numbering = page.numbering
  if page-numbering == none { page-numbering = "1" }

  let both = type(page-numbering) == function or {
    page-numbering.clusters().filter(c => c in (
      // Counting symbols: https://typst.app/docs/reference/model/numbering
      "1", "a", "A", "i", "I", "α", "Α", "*",
      "א", "一", "壹", "あ", "い", "ア", "イ", "ㄱ",
      "가", "\u{0661}", "\u{06F1}", "\u{0967}",
      "\u{09E7}", "\u{0995}", "①", "⓵"
    )).len() >= 2
  }

  numbering(
    page-numbering,
    ..counter(page).get(),
    ..if both { counter(page).at(<numbering-main-end>) }
  )
})

Note that I have already included the counting symbols that are not released yet (as of v0.12.0) but are part of current development builds. They are not shown in the documentation yet, but can be retrieved from the source code.