finish region adjustments

- flush pending headers
- properly layout headers at region start
This commit is contained in:
PgBiel 2025-04-04 21:30:04 -03:00
parent 63055c8c83
commit e586cffa6c
2 changed files with 57 additions and 54 deletions

View File

@ -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,21 +1411,43 @@ 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 !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, and don't layout it twice.
if !footer_would_be_orphan
&& self.lrows.iter().all(|row| row.index() < footer.start)
{
// 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())?;
}
}
}
// Determine the height of existing rows in the region.
let mut used = Abs::zero();
@ -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(())

View File

@ -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) {