How can I turn this not converging document possibly showing a bug in 0.14 related to character-level justification and footnotes into an actionable MWE?

I have a two‑page MWE that doesn’t converge (for the search: warning: “layout did not converge within 5 attempts”). I suspect it’s specific to 0.14, possibly due to character‑level justification.

My theory: The first page must end EXACTLY with a footnote. Exactly in the literal sense, it’s not enough if it’s just the last word.

Change anything in this document that shifts the last footnote even slightly and the document converges.

You can swap letters with the same glyph width (e.g., b, p and d), but any change that moves the footnote makes it converge. This includes changing the page margin, justification limits or switching to numerical numbering.

I assume this is a bug, but it’s not really actionable as is and not isolated enough for a solid GitHub issue. Even after several hours of trying, I couldn’t narrow it down further, as it’s hard to hit exactly the right spot at the end of the page (this is also why some of my document specific stuff is still in the MWE… the my-par() usually does more).

Can someone please help to narrow it down further?

(Ideas for temporary fixes appreciated as well :smile: because currently you will hit this problem, if the document is long enough even if you “fix” it at one place, if a page reaches juuust the right length, it will break again. This leads to very hard‑to‑pinpoint warnings and broken footnotes, note that in the example both are “a”. And I’m not 100% certain it only happens with character-level justification.)

Here is the current MWE (works local or in the app, but local you need EB Garamond):

#set page(
  paper: "us-letter",
  flipped: true,
  margin: (y: 11.11%, left: 11.11%, right: 11.11% * 4),
  header: {
    counter(footnote).update(0)
  },
)

#set text(size: 12pt, font: "EB Garamond")
#set par(justify: true)
#set par(justification-limits: (
  spacing: (min: 100% * 2 / 3, max: 200%),
  tracking: (min: -0.005em, max: 0.02em),
))

#set footnote(numbering: "a")

#show heading.where(level: 4): it => {
  set text(size: 10pt)
  it.body
}

#let par-num(body) = {
  heading(level: 4, body + [. ])
}

#let my-par(id: none, body) = text({
  par-num(id)
  body
})

#lorem(20) #footnote[Footnote 1]

#box(
  height: 320pt,
)

This is some random text but it is exactly as long as it needs to be to lead to tssssss
#my-par(
  id: "3",
  [Btu rnitepe ad, nur his frienssss, but pat heoleh of rbe dnal, did rkenhea nnto tba porws of tpe sdfefrj, wpicp yk pakes yo tpz tehhroh Jimreskb. #footnote[Footnote 2]],
) #[*4.* #lorem(12)]

You can (mostly, depending on the kerning pair!) exchange the following glyphs in the gibberish above:\
n = u \
e = a \
r = t \
b = p = d \

I played a bit around and managed to minimize it quite a bit.
It’s not character‑level justification, and all version back to at least 0.10 are affected. The difference in width of the number 1 and 2 glyphs of the second footnote cause the issue.
The counter reset needs to be present and EB Garamond as the font is needed, with the default font I didn’t manage to trigger the bug.

#set page(
  paper: "a8",
  header: {
    counter(footnote).update(0)
  },
)
#set text(font: "EB Garamond")

#v(3.5cm)
#footnote[Footnote1]
Test test test test test test test test test test test test test test test test test test w 
#footnote[Footnote 2]

Enjoy reporting it :D

2 Likes

Team work makes the Typst work :smiling_face_with_tear: thanks!

Reported :raised_hands:

PS: If someone has an idea for a numbering pattern that fixes this for the time being, I’m all ears.

1 Like

For future reference: Footnotes at the end of the page can break and lead to documents not converging · Issue #7291 · typst/typst · GitHub.

By the way, Laurenz Mädje posted a workaround on GitHub.

#set footnote(numbering: n => text(number-width: "tabular", numbering("1", n)))