diff --git a/crates/typst-layout/src/grid/layouter.rs b/crates/typst-layout/src/grid/layouter.rs index dbe31c30b..bc64b6e78 100644 --- a/crates/typst-layout/src/grid/layouter.rs +++ b/crates/typst-layout/src/grid/layouter.rs @@ -66,6 +66,16 @@ pub struct GridLayouter<'a> { /// calculation. /// TODO: consider refactoring this into something nicer. pub(super) current_row_height: Option, + /// This is `true` when laying out non-short lived headers and footers. + /// That is, headers and footers which are not immediately followed or + /// preceded (respectively) by conflicting headers and footers of same or + /// lower level, or the end or start of the table (respectively), which + /// would cause them to stop repeating. + /// + /// If this is `false`, the next row to be laid out will remove an active + /// orphan snapshot and will flush pending headers, as there is no risk + /// that they will be orphans anymore. + pub(super) in_active_repeatable: bool, /// The span of the grid element. pub(super) span: Span, } @@ -211,6 +221,7 @@ impl<'a> GridLayouter<'a> { upcoming_headers: &grid.headers, pending_headers: Default::default(), current_row_height: None, + in_active_repeatable: false, current: Current { initial: regions.size, repeated_header_rows: 0, @@ -328,7 +339,9 @@ impl<'a> GridLayouter<'a> { self.layout_relative_row(engine, disambiguator, v, y)? } Sizing::Fr(v) => { - self.flush_orphans(); + if !self.in_active_repeatable { + self.flush_orphans(); + } self.lrows.push(Row::Fr(v, y, disambiguator)) } } @@ -1441,9 +1454,11 @@ impl<'a> GridLayouter<'a> { /// will be pushed for this particular row. It can be `false` for rows /// spanning multiple regions. fn push_row(&mut self, frame: Frame, y: usize, is_last: bool) { - // There is now a row after the rows equipped with orphan prevention, - // so no need to remove them anymore. - self.flush_orphans(); + if !self.in_active_repeatable { + // There is now a row after the rows equipped with orphan + // prevention, so no need to keep moving them anymore. + self.flush_orphans(); + } self.regions.size.y -= frame.height(); self.lrows.push(Row::Frame(frame, y, is_last)); } diff --git a/crates/typst-layout/src/grid/repeated.rs b/crates/typst-layout/src/grid/repeated.rs index bc1d82152..f764839e4 100644 --- a/crates/typst-layout/src/grid/repeated.rs +++ b/crates/typst-layout/src/grid/repeated.rs @@ -57,11 +57,20 @@ impl<'a> GridLayouter<'a> { y: usize, engine: &mut Engine, disambiguator: usize, + as_short_lived: bool, ) -> SourceResult> { let previous_row_height = std::mem::replace(&mut self.current_row_height, Some(Abs::zero())); + let previous_in_active_repeatable = + std::mem::replace(&mut self.in_active_repeatable, !as_short_lived); + self.layout_row(y, engine, disambiguator)?; + _ = std::mem::replace( + &mut self.in_active_repeatable, + previous_in_active_repeatable, + ); + Ok(std::mem::replace(&mut self.current_row_height, previous_row_height)) } @@ -73,11 +82,13 @@ impl<'a> GridLayouter<'a> { header: &Header, engine: &mut Engine, disambiguator: usize, + as_short_lived: bool, ) -> SourceResult { let mut header_height = Abs::zero(); for y in header.range() { - header_height += - self.layout_header_row(y, engine, disambiguator)?.unwrap_or_default(); + header_height += self + .layout_header_row(y, engine, disambiguator, as_short_lived)? + .unwrap_or_default(); } Ok(header_height) } @@ -270,7 +281,8 @@ impl<'a> GridLayouter<'a> { // 'layout_row' so this is fine. let mut i = 0; while let Some(&header) = self.repeating_headers.get(i) { - let header_height = self.layout_header_rows(header, engine, disambiguator)?; + let header_height = + self.layout_header_rows(header, engine, disambiguator, false)?; self.current.header_height += header_height; self.current.repeating_header_height += header_height; @@ -307,7 +319,7 @@ impl<'a> GridLayouter<'a> { for header in self.pending_headers { let header_height = - self.layout_header_rows(header.unwrap(), engine, disambiguator)?; + self.layout_header_rows(header.unwrap(), engine, disambiguator, false)?; self.current.header_height += header_height; if matches!(header, Repeatable::Repeated(_)) { self.current.repeating_header_height += header_height; @@ -365,7 +377,8 @@ impl<'a> GridLayouter<'a> { let initial_row_count = self.lrows.len(); for header in headers { - let header_height = self.layout_header_rows(header.unwrap(), engine, 0)?; + let header_height = + self.layout_header_rows(header.unwrap(), engine, 0, false)?; // Only store this header height if it is actually going to // become a pending header. Otherwise, pretend it's not a @@ -483,10 +496,24 @@ impl<'a> GridLayouter<'a> { // anyway, so this is mostly for correctness. self.regions.size.y += self.current.footer_height; + let repeats = self + .grid + .footer + .as_ref() + .is_some_and(|f| matches!(f, Repeatable::Repeated(_))); let footer_len = self.grid.rows.len() - footer.start; self.unbreakable_rows_left += footer_len; + for y in footer.start..self.grid.rows.len() { + let previous_in_active_repeatable = + std::mem::replace(&mut self.in_active_repeatable, repeats); + self.layout_row(y, engine, disambiguator)?; + + _ = std::mem::replace( + &mut self.in_active_repeatable, + previous_in_active_repeatable, + ); } Ok(()) diff --git a/tests/ref/grid-subheaders-non-repeating-header-before-multi-page-row.png b/tests/ref/grid-subheaders-non-repeating-header-before-multi-page-row.png new file mode 100644 index 000000000..3db97f782 Binary files /dev/null and b/tests/ref/grid-subheaders-non-repeating-header-before-multi-page-row.png differ diff --git a/tests/suite/layout/grid/subheaders.typ b/tests/suite/layout/grid/subheaders.typ index 9cd42bba3..d4d2fba14 100644 --- a/tests/suite/layout/grid/subheaders.typ +++ b/tests/suite/layout/grid/subheaders.typ @@ -684,6 +684,17 @@ grid.cell(x: 0)[end], ) +--- grid-subheaders-non-repeating-header-before-multi-page-row --- +#set page(height: 6em) +#grid( + grid.header( + repeat: false, + [h] + ), + [row #colbreak() row] +) + + --- grid-subheaders-short-lived-no-orphan-prevention --- // No orphan prevention for short-lived headers. #set page(height: 8em)