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
})
]
}
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.
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).
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.
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.