How to add spacing in front of page number to the outline that is also respected by entry body linebreaks?

I want to create an outline that has a bit of spacing between the fill and the page number. However for multiline entries in the outline I want to have the heading linebreak before that space. The following

#show outline.entry: it => {
  link(
    it.element.location(),
    it.indented(
      it.prefix(),
      it.body()
        + h(0.5cm) // spacing between heading and fill
        + box(width: 1fr, it.fill)
        + sym.wj // prevent number from ending up on its own line
        + box(width:1cm, align(end, it.page())), // spacing next to the page number (start should align even if page numbers have multiple digits)
    ),
  )
}

#outline()

= This is a heading
== This is also a heading
== This is another very long -- and I mean so long that it stretches more than one line -- second level heading

generates this
grafik

where the word “level” should already be in the newline.

Any suggestions on how to achieve this

Hello and welcome! You could wrap the body + fill inside a box:

#show outline.entry: it => {
  link(
    it.element.location(),
    it.indented(
      it.prefix(),
      box(
        width: 100% - 1cm,  // 1cm comes from width of the number box
        it.body()
          + h(0.5cm) // spacing between heading and fill
          + box(width: 1fr, it.fill)
      )
      + sym.wj // prevent number from ending up on its own line
      + box(width: 1cm, align(end, it.page())), // spacing next to the page number (start should align even if page numbers have multiple digits)
    ),
  )
}

This already comes quite close. But combined with numbering the numbers get aligned with the bottom line instead of the top line for some reason

#set heading(numbering: "1.1")
#show outline.entry: it => {
  let spacing = 1cm
  link(
    it.element.location(),
    it.indented(
      it.prefix(),
      box(
        width:100% - spacing,
        it.body()
        + box(width: 1fr, inset: (left: 0.5cm), it.fill)
      )
        + sym.wj
        + box(width: spacing, align(end, it.page())),
    ),
  )
}

grafik

FYI, I made the spacing into a variable to make it clearer and the horizontal space has been moved to the inset of the box containing the fill

I think I found a solution that seems to work.

#set heading(numbering: "1.1")

#let style-outline-inner(it, page-spacing: 1cm, left-spacing: 1.2em) = {
  box(
    baseline: 100% - 0.65em,
    box(
      width: 100% - page-spacing,
      it.body() + box(width: 1fr, inset: (left: left-spacing), it.fill),
    )
      + box(width: page-spacing, align(end, it.page())),
  )
}

#show outline.entry: it => {
  link(
    it.element.location(),
    it.indented(
      it.prefix(),
      style-outline-inner(it),
    )
  )
}

I am not sure though if there is an easier way or why exactly 0.65em seems to be aligned and whether that fails if there is e.g. a different line spacing. So I’ll leave the question open for a few days to see if someone else has an answer to that.

I usually place the prefix from inside the box containing the title:

#show outline.entry: it => {
  let spacing = 1cm
  link(
    it.element.location(),
    it.indented(
      // gap: 0.5em, // default gap
      hide(it.prefix()),
      {
        box(
          width: 100% - spacing,
          {
            box(width: 0pt, {
              set align(end)
              it.prefix()
              h(0.5em) // default gap
            })
            it.body()
            box(width: 1fr, inset: (left: 0.5cm), it.fill)
          }
        )
        sym.wj
        box(width: spacing, align(end, it.page()))
      }
    ),
  )
}

(at this point, the outline.entry.indented() function becomes a bit pointless, I prefer to indent by a fixed amount and put the prefix in a box of fixed width, similar to the page number)

This seems to work because 0.65 is close to the height of text/letters. This breaks when using other fonts with different measurements. What you want there is measure(sym.zws).height (this needs context)