Truncating Hydra Headers To A Single Line

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; ""}}

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

Thanks a bunch this worked perfectly right for my headers <3