How can I delay a floating by exactly one page?

Hi! I’m making a little template for a magazine layout and I would like to have a floating block at the top of the first two pages of the article (i.e. the first page spread). I have the text of the article in a separate file, loading in the template and applying one of its functions as the show rule to the whole article. This makes the first float very easy to generate, before the body of the article itself. However, I am having trouble placing the second float, on the top of the right page. I also need it to not interfere with any floats generated by the article itself, whose content can be arbitrary. Any ideas for how to achieve that?

what i’m doing currently:

  • generate the 2nd page float content before the body of the article is added
  • save the float content into a state
  • periodically check to see if there is a float in the state to be placed
    • if there is, check we’re on the next page by now
    • if we’re on the next page, place the float here
    • clear the state if it got placed

why this doesn’t work:

  • i need to somehow call a function a bunch of times in the future. so far i’m doing that as a show rule on parbreaks, because they’re common enough that i can be reasonably certain that it will happen at least once on the next page
  • the compiler is complaining that my document layout doesn’t converge. i think this has something to do with the state not being properly cleared once the float is placed, because sometimes it will spawn in the float a bunch of times

what would really help is to be able to call a function exactly once per page from within the page body i think. but i couldn’t figure out how to do that with a show page rule, since it doesn’t really give me good access to the page body.

I’m not aware of a direct solution to this. page.background is rendered once per page, but a figure in the background won’t actually go onto the page in the way you want it to float. Same goes for header and footer, if my test are correct.

That we can help with. What you describe indeed sounds like something that can easily have that effect. You can check out Why is State Final not "final"?, Which I think should apply fairly well to your situation. If you don’t get it to work, show your relevant code and we can figure it out together.

I read the post you’ve linked, and I think I understand the issue there, but I think my case is somewhat different. With my state, I am checking to see if the float has been placed yet. This will necessarily depend on context, since it will depend on what page it’s on. Is is still possible to write a state update function if it needs to depend on context?

Okay, I’ve managed to create this function, which successfully finds the place where the floating element should be placed – at each parbreak in an article, I apply this:

let check-float-queue() = context {
  let here-page = here().page()

  context placed-float.get() // inspect before update
  placed-float.update(x => {
    x or (here-page > float-page)
  })
  context placed-float.get() // inspect after update

  if placed-float.get() == false and here-page > float-page {
    // float-place
  }
}
show parbreak: it => it + check-float-queue()

I can see that on the first page of the article, the state is false both before and after the update, at the first parbreak of the next page it switches from false to true, and then stays true forever. So I thought this would be enough – I place the float when it would switch. But, the moment I uncomment float-place, which will place my floating element, my document no longer converges. I assume it must be moving stuff around, possibly changing what pages the parbreaks are on. Not really sure what to do with this now.

ah that’s true, since your placement decisions affect where page parbreaks occur at, you not only have have a get/update dependency but also a layout dependency… hmm, that makes the problem considerably harder. I’d have to try stuff out to see if it can be made to work. Could you post a full reproducing example (with state initialization, example content, etc.)?

This should reproduce the issue:

#let placed-float = state("placed-float", false)

#let article(body) = context {
	pagebreak(weak: true, to: "even")
	let float(side) = place(
		top,
		float: true,
		scope: "parent",
		block(width: 100%, height: 10em, stroke: black, align(center + horizon)[template float #side])
	)
	
	let float-page = here().page()
	let float-place = float[right]
	placed-float.update(false)

	let check-float-queue() = context {
		let here-page = here().page()

		// context placed-float.get() // inspect before update
		placed-float.update(x => {
			x or (here-page > float-page)
		})
		// context placed-float.get() // inspect after update

		if placed-float.get() == false and here-page > float-page {
			float-place
		}
	}
	show parbreak: it => it + check-float-queue()

	float[left]

	body
}

#{
	show: article
	[
		#lorem(200)
		
		#lorem(200)
		
		#lorem(200)
		
		#lorem(200)
		
		#lorem(200)

	]
}

#{
	show: article
	[
		#lorem(200)

		#lorem(400)

		#lorem(300)
	]
}

If you change the check-float-queue function to the one from my last post, it will correctly show falsetrue on exactly the first parbreak on the new page. But if you uncomment float-page, it should break in the second article only. No idea why.

This is a more minimal example. I noticed that last parbreak is also important for the warning.

code
#let placed-float = state("placed-float", false)

#let article(body) = context {
  pagebreak(weak: true, to: "even")
  let float(side) = place(
    top,
    float: true,
    rect(height: 10em, align(center + horizon)[template float #side]),
  )

  let float-page = here().page()
  let float-place = float[right]
  placed-float.update(false)

  let check-float-queue() = context {
    let here-page = here().page()

    // context placed-float.get() // inspect before update
    placed-float.update(x => x or here-page > float-page)
    // context placed-float.get() // inspect after update

    if placed-float.get() == false and here-page > float-page {
      float-place
    }
  }
  show parbreak: it => it + check-float-queue()

  float[left]

  body
}

#show: article
#lorem(200)

#lorem(200)

#lorem(200)

#lorem(200)

#lorem(200)