mark headers as short-lived during resolve

This commit is contained in:
PgBiel 2025-04-19 23:08:10 -03:00
parent af0c27cb98
commit 6e21eae3eb
3 changed files with 81 additions and 106 deletions

View File

@ -236,54 +236,12 @@ impl<'a> GridLayouter<'a> {
let mut y = 0; let mut y = 0;
let mut consecutive_header_count = 0; let mut consecutive_header_count = 0;
while y < self.grid.rows.len() { while y < self.grid.rows.len() {
if let Some(first_header) = if let Some(next_header) = self.upcoming_headers.get(consecutive_header_count)
self.upcoming_headers.get(consecutive_header_count)
{ {
if first_header.unwrap().range().contains(&y) { if next_header.unwrap().range().contains(&y) {
consecutive_header_count += 1; self.place_new_headers(&mut consecutive_header_count, engine)?;
y = next_header.unwrap().end;
// TODO: surely there is a better way to do this
match self.upcoming_headers.get(consecutive_header_count) {
// No more headers, so place the latest headers.
None => {
self.place_new_headers(
consecutive_header_count,
None,
engine,
)?;
consecutive_header_count = 0;
}
// Next header is not consecutive, so place the latest headers.
Some(next_header)
if next_header.unwrap().start > first_header.unwrap().end =>
{
self.place_new_headers(
consecutive_header_count,
None,
engine,
)?;
consecutive_header_count = 0;
}
// Next header is consecutive and conflicts with one or
// more of the latest consecutive headers, so we must
// place them before proceeding.
Some(next_header)
if next_header.unwrap().level
<= first_header.unwrap().level =>
{
self.place_new_headers(
consecutive_header_count,
Some(next_header),
engine,
)?;
consecutive_header_count = 0;
}
// Next header is a non-conflicting consecutive header.
// Keep collecting more headers.
_ => {}
}
y = first_header.unwrap().end;
// Skip header rows during normal layout. // Skip header rows during normal layout.
continue; continue;
} }

View File

@ -9,71 +9,44 @@ use super::rowspans::UnbreakableRowGroup;
impl<'a> GridLayouter<'a> { impl<'a> GridLayouter<'a> {
pub fn place_new_headers( pub fn place_new_headers(
&mut self, &mut self,
consecutive_header_count: usize, consecutive_header_count: &mut usize,
conflicting_header: Option<&Repeatable<Header>>,
engine: &mut Engine, engine: &mut Engine,
) -> SourceResult<()> { ) -> SourceResult<()> {
*consecutive_header_count += 1;
let (consecutive_headers, new_upcoming_headers) = let (consecutive_headers, new_upcoming_headers) =
self.upcoming_headers.split_at(consecutive_header_count); self.upcoming_headers.split_at(*consecutive_header_count);
if new_upcoming_headers.first().is_some_and(|next_header| {
consecutive_headers.last().is_none_or(|latest_header| {
!latest_header.unwrap().short_lived
&& next_header.unwrap().start == latest_header.unwrap().end
}) && !next_header.unwrap().short_lived
}) {
// More headers coming, so wait until we reach them.
// TODO: refactor
return Ok(());
}
self.upcoming_headers = new_upcoming_headers; self.upcoming_headers = new_upcoming_headers;
*consecutive_header_count = 0;
let (non_conflicting_headers, conflicting_headers) = match conflicting_header { // Layout short-lived headers immediately.
// Headers succeeded by end of grid or footer are short lived and if consecutive_headers.last().is_some_and(|h| h.unwrap().short_lived) {
// can be placed in separate regions (no orphan prevention). // No chance of orphans as we're immediately placing conflicting
// TODO: do this during grid resolving? // headers afterwards, which basically are not headers, for all intents
// might be needed for multiple footers. Or maybe not if we check // and purposes. It is therefore guaranteed that all new headers have
// "upcoming_footers" (O(1) here), however that looks like. // been placed at least once.
_ if consecutive_headers
.last()
.is_some_and(|x| x.unwrap().end == self.grid.rows.len())
|| self
.grid
.footer
.as_ref()
.zip(consecutive_headers.last())
.is_some_and(|(f, h)| f.unwrap().start == h.unwrap().end) =>
{
(Default::default(), consecutive_headers)
}
Some(conflicting_header) => {
// All immediately conflicting headers will
// be laid out without orphan prevention.
consecutive_headers.split_at(consecutive_headers.partition_point(|h| {
conflicting_header.unwrap().level > h.unwrap().level
}))
}
_ => (consecutive_headers, Default::default()),
};
self.layout_new_pending_headers(non_conflicting_headers, engine)?;
// Layout each conflicting header independently, without orphan
// prevention (as they don't go into 'pending_headers').
// These headers are short-lived as they are immediately followed by a
// header of the same or lower level, such that they never actually get
// to repeat.
for conflicting_header in conflicting_headers.chunks_exact(1) {
self.layout_new_headers(
// Using 'chunks_exact", we pass a slice of length one instead
// of a reference for type consistency.
// In addition, this is the only place where we layout
// short-lived headers.
conflicting_header,
true,
engine,
)?
}
// No chance of orphans as we're immediately placing conflicting
// headers afterwards, which basically are not headers, for all intents
// and purposes. It is therefore guaranteed that all new headers have
// been placed at least once.
if !conflicting_headers.is_empty() {
self.flush_pending_headers(); self.flush_pending_headers();
}
Ok(()) // Layout each conflicting header independently, without orphan
// prevention (as they don't go into 'pending_headers').
// These headers are short-lived as they are immediately followed by a
// header of the same or lower level, such that they never actually get
// to repeat.
self.layout_new_headers(consecutive_headers, true, engine)
} else {
self.layout_new_pending_headers(consecutive_headers, engine)
}
} }
/// Lays out a row while indicating that it should store its persistent /// Lays out a row while indicating that it should store its persistent

View File

@ -438,6 +438,12 @@ pub struct Header {
/// lower level header stops repeating, all higher level headers do as /// lower level header stops repeating, all higher level headers do as
/// well. /// well.
pub level: u32, pub level: u32,
/// Whether this header cannot be repeated nor should have orphan
/// prevention because it would be about to cease repetition, either
/// because it is followed by headers of conflicting levels, or because
/// it is at the end of the table (possibly followed by some footers at the
/// end).
pub short_lived: bool,
} }
impl Header { impl Header {
@ -469,12 +475,15 @@ impl Footer {
} }
} }
/// A possibly repeatable grid object. /// A possibly repeatable grid child.
///
/// It still exists even when not repeatable, but must not have additional /// It still exists even when not repeatable, but must not have additional
/// considerations by grid layout, other than for consistency (such as making /// considerations by grid layout, other than for consistency (such as making
/// a certain group of rows unbreakable). /// a certain group of rows unbreakable).
pub enum Repeatable<T> { pub enum Repeatable<T> {
/// The user asked this grid child to repeat.
Repeated(T), Repeated(T),
/// The user asked this grid child to not repeat.
NotRepeated(T), NotRepeated(T),
} }
@ -490,7 +499,7 @@ impl<T> Repeatable<T> {
} }
/// Gets the value inside this repeatable, regardless of whether /// Gets the value inside this repeatable, regardless of whether
/// it repeats. /// it repeats (mutably).
#[inline] #[inline]
pub fn unwrap_mut(&mut self) -> &mut T { pub fn unwrap_mut(&mut self) -> &mut T {
match self { match self {
@ -1540,8 +1549,29 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
end: group_range.end, end: group_range.end,
level: row_group.repeatable_level.get(), level: row_group.repeatable_level.get(),
// This can only change at a later iteration, if we
// find a conflicting header or footer right away.
short_lived: false,
}; };
// Mark consecutive headers right before this one as short
// lived if they would have a higher or equal level, as
// then they would immediately stop repeating during
// layout.
let mut consecutive_header_start = data.start;
for conflicting_header in
headers.iter_mut().rev().take_while(move |h| {
let conflicts = h.unwrap().end == consecutive_header_start
&& h.unwrap().level >= data.level;
consecutive_header_start = h.unwrap().start;
conflicts
})
{
conflicting_header.unwrap_mut().short_lived = true;
}
headers.push(if row_group.repeat { headers.push(if row_group.repeat {
Repeatable::Repeated(data) Repeatable::Repeated(data)
} else { } else {
@ -1826,6 +1856,20 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
} }
}); });
// Mark consecutive headers right before the end of the table, or the
// final footer, as short lived, given that there are no normal rows
// after them, so repeating them is pointless.
let mut consecutive_header_start =
footer.as_ref().map(|f| f.unwrap().start).unwrap_or(row_amount);
for header_at_the_end in headers.iter_mut().rev().take_while(move |h| {
let at_the_end = h.unwrap().end == consecutive_header_start;
consecutive_header_start = h.unwrap().start;
at_the_end
}) {
header_at_the_end.unwrap_mut().short_lived = true;
}
Ok(footer) Ok(footer)
} }