#set heading(numbering: "1.1")
Blabla 1
= This is test 1
#set heading(numbering: (..n) => h(1cm) + numbering("1.1", ..n))
Blabla 2
= This is test 2
Blabla 3
However, note that if you plan on using a period (or other symbol) at the end of the numbering format string, this will introduce an unwanted behavior when referencing a heading label:
#set heading(numbering: "1.1.")
= A <a>
@a.
#set heading(numbering: (..n) => numbering("1.1.", ..n))
= B <b>
@b.
This is because with a custom/user function, Typst can’t know if the last period is mandatory or can be lifted when a period already exist (at the end of a sentence).
If you want to have more spacing between “1.” and “Heading” in “1. Heading”, you can use a show rule to “rebuild” the heading from scratch:
One thing to note when rebuilding the heading like that is that you should wrap it in a block to keep the spacing behavior of the original heading. When just using it.body, the heading gets display inline (or here as a normal paragraph).
This will affect @references as well, so it’s not recommended. Use a show rule similar to the one in my answer but for outline.entry instead. Note that it gets a bit longer though, since you also have to add it.fill and it.page after the title.
You will also have to write numbering(it.element.numbering, ..counter(heading).at(it.element.location())) instead of just counter(heading).display(it.element.numbering) to ensure it uses the numbers from the heading’s original location instead of the outline entry’s location (which would always return the number 0, since the outline comes before any headings).
Here’s the complete deal:
#set heading(numbering: "1.")
// Don't add spacing when there's no numbering (which is the case for the outline title)
#show heading: it => if it.numbering == none { it } else { block(counter(heading).display(it.numbering) + h(2em) + it.body) }
// NEW! Add spacing in the entries
#show outline.entry: it => {
if it.element.func() == heading { // don't interfere with figure outlines (optional if you don't have one)
let head = it.element // just to make our code shorter
let number = numbering(head.numbering, ..counter(heading).at(head.location()))
let fill = box(width: 1fr, it.fill) // ensure the fill doesn't occupy the full page width, just the available space (1fr)
[#number #h(2em) #head.body #fill #it.page]
}
}
#outline()
= Hello world!
== Hello world!
= Hello world!
Hmm, this is slightly weird as I thought it won’t add any additional space to the heading because multiple spaces are generally always merged into one. But for references this rule does apply, although it removes all spaces, since the period is connected to the reference body.
This doesn’t actually have to do with multiple spaces being merged into one. In references, the trimmed numbering pattern is used, which consists only of the actual number. Any prefixes and suffixes are removed. This is also why a reference to an equation with the numbering "(1)" shows as “Equation 1” instead of “Equation (1)”.
Thanks, I wasn’t fully aware that spaces were also trimmed. Though I’d still be wary of issues popping up when e.g. manually invoking numbering(it.numbering, ..numbers) in a show rule.
If you have any unnumbered headings (e.g. bibliography), the given show rule will error as you can’t pass none to the numbering function. This is why I would always suggest putting the number (and the following space) in a conditional. For example, when adjusting @PgBiel’s code from above:
#show outline.entry: it => {
if it.element.func() == heading { // don't interfere with figure outlines (optional if you don't have one)
let head = it.element // just to make our code shorter
let number = if head.numbering != none {
numbering(head.numbering, ..counter(heading).at(head.location()))
}
let fill = box(width: 1fr, it.fill) // ensure the fill doesn't occupy the full page width, just the available space (1fr)
[#number #h(2em, weak: true) #head.body #fill #it.page]
} else {
it // use default style for figure outlines
}
}
By making the 2em spacing weak, it will collapse if there is no number before it.
You were both right, I have one unnumbered headline (Bibliography). Very, very helpful thanks so much!
Update: Original part of the post:
This is more of a learning question, but maybe it is also useful for others (and actually related to the topic). I have this if-condition to make the level: 1 heads bold:
It works, but it is quite repetitive – is there a better, more elegant way to make such modifications? I don’t think there is a ternary, but I think I had situations like this a couple of times now and always wondered if I could do it better somehow.
Update: My approach to making this shorter and easier to maintain was to store the string contruction in a variable and using that, instead of repeating the line. See post 19.
And while I wrote this actually a follow-up question came up. My
[#number #h(1em, weak: true) #head.body #fill #it.page]
#v(0.1pt, weak: true) // spaces way more than expected
I had in there now goes rampant even if I make it 0.01pt I guess that is because the boxes/blocks collapsed and the vertical space stops that. I can get the expected result by making the #v negative, but it doesn’t seem like the “correct” solution? How would control of the vertical spacing be approach best?
This is still relevant but might get answered below.
Fails with “error: expected string, dictionary, location, or label, found content”. or “error: expected integer, boolean, float, decimal, or string, found content”
This can be solved by using head.location(), please see below.
This is what I came up with to get the links in the outline back:
#show outline.entry: it => {
if it.element.func() == heading {
// don't interfere with figure outlines (optional if you don't have one)
let head = it.element // just to make our code shorter
let number = if head.numbering != none {
numbering(head.numbering, ..counter(heading).at(head.location()))
}
let fill = box(
width: 1fr,
it.fill,
) // ensure the fill doesn't occupy the full page width, just the available space (1fr)
let toc-entry = box(
//stroke: 1pt + red, //used for debugging
link(head.location(), box(number + h(0.75em, weak: true) + head.body + fill + it.page)),
)
if head.level == 1 {
strong(toc-entry)
} else {
toc-entry
}
v(-0.4em, weak: true)
} else {
it // use default style for figure outlines
}
}
#outline(indent: 1.65em)
#pagebreak()
#set page(numbering: "1")
#counter(page).update(1)
#set heading(numbering: "1.1 ")
= First Headline 1
== First Headline 2
== Second Headline 2
#lorem(800)
= Second Headline 1
= Third Headline 1
#heading(numbering: none)[Literature]
If you need indents for everything below level 1 you get line breaks, messing up the spacing, because the indent “overfills” due to the 1fr fill. I guess it would be possible to leave the indent and add a own spacing instead, substracting this spacing from the 1fr, but then the code would bloat even further and another if/loop for subsequent levels would be necessary. Update: I tried that but subsctracting from fractions is not possible or at least would involve further measuring steps. Maybe there is a compact/elegant solution I’m just not seeing? I also tried pad() for the indents, but that leads to the same problem, just that the line breaks is below instead of above.
The spacing needs the weird v(-0.4em, weak: true), which I suppose should not be necessary if done right – I think my code introduces some space where it should not?