How to measure available height and width in a context?

I would like to write an autofit function that sizes the content of raw blocks to fit the available space in presentation slides.

So I began to measure toe available height.

#set page(paper: "presentation-16-9")

Content before...

#let autofit() = {
  block(height: 1fr)[
    #layout(size => size)
  ]
}

#autofit()

Content after...

The output is (width: 729.14pt, height: 319.93pt) adapted to the available height. This does the trick, I thought.

The height changes when I add content before and after. So I thought this could be used to determine the available size like this:

Content before...

#let autofit() = context {
  let size = measure(block(height: 1fr))
  size
}

#autofit()

Content after...

or even simpler

#let autofit() = context {
  let size = measure(v(1fr))
  size
}

But this always results in (width: 0pt, height: 0pt).

Is there a way to measure the available height (and/or width) of a block? Maybe I get context wrong. Can I be rewritten to fit the context?

I think you need to request and use the available size from inside the block that reserves this size.

To return the correct height, measure needs the available height. You could do something like block(height: 1fr, layout(size => measure(..size, v(1fr))) but then the measure call is redundant, you can simply do:

#let autofit() = {
  block(height: 1fr)[
    #layout(size => {
      // generate raw block of correct size here
    })
  ]
}

Thank you for your answer.

The problem is that 1fr moves the entire content down to make space for the available content. I want to measure the available content but I don’t want to fill it with void. A call to measure is hypothetical, it does not change the layout but for 1fr it always returns 0.

for 1fr it always returns 0

That’s because there’s no maximum size defined in that measure call, so 1fr doesn’t make much sense in this case and Typst decides to give it a size of zero (measure is a pure function, when you call it it just measures something based on the given arguments).

It sounds like you want to insert something in the document, see how it would affect the layout (e.g. measure a part of the result) then remove it from the document to insert something different instead. You’ll probably need a slightly different approach.

You could make a function that takes the three parts of the slide content (the stuff above, the main thing you want to scale, and the stuff below) and checks how big the whole thing would be compared to the whole space available on the slide (this whole space is the size provided by layout). Maybe something like this:

#let scaled-content(before, main, after) = layout(size => {
  let all = before + block(main) + after
  let dummy = before + block(height: 0pt) + after
  let all-y = measure(width: size.width, all).height
  let dummy-y = measure(width: size.width, dummy).height
  let main-y = all-y - dummy-y
  if all-y > size.height {
    let factor = (size.height - dummy-y) / main-y
    before + scale(reflow: true, factor * 100%, block(main)) + after
  } else {
    all
  }
})

#scaled-content[
  Content before...
][
  #set text(36pt) // just a max value: the content below might be scaled down
  #raw("A\n"*20)
][
  Content after...
]

This example only takes care of scaling down the main content to fit the slide height (it won’t enlarge the content, and doesn’t check the width), but it might be a good starting point.

Note: here I always use a block for the middle part, to include the spacing betwen blocks in the calculation.

Thank you very much for your example. It does exactly what I would like, but unfortunately the API is not what I’ve been looking for. Sorry to be pedantic, but I would like it to be like a setting:

#show raw.where(block: true): scaled-content // or autofit whatever the function name is

to make all raw blocks automatically resize when the content is about to move out of bounds.

I apologize that this might not work with Typst.

That’s because there’s no maximum size defined in that measure call, so 1fr doesn’t make much sense in this case and Typst decides to give it a size of zero (measure is a pure function, when you call it it just measures something based on the given arguments).

Maybe I misunderstand it, but in your other example from here: How to auto-size text and images you have this call to measure

measure(width: size.width, { set text(text-size); it }).height <= size.height

where you size the text, measure it, but don’t actually apply it to . So I thought this behavior could be accomplished with measure(block(height: 1fr)) too. I apologize for not fully understanding the concept and what the difference is.

No apologies necessary :slight_smile:

I don’t know if it’s possible. The show rule doesn’t know about the other elements that are being laid out before and after the raw block so my solution above won’t work.

What would work is to wrap the raw block in a container that takes the size of 1fr or the natural height of its child, whichever is smaller. But I don’t know how to make such a container.

In that example I was measuring something whose size depends only on the width of the parent container (which I obtained by using layout). A block(height: 1fr) is quite different: its height depends on the content that comes before it and after it in the flow of the document.