Don't break blocks after empty frame

Instead, spill the whole child into the next region to prevent small
leftovers to influence layout. This is not done when all frames are
empty (e.g. for an explicitly sized block without content or fill).

This helps with the following cases:
- Previously, if a sticky block was followed by a leftover frame, the
  stickiness would be ignored, as the leftover was in fact sticking.
  This is not currently a problem, as sticky blocks aren't really
  breakable at the moment, but probably will be in the future.
- When ignoring stroke and fill for a first empty frame, a nested broken
  block would previously make the first frame not be considered empty
  anymore, which would lead to the leftover frame being filled.
- Similarly, when the fill of an explicitly sized block is ignored in
  the first empty frame, the leftover part would still be considered as
  laid out, making the actually visible block too small.
This commit is contained in:
Eric Biedert 2025-05-26 19:44:51 +02:00
parent 606183cd30
commit 693edb475d
5 changed files with 10 additions and 0 deletions

View File

@ -457,6 +457,7 @@ impl<'a> MultiChild<'a> {
regions: Regions, regions: Regions,
) -> SourceResult<(Frame, Option<MultiSpill<'a, 'b>>)> { ) -> SourceResult<(Frame, Option<MultiSpill<'a, 'b>>)> {
let fragment = self.layout_full(engine, regions)?; let fragment = self.layout_full(engine, regions)?;
let exist_non_empty_frame = fragment.iter().any(|f| !f.is_empty());
// Extract the first frame. // Extract the first frame.
let mut frames = fragment.into_iter(); let mut frames = fragment.into_iter();
@ -466,6 +467,7 @@ impl<'a> MultiChild<'a> {
let mut spill = None; let mut spill = None;
if frames.next().is_some() { if frames.next().is_some() {
spill = Some(MultiSpill { spill = Some(MultiSpill {
exist_non_empty_frame,
multi: self, multi: self,
full: regions.full, full: regions.full,
first: regions.size.y, first: regions.size.y,
@ -537,6 +539,7 @@ fn layout_multi_impl(
/// The spilled remains of a `MultiChild` that broke across two regions. /// The spilled remains of a `MultiChild` that broke across two regions.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MultiSpill<'a, 'b> { pub struct MultiSpill<'a, 'b> {
pub(super) exist_non_empty_frame: bool,
multi: &'b MultiChild<'a>, multi: &'b MultiChild<'a>,
first: Abs, first: Abs,
full: Abs, full: Abs,

View File

@ -283,6 +283,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
// Lay out the block. // Lay out the block.
let (frame, spill) = multi.layout(self.composer.engine, self.regions)?; let (frame, spill) = multi.layout(self.composer.engine, self.regions)?;
if frame.is_empty() && spill.as_ref().is_some_and(|s| s.exist_non_empty_frame) {
// If the first frame is empty, but there are non-empty frames in
// the spill, the whole child should be put in the next region to
// avoid any invisible orphans at the end of this region.
return Err(Stop::Finish(false));
}
self.frame(frame, multi.align, multi.sticky, true)?; self.frame(frame, multi.align, multi.sticky, true)?;
// If the block didn't fully fit into the current region, save it into // If the block didn't fully fit into the current region, save it into

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 B

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 B

After

Width:  |  Height:  |  Size: 144 B