Apple
March 9, 2026, 1:18am
1
Howdy,
I have headers that are more than one line long, how can I truncate them to fit on the top of page with hydra? One approach I tried was to use the chomp package with truncate-to-fit:
#set page(
header: context {
{
hydra(1, display: (
_,
it,
) => {
truncate-to-fit(
it.body,
height: 2em,
suffix: "...",
)
})
}
},
)
Yields this error:
error: element sequence has no method `len`
āā @preview/chomp:0.1.0/src/lib.typ:66:22
ā
66 ā let high = string.len()
ā ^^^
Which I believe is because the hydra header (it.body) is content? Rather than the string truncate-to-fit expects.
Other approaches fail in a similar way, not being able to modify the hydra header because it is content.
Thanks for the help in advance,
Do u use two heading packages (Hydra, Chomp) at the same time? Perhaps there are no guarantee to use two packages as-is.
But u could convert type (content ā string) via a simple script:
#let _to-string(content,) = {
if content == none { "" }
else if type(content) == str { content }
else if type(content) == array {content.map(_to-string).join(", ")}
else if content.has("text") { content.text }
else if content.has("children") {
if content.children.len() == 0 { "" }
else { content.children.map(_to-string).join("") }}
else if content.has("child") { _to-string(content.child) }
else if content.has("body") { _to-string(_to-string(content.body))}
else if content == [] { "" }
else if content == [ ] { " " }
else if content.func() == ref { "_ref_" }
else {let offending = content; ""}}
Andrew
March 30, 2026, 9:28am
3
Just use t4t , get.text works for most use cases just fine.
I use something like the following:
let section = /* hydra */
let max-width = /* length */
if section != none {
if section == none { section = [] }
if section.has("text") or section.has("children") {
let textwidth = max-width
let width = measure(section).width
if width <= textwidth [#section] else {
if section.has("text") {
section = section.text
textwidth -= measure([...]).width
while width > textwidth {
section = section.clusters().slice(0, -1).join()
width = measure(section).width
}
[#section...]
} else {
textwidth -= measure([...]).width
while width > textwidth and section.has("children") {
if section.children.last().has("text") and measure(section.children.last()).width > 4pt {
section = {
section.children.slice(0, -1).join()
section.children.last().text.clusters().slice(0, -1).join()
}
} else if section.children.last().has("body") and section.children.last().body.has("text") and measure(section.children.last()).width > 4pt {
section = {
section.children.slice(0, -1).join()
(section.children.last().func())(
section.children.last().body.text.clusters().slice(0, -1).join()
)
}
} else {
section = section.children.slice(0, -1).join()
}
width = measure(section).width
}
[#section...]
}
}
} else [#section]
}
Iām sure this could be made more elegant and/or better, but it works for me :)
#let max-width = 60pt
#let truncate(section) = context { /**/ }
#let demo(it) = block(fill: green, truncate(it))
#place(block(width: max-width, height: 20cm, fill: aqua))
#line(length: max-width)
#demo([Simple Title])
#demo([1: Simple Title])
#demo(["Simple Title"])
#demo([Simple _Title_])
#demo([This _has_ *some*])
#demo([This _has some formatting! Blah blah_])
#demo([Short])
#demo([With $sqrt(x)$ math])
#demo(none)
(EDIT: slightly improved it)
1 Like
Apple
March 31, 2026, 11:21pm
5
Thanks a bunch this worked perfectly right for my headers <3