Compare commits

...

6 Commits

Author SHA1 Message Date
Eric Biedert
a6f99ebdcc
Merge bef4e20434334d450a9d3cf3a41ada9c6cde1535 into b036fd97ab2c1cdc003b50eea7850279a1a7601f 2025-07-22 00:23:47 +09:00
Eric Biedert
bef4e20434 Add test for location of migrated block
Previously, this would result in a position on the first page.
2025-05-28 13:03:17 +02:00
Eric Biedert
811996eb70 Update references of existing tests
In `grid-header-containing-rowspan`, the first region is now correctly
not stroked.

Not sure what happened in `grid-header-orphan-prevention`, but the "B"
in the first header was too bold before.
2025-05-27 15:27:04 +02:00
Eric Biedert
02f07e7912 Don't label empty orphan frames
Adding a label makes a previously empty frame non-empty, but we want to
keep orphans empty.
2025-05-27 15:27:04 +02:00
Eric Biedert
693edb475d 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.
2025-05-27 15:21:15 +02:00
Eric Biedert
606183cd30 Add tests 2025-05-27 15:21:15 +02:00
13 changed files with 64 additions and 6 deletions

View File

@ -206,13 +206,11 @@ pub fn layout_multi_block(
let has_inset = !inset.is_zero();
let is_explicit = matches!(body, None | Some(BlockBody::Content(_)));
// Skip filling/stroking the first frame if it is empty and a non-empty
// one follows.
// Skip filling, stroking and labeling the first frame if it is empty and
// a non-empty one follows.
let mut skip_first = false;
if let [first, rest @ ..] = fragment.as_slice() {
skip_first = has_fill_or_stroke
&& first.is_empty()
&& rest.iter().any(|frame| !frame.is_empty());
skip_first = first.is_empty() && rest.iter().any(|frame| !frame.is_empty());
}
// Post-process to apply insets, clipping, fills, and strokes.
@ -244,7 +242,8 @@ pub fn layout_multi_block(
// Assign label to each frame in the fragment.
if let Some(label) = elem.label() {
for frame in fragment.iter_mut() {
// Skip empty orphan frames, as a label would make them non-empty.
for frame in fragment.iter_mut().skip(if skip_first { 1 } else { 0 }) {
frame.label(label);
}
}

View File

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

View File

@ -283,6 +283,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
// Lay out the block.
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)?;
// If the block didn't fully fit into the current region, save it into

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

View File

@ -72,6 +72,18 @@ B
#pagebreak(weak: true)
#metadata(none) <e>
--- locate-migrated-breakable ---
// Ensure that when a breakable element fully migrates to the next page without
// orphan frames, its position correctly reflects that.
#set page(height: 40pt)
A
#block[B]<a>
#context test(
locate(<a>).position(),
(page: 2, x: 10pt, y: 10pt),
)
--- issue-4029-locate-after-spacing ---
#set page(margin: 10pt)
#show heading: it => v(40pt) + it

View File

@ -64,6 +64,12 @@ First!
is the sun.
]
--- block-multiple-pages-empty ---
#set page(height: 60pt)
A
#block(height: 30pt)
B
--- block-box-fill ---
#set page(height: 100pt)
#let words = lorem(18).split()
@ -287,6 +293,37 @@ Paragraph
#block(width: 100%, fill: red, box("a box"))
#block(width: 100%, fill: red, [#box("a box") #box()])
--- issue-2914-block-height-cut-off ---
// Ensure that breaking a block doesn't shrink its height.
#set page(height: 65pt)
#set block(fill: aqua, width: 25pt, height: 25pt, inset: 5pt)
#block[A]
#block[B]
--- issue-2914-block-fill-skip-nested ---
// Ensure that fill and stroke are skipped for an empty frame with a nested block.
#set page(height: 50pt)
A
#block(fill: aqua, stroke: blue, inset: 5pt, width: 100%, block[B])
--- issue-6304-block-skip-label ---
// Ensure that labeling is skipped for an empty orphan frame.
#set page(height: 60pt)
A
#block(sticky: true)[B]
#block[C] <label>
--- issue-6125-block-place-width-limited ---
// Ensure that the width of a placed block isn't limited by its siblings.
#set page(height: 70pt)
#let b = block({
square(size: 20pt, fill: aqua)
place(top, box(height: 10pt, width: 1fr, fill: blue))
})
#b
#b
--- issue-5296-block-sticky-in-block-at-top ---
#set page(height: 3cm)
#v(1.6cm)