Something similar could likely be available in existing packages, especially those originating from a particular organisation. However considering that it’s your own, the chances of that are lower. Or so I understood.
About the code you have provided:
-
Seems like you’re using this as part of a template. Not that your use is wrong, but usually, you would have one template function which you would then
showafterimporting, see Making a Template – Typst Documentation. -
Something like
set outline(...)can contain more than one argument. I suppose it’s reasonable that you separated them because you’re commenting what the purpose is. -
Using
emfor spacing is more recommended I would say, but I decided not to change that.
From what I can tell, the
outline.entryfunction is what I want, but I honestly don’t get how it operates from the examples in the Typst docs.
This is what you should follow: Outline Function – Typst Documentation.
I took the time to follow through your outline.
Notable features:
- Outline entries from prelude pages using roman numerals and unnumbered headings are properly distinguished
- Right-aligned dotted lines, which are simpler to achieve than aligned. Not to undermine @ensko’s suggestion, but I think it’s less relevant since you specified this. Mentioned also by @Eric in How to remake this outline in Typst? - #7 by Eric
- Automated left and right tab-like spacing
These are the implemented horizontal spacings, each of which applies the same for all outline entries of the second level, mimicking Typst’s initial outline:
- Helper spacings:
fill-gapcontrols the spacing between therepeatseparators:- You can change the recommended
1em
- You can change the recommended
fill-gap-padensures that therepeatseparator on either end doesn’t interfere with the heading nor the page number:- Automatically based on
fill-gap
- Automatically based on
- Left indent: [1]
- Automatically based on the widest-numbered level 1 outline entry
- This could have simply been
auto, see the code why it’s not
- Left indented.gap: [1:1]
- Automatically based on the widest-numbered level 2+ outline entry [2]
- Comparable to what
indented.gaprepresents would bewidest-level-2 - curr-level-2 + 1em - You can change the recommended
1em
- Right page number gap:
- Automatically based on the last heading’s page number, even level 1’s, assuming that it’s the widest-numbered
- The actual gap is
last-page-number-width - curr-page-number-width + fill-gap-pad + 1em - You can change the recommended
1em
There’s also indented-gap which is indented.gap for the level 1 outline entries and can be changed. These also have a strong emphasis which can be changed through text-weight.
Code
// Outline rules from @hpcfzl on Typst forum
#{
// Excludes headings level `excl`+
let excl = 4
set outline(depth: excl)
/*
This is unreliable for more than single-digit numbers, but
would be in case `auto` was allowed in place of `1em`
*/
// Indents only heading numbers level 2+, all the same
// set outline(indent: n => if n == 0 { 0em } else { 1em })
// Indentation is done in the `outline.entry` rule
// set outline(indent: 0em)
// Indentation is actually NOT needed for heading numbers level 2+
// This would be equivalent to `set outline(indent: n => if n == 0 { auto } else { 0em })`
show selector.or(..range(2, excl + 1).map(it => outline.entry.where(level: it))): set outline(indent: 0em)
// Repeating separator
let fill-gap = 1em
let fill-gap-pad = fill-gap / 5
set outline.entry(fill: repeat(gap: fill-gap, justify: false)[.])
show repeat: set align(right)
// Rule for outline entries
show outline.entry: it => {
let indented-gap = 0.5em
let text-weight = "bold"
set text(weight: text-weight) if it.level == 1
set block(above: 24pt) if it.level == 1 // Could be made proportional to the `indented-gap`
// Seizure warning
let widest-heading = {
let heading-locs = query(heading).map(h => h.location())
(
n: calc.max(
..heading-locs.map(loc => counter(heading).at(loc)).map(it => it.map(str).join(".")).map(it => measure(it).width)
),
p: measure(
text(weight: text-weight, str(counter(page).at(heading-locs.last()).first()))
).width
)
}
link(
it.element.location(),
it.indented(
if it.level == 1 { it.prefix() },
{
if it.level > 1 {
hide(
text(
weight: text-weight,
str(counter(heading).final().first()) + h(indented-gap), // Wrong to use `counter(heading).display()`
)
)
box(width: widest-heading.n + 1em, it.prefix()) // Pointless to add `h(indented-gap)`
}
it.body()
h(fill-gap-pad)
box(width: 1fr, if it.level > 1 { it.fill })
h(fill-gap-pad)
box(width: widest-heading.p + 1em, align(right, it.page()))
},
gap: indented-gap,
)
)
}
outline()
}
I believe my approach addresses the few conundrums, but feel free to allude to any further observations.
Mind that these hyperlinked values aren’t the ones actually used. ↩︎ ↩︎
This is something to be aware of, as there can be a level
exclheading which is the widest but invisible to the outline. Filtering thequeryis all that’s needed, I just didn’t think this would be worth the additional hassle. ↩︎
