How to auto-size text and images

I use typst heavily to write lecture slides, one thing that would be very handy would be if when adding text or images to a slide they didn’t wrap to the next page. Write now it’s a process of back and forth setting text size manual and re-rendering, or the equivalent with images. I should say images work better but when you’re combining images and text you have to play around with the image size to make sure that the subsequent text doesn’t page-wrap.

I’ve used both polylux and touying, and both have tried with the #fit-to-height function, but it’s quite buggy and results in strange layouts.

How much is this an internal typst layout issue, versus possible within the existing scripting environment?

To be clear what I’m looking for is a flag for the page that would prevent content from wrapping, and auto-size it to fit within the page if the flag is set to True. From previous attempts at this I gather it’s somewhat complicated because you have to layout out the content first before you decide where it wraps and at what size to set the text.

There are additional issue when using multiple columns… and ideally you could tell the content to auto-size within a column (i.e. a 2-column slide with text on one side and an image on the other).

Typst 0.12 has improved things a bit on this theme. The scale function now accepts absolute dimensions as the target size, and measure accepts arguments for the target container size. As I understand, these make the polylux/touying fit-to-height obsolete.

The measure thing could also help for the specific goal of scaling the text size to fill some available space. But I can only think of a rather inefficient approach: a bisection algorithm to find the largest text size that still fits.

Here’s an implementation of the bisection algorithm I was referring to:

#let fill-height-with-text(min: 0.3em, max: 5em, eps: 0.1em, it) = layout(size => {
  let fits(text-size, it) = {
    measure(width: size.width, { set text(text-size); it }).height <= size.height
  }

  if not fits(min, it) { panic("Content doesn't fit even at minimum text size") }
  if fits(max, it) { set text(max); it }

  let (a, b) = (min, max)
  while b - a > eps {
    let new = 0.5 * (a + b)
    if fits(new, it) {
      a = new
    } else {
      b = new
    }
  }

  set text(a)
  it
})

#block(height: 5cm, fill: luma(80%), fill-height-with-text(lorem(10)))

#block(height: 5cm, fill: luma(80%), columns(2)[
  #fill-height-with-text(lorem(10))

  #fill-height-with-text(lorem(7))
])

1 Like

Wow that’s amazing! I was working on my own crude solution, but that seems to hit the nail on the head. Thanks!

1 Like

(I’ve edited the code to avoid producing an error when the max size fits)