diff --git a/crates/typst/src/layout/container.rs b/crates/typst/src/layout/container.rs index 2ff0f0fd6..705a5ef74 100644 --- a/crates/typst/src/layout/container.rs +++ b/crates/typst/src/layout/container.rs @@ -925,18 +925,22 @@ fn breakable_pod<'a>( /// height and a new backlog. fn distribute<'a>( height: Abs, - regions: Regions, + mut regions: Regions, buf: &'a mut SmallVec<[Abs; 2]>, ) -> (Abs, &'a mut [Abs]) { // Build new region heights from old regions. let mut remaining = height; - for region in regions.iter() { - let limited = region.y.min(remaining); + loop { + let limited = regions.size.y.clamp(Abs::zero(), remaining); buf.push(limited); remaining -= limited; - if remaining.approx_empty() { + if remaining.approx_empty() + || !regions.may_break() + || (!regions.may_progress() && limited.approx_empty()) + { break; } + regions.next(); } // If there is still something remaining, apply it to the diff --git a/crates/typst/src/layout/flow/compose.rs b/crates/typst/src/layout/flow/compose.rs index cfce7d496..003e4e72c 100644 --- a/crates/typst/src/layout/flow/compose.rs +++ b/crates/typst/src/layout/flow/compose.rs @@ -282,7 +282,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { let need = frame.height() + clearance; // If the float doesn't fit, queue it for the next region. - if !remaining.fits(need) && !regions.in_last() { + if !remaining.fits(need) && regions.may_progress() { self.work.floats.push(placed); return Ok(()); } @@ -343,7 +343,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { let mut relayout = false; let mut regions = *regions; - let mut migratable = !breakable && !regions.in_last(); + let mut migratable = !breakable && regions.may_progress(); for (y, elem) in notes { // The amount of space used by the in-flow content that contains the diff --git a/crates/typst/src/layout/flow/distribute.rs b/crates/typst/src/layout/flow/distribute.rs index 71f9598b8..65516ccd8 100644 --- a/crates/typst/src/layout/flow/distribute.rs +++ b/crates/typst/src/layout/flow/distribute.rs @@ -194,9 +194,9 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> { /// Processes a line of a paragraph. fn line(&mut self, line: &'b LineChild) -> FlowResult<()> { - // If the line doesn't fit and we're allowed to break, finish the - // region. - if !self.regions.size.y.fits(line.frame.height()) && !self.regions.in_last() { + // If the line doesn't fit and a followup region may improve things, + // finish the region. + if !self.regions.size.y.fits(line.frame.height()) && self.regions.may_progress() { return Err(Stop::Finish(false)); } @@ -228,9 +228,9 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> { // Lay out the block. let frame = single.layout(self.composer.engine, self.regions.base())?; - // If the block doesn't fit and we're allowed to break, finish the - // region. - if !self.regions.size.y.fits(frame.height()) && !self.regions.in_last() { + // If the block doesn't fit and a followup region may improve things, + // finish the region. + if !self.regions.size.y.fits(frame.height()) && self.regions.may_progress() { return Err(Stop::Finish(false)); } diff --git a/crates/typst/src/layout/grid/repeated.rs b/crates/typst/src/layout/grid/repeated.rs index acb00f046..f187d0bc4 100644 --- a/crates/typst/src/layout/grid/repeated.rs +++ b/crates/typst/src/layout/grid/repeated.rs @@ -57,7 +57,7 @@ impl<'a> GridLayouter<'a> { let mut skipped_region = false; while self.unbreakable_rows_left == 0 && !self.regions.size.y.fits(header_rows.height + self.footer_height) - && !self.regions.in_last() + && self.regions.may_progress() { // Advance regions without any output until we can place the // header and the footer. @@ -124,7 +124,7 @@ impl<'a> GridLayouter<'a> { let mut skipped_region = false; while self.unbreakable_rows_left == 0 && !self.regions.size.y.fits(footer_height) - && !self.regions.in_last() + && self.regions.may_progress() { // Advance regions without any output until we can place the // footer. diff --git a/crates/typst/src/layout/grid/rowspans.rs b/crates/typst/src/layout/grid/rowspans.rs index 91e7d8efe..6381ba668 100644 --- a/crates/typst/src/layout/grid/rowspans.rs +++ b/crates/typst/src/layout/grid/rowspans.rs @@ -1147,7 +1147,7 @@ impl<'a> RowspanSimulator<'a> { // Skip until we reach a fitting region for both header and footer. while !self.regions.size.y.fits(header_height + footer_height) - && !self.regions.in_last() + && self.regions.may_progress() { self.regions.next(); self.finished += 1; diff --git a/crates/typst/src/layout/regions.rs b/crates/typst/src/layout/regions.rs index 68ad4b7a9..385664bbd 100644 --- a/crates/typst/src/layout/regions.rs +++ b/crates/typst/src/layout/regions.rs @@ -96,14 +96,18 @@ impl Regions<'_> { /// Whether the first region is full and a region break is called for. pub fn is_full(&self) -> bool { - Abs::zero().fits(self.size.y) && !self.in_last() + Abs::zero().fits(self.size.y) && self.may_progress() } - /// Whether the first region is the last usable region. - /// - /// If this is true, calling `next()` will have no effect. - pub fn in_last(&self) -> bool { - self.backlog.is_empty() && self.last.map_or(true, |height| self.size.y == height) + /// Whether a region break is permitted. + pub fn may_break(&self) -> bool { + !self.backlog.is_empty() || self.last.is_some() + } + + /// Whether calling `next()` may improve a situation where there is a lack + /// of space. + pub fn may_progress(&self) -> bool { + !self.backlog.is_empty() || self.last.is_some_and(|height| self.size.y != height) } /// Advance to the next region if there is any. diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs index dfcb0f7b7..83d3fc748 100644 --- a/crates/typst/src/math/equation.rs +++ b/crates/typst/src/math/equation.rs @@ -327,8 +327,9 @@ fn layout_equation_block( let mut rows = full_equation_builder.frames.into_iter().peekable(); let mut equation_builders = vec![]; let mut last_first_pos = Point::zero(); + let mut regions = regions; - for region in regions.iter() { + loop { // Keep track of the position of the first row in this region, // so that the offset can be reverted later. let Some(&(_, first_pos)) = rows.peek() else { break }; @@ -344,8 +345,9 @@ fn layout_equation_block( // we placed at least one line _or_ we still have non-last // regions. Crucially, we don't want to infinitely create // new regions which are too small. - if !region.y.fits(sub.height() + pos.y) - && (!frames.is_empty() || !regions.in_last()) + if !regions.size.y.fits(sub.height() + pos.y) + && (regions.may_progress() + || (regions.may_break() && !frames.is_empty())) { break; } @@ -357,6 +359,7 @@ fn layout_equation_block( equation_builders .push(MathRunFrameBuilder { frames, size: Size::new(width, height) }); + regions.next(); } // Append remaining rows to the equation builder of the last region. diff --git a/tests/ref/issue-5044-pad-100-percent.png b/tests/ref/issue-5044-pad-100-percent.png new file mode 100644 index 000000000..510475679 Binary files /dev/null and b/tests/ref/issue-5044-pad-100-percent.png differ diff --git a/tests/suite/layout/pad.typ b/tests/suite/layout/pad.typ index 3a7439d00..4ff9f8ec4 100644 --- a/tests/suite/layout/pad.typ +++ b/tests/suite/layout/pad.typ @@ -28,3 +28,7 @@ Hi #box(pad(left: 10pt)[A]) there --- pad-adding-to-100-percent --- // Test that padding adding up to 100% does not panic. #pad(50%)[] + +--- issue-5044-pad-100-percent --- +#set page(width: 30pt, height: 30pt) +#pad(100%, block(width: 1cm, height: 1cm, fill: red))