How to increase the spacing between the number and the title of a heading?

I say it is great :face_holding_back_tears: your approach is way more elegant as it doesn’t make it necessary to reassemble the links. Thank you very much!

Maybe you had another version of your code that made the first level bold, because in the pasted example, that doesn’t seem to be the case?

So this is what I ended up with:

// style outline
#show outline.entry: entry => {
  if entry.element.func() == heading {
    // because we modify entries by replacing them with new ones, we need to recognize them to avoid endless recursion
    if entry.at("label", default: none) == <modified-outline-entry> {
      entry
    } else {
      // we destructure entry, change fields and then reassemble the entry
      let fields = entry.fields()
      let he = fields.element
      let number = if he.numbering != none {
        numbering(he.numbering, ..counter(heading).at(he.location()))
      }
      let prefix = if number != none [ #number #h(0.8em) ]
      fields.body = [ #prefix #he.body ]
      let newe = outline.entry(..fields.values())
      if entry.level == 1 {
        [ *#newe <modified-outline-entry>* ]  
      }
      else if entry.level == 3 {
        [ #h(0.6em) #newe <modified-outline-entry> ]
      }
      else {
        [ #newe <modified-outline-entry> ]  
      }
      v(-0.4em, weak: true)
    }
  } else {
    entry
  }
}

Basically just adding/changing this part:

      if entry.level == 1 {
        [ *#newe <modified-outline-entry>* ]  
      }
      else if entry.level == 3 {
        [ #h(0.6em) #newe <modified-outline-entry> ]
      }
      else {
        [ #newe <modified-outline-entry> ]  
      }
      v(-0.4em, weak: true)

To:

  • Make level 1 bold
  • Correct the level 3 indent (this wasn’t in the original question/case it come up once I added a level 3 headline in my paper :sweat_smile:)
  • Add vertical spacing (still in the weird negative way, but hey
 it works)

Overall this feels like a lot of hassle to style outlines currently, especially since even the outrageous package suffers from some of the problems (especially the unwanted line break if a space is added between prefix-number and body).

But I think your solution is as good as currently possibly. So everybody landing here, please give issues like the on of PgBiel Add outline spacing/gutter · Issue #1424 · typst/typst · GitHub some thumbs up :up::smiling_face:

The result:

Some further thoughts on your last code snippet:

A)
I prefer to change the weight or add a strong function. This makes it more obvious what is different in the two cases:

#{
      let w = if entry.level == 1 { "bold" } else { "regular" }
      text(weight: w)[ #newe <modified-outline-entry> ] 
}

But this is probably really just preference.

B)
I saw that you added another case for level == 3 with even more indent. Maybe you should then use a separate function to control the indent:

#let indent_func = i => {
  (0em, 1.5em, 3.6em).at(i)
}
#outline(indent: indent_func)

This again separates the different logical units.

Output

1 Like

Wow, thanks again, great additions.

But this is probably really just preference.

No, no, I actually asked about exactly that case above. So how it could be improved/reduced the repetitiveness. So perfect, learned something, I wondered a couple of times how others solve these cases.

#let indent_func = i => {
  (0em, 1.5em, 3.6em).at(i)
}
#outline(indent: indent_func)

Black magic. How does that even work :open_mouth: i
 no loop, no count up, no nothing?

(Now our discussion is not about the original topic anymore xD)

My intuition told me that the devs probably added the option to provide a function for the outline indent 
 and indeed they did: Outline Function – Typst Documentation

I think the part in the documentation about the function is very well written. You can provide a function that takes one number as argument and returns a relative length or even content. (I think the documentation is mostly very worth to read and often provides answers. If you are used to this.)

So i defined the function indent_func. In it I create an array with 3 elements and return the element at position i, which is the level of the entry.

All three following code snippets should be equivalent to the above:

#let arr = (0em, 1.5em, 3.6em)
#let func(i) = arr.at(i)
#outline(indent: func)

and

#outline(indent: i => (0em, 1.5em, 3.6em).at(i))

I like this the most:

#let indent_func(i) = (0em, 1.5em, 3.6em).at(i)
#outline(indent: indent_func)

(That being said: my original function is somewhat ugly as it states an anonymous function which is then given an explicit name.)

If you are not familiar with the concepts used here, you might want to read about:

1 Like

(Now our discussion is not about the original topic anymore xD)

Well, it explains how the solution works :blush: thanks for taking the time to explain!

And for anyone landing here because you want the number of your outline indented/add space before the numbering, make the level 1 headings bold and horizontally align the levels in the outline. Here is the refactored code you can copy and paste:

(Adjust em-values as needed)

#show outline.entry: entry => {
  if entry.element.func() == heading {
    // because we modify entries by replacing them with new ones, we need to recognize them to avoid endless recursion
    if entry.at("label", default: none) == <modified-outline-entry> {
      entry
    } else {
      // we destructure entry, change fields and then reassemble the entry
      let fields = entry.fields()
      let he = fields.element
      let number = if he.numbering != none {
        numbering(he.numbering, ..counter(heading).at(he.location()))
      }
      let prefix = if number != none [ #number #h(0.5em) ]
      fields.body = [ #prefix #he.body ]
      let newe = outline.entry(..fields.values())
      let w = if entry.level == 1 {
        "bold"
      } else {
        "regular"
      }
      text(weight: w)[ #newe <modified-outline-entry> ]
      v(-0.4em, weak: true)
    }
  } else {
    entry
  }
}

#let indent_level(i) = (0em, 1.575em, 4em).at(i)
#outline(indent: indent_level)

PS: @PgBiel since this topic evolved from numbering and adjusting the spacing of headlines to also control said spacing in the outline, but provides solutions for both now, maybe it would make sense to rename the topic to reflect that? :smiling_face:

Hi everyone! Sorry to revive this thread, but I’m running into issues with customizing the outline in Typst v0.13.1, and none of the previous solutions seem to work anymore.

I’m trying to modify the outline entries to add spacing between the numbering and the section title. I followed some of @Xodarap suggestions, but hit a few roadblocks:

What I tried:

Running

// style outline: chapters as bold and without dots
#show outline.entry: entry => {
  if entry.element.func() == heading {
    // because we modify entries by replacing them with new ones, we need to recognize them to avoid endless recursion
    if entry.at("label", default: none) == <modified-outline-entry> {
      entry
    } else {
      // we destructure entry, change fields and then reassemble the entry
      let fields = entry.fields()
      let he = fields.element
      let number = if he.numbering != none {
        numbering(he.numbering, ..counter(heading).at(he.location()))
      }
      let prefix = if number != none [ #number #h(3em) ]
      fields.body = [ #prefix #he.body ]
      let newe = outline.entry(..fields.values())
      [ #newe <modified-outline-entry> ]
    }
  } else {
    entry
  }
}

fails because outline.entry expects positional arguments, not a spread of values. So I tried manually unpacking:

let newe = outline.entry(fields.level, fields.element)

This works, but it doesn’t include the modified body — so no spacing prefix.

So I tried creating a new heading element:

let new_element = heading(
  [ #prefix #he.body ],
  level: he.level,
  numbering: he.numbering,
)
let newe = outline.entry(fields.level, new_element)
[ #newe <modified-outline-entry> ]

But this fails with:

outline.entry: heading must have a location

My understanding:

Typst requires the heading passed to outline.entry to have a location, which is automatically assigned when the heading is part of the document. But when I create a new heading manually, to get around the headings immutable values, it doesn’t have a location, and I can’t find a way to assign the one of the heading I try to replace.

Additional notes

I’m also trying to solve a related issue: due to my font having variable-width numbers, the section titles in the outline are visually misaligned, as shown below:

In my opinion, sections of the same level as Scope, Audience, Requirements and Setup should be aligned with each other by default.

This is the intended look, that I previously made with word:

More or less everything in here does not work anymore since outline got a major rework in 0.13. (see: changelog)

I think it would be best if you’d open a new Question for this :)

1 Like

I have experimented a little and now came up with this:

// Styling
#show outline.entry.where(level: 1): set block(above: 10pt)
#set outline.entry(fill: repeat(gap: 1pt)[.])
#show link: set text(fill: black, hyphenate: false)
#show link: set underline(stroke: 0pt) // disbale underline if you had it set for link

// Add horizontal space between number and title in the outline
#show outline.entry: it => link(
  it.element.location(),
  it.indented(
    it.prefix(), // left column: the numbering/supplement
    [
      #h(5em) // <- extra space after the number (tweak to taste)
      #it.body() // the title/caption
      #box(width: 1fr, it.fill) // leader (dots), stretched across the gap
      #it.page() // page number
    ],
  ),
)

// Outline
#outline(indent: 1.25em)

which seems to do the trick: