Help needed in making layout converge within 5 attempts

I don’t know if it’s right to post this here, but I have written a template for the university I am currently attending, it is hosted here GitHub - eduardz1/ensimag-nificent-thesis: A Typst template for the INP school at UGA, Université Grenoble Alpes, this template uses glossy – Typst Universe as a glossarium, alexandria – Typst Universe to manage multiple bibliographies and a snippet I stole in a GitHub discussion to make the page numbering appear only on empty pages:

#set page(
    header: context {
      let page = here().page()
      let is-start-chapter = query(heading.where(level: 1))
        .map(it => it.location().page())
        .contains(page)
      if not state("content.switch", false).get() and not is-start-chapter {
        return
      }
      state("content.pages", (0,)).update(it => {
        it.push(page)
        return it
      })
    },
    footer: context {
      let has-content = state("content.pages", (0,))
        .get()
        .contains(here().page())
      if has-content {
        align(center, counter(page).display())
      }
    },
  )

The combination of the three causes the layout not to converge within 5 attempts, locally I have recompiled Typst with the limit lifted so I know there are no deadlocks, it’s just a lot of context but I not sure what to do about it. Thank you in advance!

1 Like

Layout not converging fast enough is always tricky

In your case, I would try if it helps to remove the header stuff – as far as I can tell could you check for headings and state("content.switch") in the footer, and get rid of state("content.pages")

In my experience having such “dependecy chains” of state/context is usually the problem (your content.pages state depends on content.switch, query.heading and here.page())

It might also be worth it to include a .before(here()) in your selector (after moving it to the footer), which would allow you to only consider the .last() heading found.

I had tried before to remove the header context but didn’t really succeed, could you show me exactly what you mean? I’m not 100% confident that I get it

This is what I mean:

#set page(
  footer: context {
    let chapters = query(selector(heading.where(level: 1)).before(here()))
    let is-start-chapter = chapters.len() > 0 and chapters.last().location().page() == here().page()
    let is-blank = state("blank-page", false).get()

    [start-chapter: #is-start-chapter | is-blank: #is-blank]
    if not is-blank {
      align(center, counter(page).display())
    }
  },
)
#show heading.where(level: 1, outlined: true): it => {
  pagebreak(weak: true)
  state("blank-page").update(true)
  pagebreak(to: "odd", weak: true)
  state("blank-page").update(false)
  it
}

= Heading 1
#lorem(40)

= Heading 2
#lorem(40)

#lorem(40)

(no idea if this fixes it, but it simplifies the states a bit)

1 Like

Oh I get it now, that makes sense, thank you! It doesn’t fix the problem completely, even without it it doesn’t converge in 5 iterations, but it certainly is a start

I found that for my use case everything related to chapters is not really needed anymore so it can just be

#show heading.where(level: 1, outlined: true): it => {
    pagebreak(weak: true)
    state("blank-page").update(true)
    pagebreak(to: "odd", weak: true)
    state("blank-page").update(false)
    it
}
#set page(footer: context {
    if not state("blank-page", false).get() {
      align(center, counter(page).display())
    }
})

I quoted your response to the Github discussion about it

2 Likes