Is there a way to do iterative rendering modifying state and reading the final page counter?

Hi I’m sorry if the title isn’t very helpful.
I’m trying to develop a system that automatically adjust a theshold variable that determines how many items are rendered, trying to fit as many items as posible in a single page.

#let actual-threshold = 2
#let item-with-threshold(level: 0, ..args) = {
    if level <= actual-threshold [#args.pos().join()\ ]
}
#item-with-threshold(level: 0)[This item has a very high priority]
#item-with-threshold(level:1)[This has lower priority]
#item-with-threshold(level:10)[Very low priority things]
#item-with-threshold(level:10)[Very low priority things]
#item-with-threshold(level:10)[Very low priority things]
#item-with-threshold(level:10)[Very low priority things]

With that code, I’m already able to manually adjust the threshold and maybe read the variable from console arguments and make a bash script that renders the document with different thresholds, checking the numbers of pages of the resulting pdf until it reaches the highest

In order to actually be able to update the variable and then check the layout I need to use state and context

#let actual-threshold = state("actual-threshold", int(0))
#let item-with-threshold(level: 0, ..args) = {
    context if level <= actual-threshold.get() [#args.pos().join()\ ]
}

And then the hard part: making a loop that increases the variable and checks the resulting layout.
The best I could do was this:

#context {
    for i in range(10) {
        context {
            if counter(page).final().at(0) > 1 {
                actual-threshold.update(old => old - 1)
            }
            else{
                render()
                }
        }
    }
}

But it doesn’t work because, as the docs say, the counter “predics the future” so it ends up running render() 10 times.

I also wrote

#context {
    for i in range(10) {
        context {
            if counter(page).final().at(0) > 1 {
                actual-threshold.update(old => old - 1)
            }
        }
    }
    render()
}

But it has some bugs with the counter. If I activate the numbering for the pages, this produces a 2 page pdf but with the number 1 in each one’s numbering

#set page(height:100pt, width: 200pt, numbering: "1")
#let actual-threshold = state("actual-threshold", int(10))
#let item-with-threshold(level: 0, ..args) = {
    context if level <= actual-threshold.get() [#args.pos().join()\ ]
}

#let render() = {
item-with-threshold(level: 0)[This item has a very high priority]
item-with-threshold(level:1)[This has lower priority]
item-with-threshold(level:1)[Very low priority things]
item-with-threshold(level:1)[Very low priority things]
item-with-threshold(level:2)[Very low priority things]
item-with-threshold(level:2)[Very low priority things]
    }
    
#context {
    for i in range(10) {
        context {
            if counter(page).final().at(0) > 1 {
                actual-threshold.update(old => old - 1)
            }
        }
    }
    render()
}

Does anyone have any idea on how to implement this? Should this be reported as a bug? Should I give up and use a bash script?

Thanks

If I’m getting the gist of your question correctly, you want to know whether some content can fit in a single page, and adjust the content until it can. I think you can achieve this in Typst; here are the fundamental steps:

  1. Put the content in a function that you can call with different actual-thresholds. You don’t need state for this; just do something like this:
    #let full-content(actual-threshold) = {
      let item-with-threshold(level: 0, ..args) = {
        if level <= actual-threshold [#args.pos().join()\ ]
      }
      item-with-threshold(level: 0)[This item has a very high priority]
      item-with-threshold(level:1)[This has lower priority]
      // ...
    }
    
  2. Use layout and measure to determine the height of the content on the actual page:
    #layout(size => {
      let measured-height = measure(full-content(2), width: size.width)
      // does it fit? check `measured-height < size.height`
    })
    
  3. Use binary or linear search to determine the largest actual-threshold that fits. Do all this inside layout(), or you’ll run into Why is the value I receive from context always content?

That’s good! I had forgotten about layout.
Now with that working, do you think there’s a way to avoid wrapping full-content in a function?
Basically what I’m trying to do is to have a “normal” document where I use the item-with-threshold function, and then call a function adjust-threshold that re-renders the entire document modifying the threshold variable, which I guess should be a state variable.

Then you’d indeed need to use state again, but I think either of these would work:

measure({
  actual-threshold.update(...)
  include "document.typ"
}, width: size.width)

or

#show: doc => {
  ...
  measure({
    actual-threshold.update(...)
    doc
  }, width: size.width)
  ...
}