move per-region state to Current

This commit is contained in:
PgBiel 2025-04-19 20:07:15 -03:00
parent 1e3719a9ba
commit 9b5c77a3a3
3 changed files with 122 additions and 105 deletions

View File

@ -43,6 +43,36 @@ pub struct GridLayouter<'a> {
/// Rowspans not yet laid out because not all of their spanned rows were
/// laid out yet.
pub(super) rowspans: Vec<Rowspan>,
/// Grid layout state for the current region.
pub(super) current: Current,
/// Frames for finished regions.
pub(super) finished: Vec<Frame>,
/// The amount and height of header rows on each finished region.
pub(super) finished_header_rows: Vec<FinishedHeaderRowInfo>,
/// Whether this is an RTL grid.
pub(super) is_rtl: bool,
/// Currently repeating headers, one per level.
/// Sorted by increasing levels.
///
/// 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>,
/// Headers, repeating or not, awaiting their first successful layout.
/// Sorted by increasing levels.
pub(super) pending_headers: &'a [Repeatable<Header>],
pub(super) upcoming_headers: &'a [Repeatable<Header>],
/// If this is `Some`, this will receive the currently laid out row's
/// height if it is auto or relative. This is used for header height
/// calculation.
/// TODO: consider refactoring this into something nicer.
pub(super) current_row_height: Option<Abs>,
/// The span of the grid element.
pub(super) span: Span,
}
/// Grid layout state for the current region. This should be reset or updated
/// on each region break.
pub struct Current {
/// The initial size of the current region before we started subtracting.
pub(super) initial: Size,
/// The amount of repeated header rows at the start of the current region.
@ -68,32 +98,6 @@ pub struct GridLayouter<'a> {
///
/// A value of zero indicates no headers were placed.
pub(super) current_last_repeated_header_end: usize,
/// Frames for finished regions.
pub(super) finished: Vec<Frame>,
/// The amount and height of header rows on each finished region.
pub(super) finished_header_rows: Vec<FinishedHeaderRowInfo>,
/// Whether this is an RTL grid.
pub(super) is_rtl: bool,
/// Currently repeating headers, one per level.
/// Sorted by increasing levels.
///
/// 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>,
/// Headers, repeating or not, awaiting their first successful layout.
/// Sorted by increasing levels.
pub(super) pending_headers: &'a [Repeatable<Header>],
pub(super) upcoming_headers: &'a [Repeatable<Header>],
/// The height for each repeating header that was placed in this region.
/// Note that this includes headers not at the top of the region (pending
/// headers), and excludes headers removed by virtue of a new, conflicting
/// header being found.
pub(super) repeating_header_heights: Vec<Abs>,
/// If this is `Some`, this will receive the currently laid out row's
/// height if it is auto or relative. This is used for header height
/// calculation.
/// TODO: consider refactoring this into something nicer.
pub(super) current_row_height: Option<Abs>,
/// Stores the length of `lrows` before a sequence of trailing rows
/// equipped with orphan prevention were laid out. In this case, if no more
/// rows are laid out after those rows before the region ends, the rows
@ -116,11 +120,14 @@ pub struct GridLayouter<'a> {
/// In particular, non-repeating headers only occupy the initial region,
/// but disappear on new regions, so they can be ignored.
pub(super) repeating_header_height: Abs,
/// The height for each repeating header that was placed in this region.
/// Note that this includes headers not at the top of the region (pending
/// headers), and excludes headers removed by virtue of a new, conflicting
/// header being found.
pub(super) repeating_header_heights: Vec<Abs>,
/// The simulated footer height for this region.
/// The simulation occurs before any rows are laid out for a region.
pub(super) footer_height: Abs,
/// The span of the grid element.
pub(super) span: Span,
}
#[derive(Debug, Default)]
@ -188,21 +195,23 @@ impl<'a> GridLayouter<'a> {
lrows: vec![],
unbreakable_rows_left: 0,
rowspans: vec![],
initial: regions.size,
current_repeating_header_rows: 0,
current_last_repeated_header_end: 0,
finished: vec![],
finished_header_rows: vec![],
is_rtl: TextElem::dir_in(styles) == Dir::RTL,
repeating_headers: vec![],
upcoming_headers: &grid.headers,
repeating_header_heights: vec![],
pending_headers: Default::default(),
lrows_orphan_snapshot: None,
current_row_height: None,
header_height: Abs::zero(),
repeating_header_height: Abs::zero(),
footer_height: Abs::zero(),
current: Current {
initial: regions.size,
current_repeating_header_rows: 0,
current_last_repeated_header_end: 0,
lrows_orphan_snapshot: None,
header_height: Abs::zero(),
repeating_header_height: Abs::zero(),
repeating_header_heights: vec![],
footer_height: Abs::zero(),
},
span,
}
}
@ -215,7 +224,7 @@ 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)?;
self.regions.size.y -= self.footer_height;
self.regions.size.y -= self.current.footer_height;
}
let mut y = 0;
@ -344,7 +353,7 @@ impl<'a> GridLayouter<'a> {
self.layout_relative_row(engine, disambiguator, v, y)?
}
Sizing::Fr(v) => {
self.lrows_orphan_snapshot = None;
self.current.lrows_orphan_snapshot = None;
self.lrows.push(Row::Fr(v, y, disambiguator))
}
}
@ -1094,7 +1103,7 @@ impl<'a> GridLayouter<'a> {
target.set_max(
region.y
- if i > 0 {
self.repeating_header_height + self.footer_height
self.current.repeating_header_height + self.current.footer_height
} else {
Abs::zero()
},
@ -1325,7 +1334,7 @@ impl<'a> GridLayouter<'a> {
&& !self.regions.size.y.fits(height)
&& may_progress_with_offset(
self.regions,
self.header_height + self.footer_height,
self.current.header_height + self.current.footer_height,
)
{
self.finish_region(engine, false)?;
@ -1459,7 +1468,7 @@ impl<'a> GridLayouter<'a> {
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.lrows_orphan_snapshot = None;
self.current.lrows_orphan_snapshot = None;
self.regions.size.y -= frame.height();
self.lrows.push(Row::Frame(frame, y, is_last));
}
@ -1470,11 +1479,11 @@ impl<'a> GridLayouter<'a> {
engine: &mut Engine,
last: bool,
) -> SourceResult<()> {
if let Some(orphan_snapshot) = self.lrows_orphan_snapshot.take() {
if let Some(orphan_snapshot) = self.current.lrows_orphan_snapshot.take() {
if !last {
self.lrows.truncate(orphan_snapshot);
self.current_repeating_header_rows =
self.current_repeating_header_rows.min(orphan_snapshot);
self.current.current_repeating_header_rows =
self.current.current_repeating_header_rows.min(orphan_snapshot);
}
}
@ -1485,11 +1494,12 @@ impl<'a> GridLayouter<'a> {
{
// Remove the last row in the region if it is a gutter row.
self.lrows.pop().unwrap();
self.current_repeating_header_rows =
self.current_repeating_header_rows.min(self.lrows.len());
self.current.current_repeating_header_rows =
self.current.current_repeating_header_rows.min(self.lrows.len());
}
let footer_would_be_widow = if let Some(last_header_row) = self
.current
.current_repeating_header_rows
.checked_sub(1)
.and_then(|last_header_index| self.lrows.get(last_header_index))
@ -1502,21 +1512,21 @@ impl<'a> GridLayouter<'a> {
.as_ref()
.and_then(Repeatable::as_repeated)
.is_none_or(|footer| footer.start != last_header_end)
&& self.lrows.len() == self.current_repeating_header_rows
&& self.lrows.len() == self.current.current_repeating_header_rows
&& may_progress_with_offset(
self.regions,
// Since we're trying to find a region where to place all
// repeating + pending headers, it makes sense to use
// 'header_height' and include even non-repeating pending
// headers for this check.
self.header_height + self.footer_height,
self.current.header_height + self.current.footer_height,
)
{
// 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();
self.current_last_repeated_header_end = 0;
self.current_repeating_header_rows = 0;
self.current.current_last_repeated_header_end = 0;
self.current.current_repeating_header_rows = 0;
true
} else {
false
@ -1533,7 +1543,7 @@ impl<'a> GridLayouter<'a> {
// This header height isn't doing much as we just confirmed
// that there are no headers in this region, but let's keep
// it here for correctness. It will add zero anyway.
self.header_height + self.footer_height,
self.current.header_height + self.current.footer_height,
)
&& footer.start != 0
} else {
@ -1564,9 +1574,9 @@ impl<'a> GridLayouter<'a> {
// Determine the size of the grid in this region, expanding fully if
// there are fr rows.
let mut size = Size::new(self.width, used).min(self.initial);
if fr.get() > 0.0 && self.initial.y.is_finite() {
size.y = self.initial.y;
let mut size = Size::new(self.width, used).min(self.current.initial);
if fr.get() > 0.0 && self.current.initial.y.is_finite() {
size.y = self.current.initial.y;
}
// The frame for the region.
@ -1588,7 +1598,7 @@ impl<'a> GridLayouter<'a> {
};
let height = frame.height();
if i < self.current_repeating_header_rows {
if i < self.current.current_repeating_header_rows {
header_row_height += height;
}
@ -1688,18 +1698,18 @@ impl<'a> GridLayouter<'a> {
output,
rrows,
FinishedHeaderRowInfo {
repeated: self.current_repeating_header_rows,
last_repeated_header_end: self.current_last_repeated_header_end,
repeated: self.current.current_repeating_header_rows,
last_repeated_header_end: self.current.current_last_repeated_header_end,
height: header_row_height,
},
);
if !last {
self.current_repeating_header_rows = 0;
self.current_last_repeated_header_end = 0;
self.header_height = Abs::zero();
self.repeating_header_height = Abs::zero();
self.repeating_header_heights.clear();
self.current.current_repeating_header_rows = 0;
self.current.current_last_repeated_header_end = 0;
self.current.header_height = Abs::zero();
self.current.repeating_header_height = Abs::zero();
self.current.repeating_header_heights.clear();
let disambiguator = self.finished.len();
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
@ -1710,7 +1720,7 @@ impl<'a> GridLayouter<'a> {
// 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;
self.regions.size.y -= self.current.footer_height;
if !self.repeating_headers.is_empty() || !self.pending_headers.is_empty() {
// Add headers to the new region.
@ -1732,14 +1742,14 @@ impl<'a> GridLayouter<'a> {
self.finished.push(output);
self.rrows.push(resolved_rows);
self.regions.next();
self.initial = self.regions.size;
self.current.initial = self.regions.size;
if !self.grid.headers.is_empty() {
self.finished_header_rows.push(header_row_info);
}
// Ensure orphan prevention is handled before resolving rows.
debug_assert!(self.lrows_orphan_snapshot.is_none());
debug_assert!(self.current.lrows_orphan_snapshot.is_none());
}
}

View File

@ -142,15 +142,16 @@ impl<'a> GridLayouter<'a> {
// Ensure upcoming rows won't see that these headers will occupy any
// space in future regions anymore.
for removed_height in self.repeating_header_heights.drain(first_conflicting_pos..)
for removed_height in
self.current.repeating_header_heights.drain(first_conflicting_pos..)
{
self.repeating_header_height -= removed_height;
self.current.repeating_header_height -= removed_height;
}
// Non-repeating headers stop at the pending stage for orphan
// prevention only. Flushing pending headers, so those will no longer
// appear in a future region.
self.header_height = self.repeating_header_height;
self.current.header_height = self.current.repeating_header_height;
// Let's try to place them at least once.
// This might be a waste as we could generate an orphan and thus have
@ -223,7 +224,7 @@ impl<'a> GridLayouter<'a> {
// available size for consistency with the first region, so we
// need to consider the footer when evaluating if skipping yet
// another region would make a difference.
self.footer_height,
self.current.footer_height,
)
{
// Advance regions without any output until we can place the
@ -238,7 +239,7 @@ impl<'a> GridLayouter<'a> {
// if 'full' changes? (Assuming height doesn't change for now...)
skipped_region = true;
self.regions.size.y -= self.footer_height;
self.regions.size.y -= self.current.footer_height;
}
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
@ -246,11 +247,11 @@ impl<'a> GridLayouter<'a> {
// Simulate the footer again; the region's 'full' might have
// changed.
// TODO: maybe this should go in the loop, a bit hacky as is...
self.regions.size.y += self.footer_height;
self.footer_height = self
self.regions.size.y += self.current.footer_height;
self.current.footer_height = self
.simulate_footer(footer, &self.regions, engine, disambiguator)?
.height;
self.regions.size.y -= self.footer_height;
self.regions.size.y -= self.current.footer_height;
}
}
@ -265,22 +266,22 @@ impl<'a> GridLayouter<'a> {
// within 'layout_row'.
self.unbreakable_rows_left += repeating_header_rows + pending_header_rows;
self.current_last_repeated_header_end =
self.current.current_last_repeated_header_end =
self.repeating_headers.last().map(|h| h.end).unwrap_or_default();
// Reset the header height for this region.
// It will be re-calculated when laying out each header row.
self.header_height = Abs::zero();
self.repeating_header_height = Abs::zero();
self.repeating_header_heights.clear();
self.current.header_height = Abs::zero();
self.current.repeating_header_height = Abs::zero();
self.current.repeating_header_heights.clear();
// Use indices to avoid double borrow. We don't mutate headers in
// '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)?;
self.header_height += header_height;
self.repeating_header_height += header_height;
self.current.header_height += header_height;
self.current.repeating_header_height += header_height;
// We assume that this vector will be sorted according
// to increasing levels like 'repeating_headers' and
@ -300,26 +301,26 @@ impl<'a> GridLayouter<'a> {
// headers which have now stopped repeating. They are always at
// the end and new pending headers respect the existing sort,
// so the vector will remain sorted.
self.repeating_header_heights.push(header_height);
self.current.repeating_header_heights.push(header_height);
i += 1;
}
self.current_repeating_header_rows = self.lrows.len();
self.current.current_repeating_header_rows = self.lrows.len();
if !self.pending_headers.is_empty() {
// Restore snapshot: if pending headers placed again turn out to be
// orphans, remove their rows again.
self.lrows_orphan_snapshot = Some(self.lrows.len());
self.current.lrows_orphan_snapshot = Some(self.lrows.len());
}
for header in self.pending_headers {
let header_height =
self.layout_header_rows(header.unwrap(), engine, disambiguator)?;
self.header_height += header_height;
self.current.header_height += header_height;
if matches!(header, Repeatable::Repeated(_)) {
self.repeating_header_height += header_height;
self.repeating_header_heights.push(header_height);
self.current.repeating_header_height += header_height;
self.current.repeating_header_heights.push(header_height);
}
}
@ -359,7 +360,7 @@ impl<'a> GridLayouter<'a> {
// 'header_height == repeating_header_height' here
// (there won't be any pending headers at this point, other
// than the ones we are about to place).
self.header_height + self.footer_height,
self.current.header_height + self.current.footer_height,
)
{
// Note that, after the first region skip, the new headers will go
@ -382,10 +383,10 @@ impl<'a> GridLayouter<'a> {
// region, so multi-page rows and cells can effectively ignore
// this header.
if !short_lived {
self.header_height += header_height;
self.current.header_height += header_height;
if matches!(header, Repeatable::Repeated(_)) {
self.repeating_header_height += header_height;
self.repeating_header_heights.push(header_height);
self.current.repeating_header_height += header_height;
self.current.repeating_header_heights.push(header_height);
}
}
}
@ -393,7 +394,7 @@ impl<'a> GridLayouter<'a> {
// Remove new headers at the end of the region if upcoming child doesn't fit.
// TODO: Short lived if footer comes afterwards
if !short_lived {
self.lrows_orphan_snapshot = Some(initial_row_count);
self.current.lrows_orphan_snapshot = Some(initial_row_count);
}
Ok(())
@ -466,7 +467,7 @@ impl<'a> GridLayouter<'a> {
// That is unnecessary at the moment as 'prepare_footers' is only
// called at the start of the region, but what about when we can have
// footers in the middle of the region? Let's think about this then.
self.footer_height = if skipped_region {
self.current.footer_height = if skipped_region {
// Simulate the footer again; the region's 'full' might have
// changed.
self.simulate_footer(footer, &self.regions, engine, disambiguator)?
@ -489,7 +490,7 @@ impl<'a> GridLayouter<'a> {
// Ensure footer rows have their own height available.
// Won't change much as we're creating an unbreakable row group
// anyway, so this is mostly for correctness.
self.regions.size.y += self.footer_height;
self.regions.size.y += self.current.footer_height;
let footer_len = self.grid.rows.len() - footer.start;
self.unbreakable_rows_left += footer_len;

View File

@ -263,7 +263,7 @@ impl GridLayouter<'_> {
// due to orphan/widow prevention, which explains the usage of
// 'header_height' (include non-repeating but pending headers) rather
// than 'repeating_header_height'.
self.header_height + self.footer_height,
self.current.header_height + self.current.footer_height,
)
{
self.finish_region(engine, false)?;
@ -422,7 +422,9 @@ impl GridLayouter<'_> {
let mapped_regions = self.regions.map(&mut custom_backlog, |size| {
Size::new(
size.x,
size.y - self.repeating_header_height - self.footer_height,
size.y
- self.current.repeating_header_height
- self.current.footer_height,
)
});
@ -535,7 +537,7 @@ impl GridLayouter<'_> {
// and unbreakable rows in general, so there is no risk
// of accessing an incomplete list of rows.
let initial_header_height = self.lrows
[..self.current_repeating_header_rows]
[..self.current.current_repeating_header_rows]
.iter()
.map(|row| match row {
Row::Frame(frame, _, _) => frame.height(),
@ -543,7 +545,9 @@ impl GridLayouter<'_> {
})
.sum();
self.initial.y - initial_header_height - self.footer_height
self.current.initial.y
- initial_header_height
- self.current.footer_height
} else {
// When measuring unbreakable auto rows, infinite
// height is available for content to expand.
@ -559,7 +563,8 @@ impl GridLayouter<'_> {
// Assume only repeating headers will survive starting at
// the next region.
let backlog = self.regions.backlog.iter().map(|&size| {
size - self.repeating_header_height - self.footer_height
size - self.current.repeating_header_height
- self.current.footer_height
});
heights_up_to_current_region.chain(backlog).collect::<Vec<_>>()
@ -574,10 +579,10 @@ impl GridLayouter<'_> {
height = *rowspan_height;
backlog = None;
full = rowspan_full;
last = self
.regions
.last
.map(|size| size - self.repeating_header_height - self.footer_height);
last = self.regions.last.map(|size| {
size - self.current.repeating_header_height
- self.current.footer_height
});
} else {
// The rowspan started in the current region, as its vector
// of heights in regions is currently empty.
@ -782,7 +787,8 @@ impl GridLayouter<'_> {
// Subtract the repeating header and footer height, since that's
// the height we used when subtracting from the region backlog's
// heights while measuring cells.
simulated_regions.size.y -= self.repeating_header_height + self.footer_height;
simulated_regions.size.y -=
self.current.repeating_header_height + self.current.footer_height;
}
if let Some(original_last_resolved_size) = last_resolved_size {
@ -921,8 +927,8 @@ impl GridLayouter<'_> {
// rowspan, since headers and footers are unbreakable, so
// assuming the repeating header height and footer height
// won't change is safe.
self.repeating_header_height,
self.footer_height,
self.current.repeating_header_height,
self.current.footer_height,
);
let total_spanned_height = rowspan_simulator.simulate_rowspan_layout(
@ -1006,7 +1012,7 @@ impl GridLayouter<'_> {
extra_amount_to_grow -= simulated_regions.size.y.max(Abs::zero());
simulated_regions.next();
simulated_regions.size.y -=
self.repeating_header_height + self.footer_height;
self.current.repeating_header_height + self.current.footer_height;
disambiguator += 1;
}
simulated_regions.size.y -= extra_amount_to_grow;