Skip to content

Instantly share code, notes, and snippets.

@PgBiel
Last active July 8, 2025 04:10
Show Gist options
  • Save PgBiel/c530ae8ac937469510ab382e03a6ba2b to your computer and use it in GitHub Desktop.
Save PgBiel/c530ae8ac937469510ab382e03a6ba2b to your computer and use it in GitHub Desktop.
Typst - Fix list item content centering and relative lengths
// MIT No Attribution
//
// Copyright (c) 2025 Pg Biel
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify,
// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Target version: Typst v0.13
// (Check https://gist.github.com/PgBiel/c530ae8ac937469510ab382e03a6ba2b for the latest version)
// Fixes equation centering and relative lengths
// But property number-align is now broken (always text baseline alignment)
// Plus `set enum(numbering: "1.a.")` must now be e.g. `set enum(full: true, numbering: (..prev, num) => numbering(if prev.pos() == () { "1." } else { "a." }, num))`
//
// Use with `show: fix-lists` on your main document file
#let fix-lists(it) = {
let enum-number = state("__centering-fix--enum-number")
show enum: it => context {
let prev = enum-number.get()
let reversed = it.at("reversed", default: false)
let i = if it.start != auto { it.start + if reversed { 1 } else { -1 } } else if reversed { it.children.len() + 1 } else { 0 }
let max-num-width = 0.0
let lst = for item in it.children {
let number = if item.at("number", default: none) != none {
item.number
} else if reversed {
i - 1
} else {
i + 1
}
i = number
let rendered-number = if it.full {
[#numbering(it.numbering, ..prev, number)]
} else {
[#numbering(it.numbering, number)]
}
// To determine padding of sub-items from the left of the list
max-num-width = calc.max(max-num-width, measure(rendered-number).width / 1pt)
let body = {
enum-number.update(prev => (..prev, number))
item.body
enum-number.update(((..prev, _)) => prev)
}
[/ #strong(delta: -strong.delta, rendered-number): #body]
}
terms(tight: it.tight, hanging-indent: max-num-width * 1pt + it.body-indent, separator: h(it.body-indent), indent: it.indent, ..if it.children.len() == 1 { (lst,) } else { [#lst].children })
}
let list-level = counter("__centering-fix--list-level")
show list: it => list-level.step() + context {
let marker = [#if type(it.marker) == array {
// Only read the level if necessary
let level = list-level.get().first() - 1
it.marker.at(calc.rem(level, it.marker.len()))
} else if type(it.marker) == function {
let level = list-level.get().first() - 1
(it.marker)(level)
} else {
it.marker
}]
let marker-width = measure(marker).width
let lst = for item in it.children {
[/ #strong(delta: -strong.delta, marker): #item.body]
}
terms(tight: it.tight, hanging-indent: marker-width + it.body-indent, separator: h(it.body-indent), indent: it.indent, ..if it.children.len() == 1 { (lst,) } else { [#lst].children })
} + list-level.update(i => i - 1)
it
}
// Usage: at the top of your main document file (NOT in template.typ or similar), add this rule:
#show: fix-lists
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment