From e586cffa6c928774643d273ce63b8e15994e96b0 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Fri, 4 Apr 2025 21:30:04 -0300 Subject: [PATCH] finish region adjustments - flush pending headers - properly layout headers at region start --- crates/typst-layout/src/grid/layouter.rs | 81 ++++++++++++++---------- crates/typst-layout/src/grid/repeated.rs | 30 +++------ 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/crates/typst-layout/src/grid/layouter.rs b/crates/typst-layout/src/grid/layouter.rs index 6a1eb9614..8bdbda6e8 100644 --- a/crates/typst-layout/src/grid/layouter.rs +++ b/crates/typst-layout/src/grid/layouter.rs @@ -15,6 +15,7 @@ use typst_library::visualize::Geometry; use typst_syntax::Span; use typst_utils::{MaybeReverseIter, Numeric}; +use super::repeated::HeadersToLayout; use super::{ generate_line_segments, hline_stroke_at_column, layout_cell, vline_stroke_at_row, LineSegment, Rowspan, UnbreakableRowGroup, @@ -1388,30 +1389,19 @@ impl<'a> GridLayouter<'a> { self.lrows.pop().unwrap(); } - // If no rows other than the footer have been laid out so far, and - // there are rows beside the footer, then don't lay it out at all. - // This check doesn't apply, and is thus overridden, when there is a - // header. - let mut footer_would_be_orphan = self.lrows.is_empty() - && !in_last_with_offset( - self.regions, - self.header_height + self.footer_height, - ) - && self - .grid - .footer - .as_ref() - .and_then(Repeatable::as_repeated) - .is_some_and(|footer| footer.start != 0); - - if let Some(last_header) = self.repeating_headers.last() { + let footer_would_be_widow = if let Some(last_header) = self + .pending_headers + .last() + .map(Repeatable::unwrap) + .or_else(|| self.repeating_headers.last().map(|h| *h)) + { if self.grid.rows.len() > last_header.end && self .grid .footer .as_ref() .and_then(Repeatable::as_repeated) - .is_none_or(|footer| footer.start != header.end) + .is_none_or(|footer| footer.start != last_header.end) && self.lrows.last().is_some_and(|row| row.index() < last_header.end) && !in_last_with_offset( self.regions, @@ -1421,19 +1411,41 @@ impl<'a> GridLayouter<'a> { // Header and footer would be alone in this region, but there are more // rows beyond the header and the footer. Push an empty region. self.lrows.clear(); - footer_would_be_orphan = true; + true + } else { + false } - } + } else if let Some(Repeatable::Repeated(footer)) = &self.grid.footer { + // If no rows other than the footer have been laid out so far, and + // there are rows beside the footer, then don't lay it out at all. + // (Similar check from above, but for the case without headers.) + // TODO: widow prevention for non-repeated footers with a similar + // mechanism / when implementing multiple footers. + self.lrows.is_empty() + && !in_last_with_offset( + self.regions, + self.header_height + self.footer_height, + ) + && footer.start != 0 + } else { + false + }; let mut laid_out_footer_start = None; - if let Some(Repeatable::Repeated(footer)) = &self.grid.footer { - // Don't layout the footer if it would be alone with the header in - // the page, and don't layout it twice. - if !footer_would_be_orphan - && self.lrows.iter().all(|row| row.index() < footer.start) - { - laid_out_footer_start = Some(footer.start); - self.layout_footer(footer, engine, self.finished.len())?; + if !footer_would_be_widow { + // Did not trigger automatic header orphan / footer widow check. + // This means pending headers have successfully been placed once + // without hitting orphan prevention, so they may now be moved into + // repeating headers. + self.flush_pending_headers(); + + if let Some(Repeatable::Repeated(footer)) = &self.grid.footer { + // Don't layout the footer if it would be alone with the header in + // the page (hence the widow check), and don't layout it twice. + if self.lrows.iter().all(|row| row.index() < footer.start) { + laid_out_footer_start = Some(footer.start); + self.layout_footer(footer, engine, self.finished.len())?; + } } } @@ -1569,13 +1581,16 @@ impl<'a> GridLayouter<'a> { self.prepare_footer(footer, engine, disambiguator)?; } - if !self.repeating_headers.is_empty() { - // Add a header to the new region. - self.layout_headers(engine, disambiguator)?; - } - // Ensure rows don't try to overrun the footer. + // Note that header layout will only subtract this again if it has + // to skip regions to fit headers, so there is no risk of + // subtracting this twice. self.regions.size.y -= self.footer_height; + + if !self.repeating_headers.is_empty() || !self.pending_headers.is_empty() { + // Add headers to the new region. + self.layout_headers(HeadersToLayout::RepeatingAndPending, engine)?; + } } Ok(()) diff --git a/crates/typst-layout/src/grid/repeated.rs b/crates/typst-layout/src/grid/repeated.rs index 9968234c4..fac2efa31 100644 --- a/crates/typst-layout/src/grid/repeated.rs +++ b/crates/typst-layout/src/grid/repeated.rs @@ -96,23 +96,16 @@ impl<'a> GridLayouter<'a> { } pub fn flush_pending_headers(&mut self) { - debug_assert!(!self.upcoming_headers.is_empty()); - debug_assert!(self.pending_header_end > 0); - let headers = self.pending_headers(); - - let [first_header, ..] = headers else { - return; - }; - - self.repeating_headers.truncate( - self.repeating_headers - .partition_point(|h| h.level < first_header.unwrap().level), - ); - - for header in self.pending_headers() { + for header in self.pending_headers { if let Repeatable::Repeated(header) = header { // Vector remains sorted by increasing levels: - // - It was sorted before, so the truncation above only keeps + // - 'pending_headers' themselves are sorted, since we only + // push non-mutually-conflicting headers at a time. + // - Before pushing new pending headers in + // 'layout_new_pending_headers', we truncate repeating headers + // to remove anything with the same or higher levels as the + // first pending header. + // - Assuming it was sorted before, that truncation only keeps // elements with a lower level. // - Therefore, by pushing this header to the end, it will have // a level larger than all the previous headers, and is thus @@ -121,12 +114,7 @@ impl<'a> GridLayouter<'a> { } } - self.upcoming_headers = self - .upcoming_headers - .get(self.pending_header_end..) - .unwrap_or_default(); - - self.pending_header_end = 0; + self.pending_headers = Default::default(); } pub fn bump_repeating_headers(&mut self) {