diff --git a/crates/typst-layout/src/grid/layouter.rs b/crates/typst-layout/src/grid/layouter.rs index e55a2f716..c2e66badb 100644 --- a/crates/typst-layout/src/grid/layouter.rs +++ b/crates/typst-layout/src/grid/layouter.rs @@ -55,11 +55,9 @@ pub struct GridLayouter<'a> { /// Note that some levels may be absent, in particular level 0, which does /// not exist (so the first level is >= 1). pub(super) repeating_headers: Vec<&'a Header>, - /// End of sequence of consecutive compatible headers found so far. - /// This is one position after the last index in `upcoming_headers`, so `0` - /// indicates no pending headers. + /// Headers, repeating or not, awaiting their first successful layout. /// Sorted by increasing levels. - pub(super) pending_header_end: usize, + pub(super) pending_headers: &'a [Repeatable
], pub(super) upcoming_headers: &'a [Repeatable
], /// The simulated header height. /// This field is reset in `layout_header` and properly updated by @@ -136,7 +134,7 @@ impl<'a> GridLayouter<'a> { is_rtl: TextElem::dir_in(styles) == Dir::RTL, repeating_headers: vec![], upcoming_headers: &grid.headers, - pending_header_end: 0, + pending_headers: Default::default(), header_height: Abs::zero(), footer_height: Abs::zero(), span, @@ -151,31 +149,34 @@ impl<'a> GridLayouter<'a> { // Ensure rows in the first region will be aware of the possible // presence of the footer. self.prepare_footer(footer, engine, 0)?; - if !matches!( - self.grid.headers.first(), - Some(Repeatable::Repeated(Header { start: 0, .. })) - ) { - // No repeatable header at the very beginning, so we won't - // subtract it later. - self.regions.size.y -= self.footer_height; - } + self.regions.size.y -= self.footer_height; } let mut y = 0; + let mut consecutive_header_count = 0; while y < self.grid.rows.len() { - if let Some(first_header) = self.upcoming_headers.first() { + if let Some(first_header) = + self.upcoming_headers.get(consecutive_header_count) + { if first_header.unwrap().range().contains(&y) { - self.bump_pending_headers(); + consecutive_header_count += 1; - if self.peek_upcoming_header().is_none_or(|h| { - h.unwrap().start > y + 1 - || h.unwrap().level <= first_header.unwrap().level - }) { + if self.upcoming_headers.get(consecutive_header_count).is_none_or( + |h| { + h.unwrap().start > y + 1 + || h.unwrap().level <= first_header.unwrap().level + }, + ) { // Next row either isn't a header. or is in a // conflicting one, which is the sign that we need to go. - self.layout_headers(next_header, engine, 0)?; + self.place_new_headers( + first_header, + consecutive_header_count, + engine, + ); + consecutive_header_count = 0; } - y = first_header.end; + y = first_header.unwrap().end; // Skip header rows during normal layout. continue; diff --git a/crates/typst-layout/src/grid/repeated.rs b/crates/typst-layout/src/grid/repeated.rs index fc364aad0..550a18d68 100644 --- a/crates/typst-layout/src/grid/repeated.rs +++ b/crates/typst-layout/src/grid/repeated.rs @@ -1,4 +1,3 @@ -use std::ops::ControlFlow; use typst_library::diag::SourceResult; use typst_library::engine::Engine; @@ -9,20 +8,74 @@ use super::layouter::GridLayouter; use super::rowspans::UnbreakableRowGroup; impl<'a> GridLayouter<'a> { - #[inline] - fn pending_headers(&self) -> &'a [Repeatable
] { - &self.upcoming_headers[..self.pending_header_end] + pub fn place_new_headers( + &mut self, + first_header: &Repeatable
, + consecutive_header_count: usize, + engine: &mut Engine, + ) { + // Next row either isn't a header. or is in a + // conflicting one, which is the sign that we need to go. + let (consecutive_headers, new_upcoming_headers) = + self.upcoming_headers.split_at(consecutive_header_count); + self.upcoming_headers = new_upcoming_headers; + + let (non_conflicting_headers, conflicting_headers) = match self + .upcoming_headers + .get(consecutive_header_count) + .map(Repeatable::unwrap) + { + Some(next_header) if next_header.level <= first_header.unwrap().level => { + // All immediately conflicting headers will + // be placed as normal rows. + consecutive_headers.split_at( + consecutive_headers + .partition_point(|h| next_header.level > h.unwrap().level), + ) + } + _ => (consecutive_headers, Default::default()), + }; + + self.layout_new_pending_headers(non_conflicting_headers, engine); + + self.layout_headers(non_conflicting_headers, engine, 0)?; + for conflicting_header in conflicting_headers { + self.simulate(); + self.layout_headers(headers, engine, disambiguator) + } } - #[inline] - pub fn bump_pending_headers(&mut self) { - debug_assert!(!self.upcoming_headers.is_empty()); - self.pending_header_end += 1; - } + /// Queues new pending headers for layout. Headers remain pending until + /// they are successfully laid out in some page once. Then, they will be + /// moved to `repeating_headers`, at which point it is safe to stop them + /// from repeating at any time. + fn layout_new_pending_headers( + &mut self, + headers: &'a [Repeatable
], + engine: &mut Engine, + ) { + let [first_header, ..] = headers else { + return; + }; + // Assuming non-conflicting headers sorted by increasing y, this must + // be the header with the lowest level (sorted by increasing levels). + let first_level = first_header.unwrap().level; - #[inline] - pub fn peek_upcoming_header(&self) -> Option<&'a Repeatable
> { - self.upcoming_headers.get(self.pending_header_end) + // Stop repeating conflicting headers. + // If we go to a new region before the pending headers fit alongside + // their children, the old headers should not be displayed anymore. + self.repeating_headers + .truncate(self.repeating_headers.partition_point(|h| h.level < first_level)); + + // Let's try to place them at least once. + // This might be a waste as we could generate an orphan and thus have + // to try to place old and new headers all over again, but that happens + // for every new region anyway, so it's rather unavoidable. + self.layout_headers(headers.iter().map(Repeatable::unwrap), true, engine); + + // After the first subsequent row is laid out, move to repeating, as + // it's then confirmed the headers won't be moved due to orphan + // prevention anymore. } pub fn flush_pending_headers(&mut self) {