diff --git a/crates/typst-layout/src/grid/layouter.rs b/crates/typst-layout/src/grid/layouter.rs index 52d42e732..3fd1d505f 100644 --- a/crates/typst-layout/src/grid/layouter.rs +++ b/crates/typst-layout/src/grid/layouter.rs @@ -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, + /// Grid layout state for the current region. + pub(super) current: Current, + /// Frames for finished regions. + pub(super) finished: Vec, + /// The amount and height of header rows on each finished region. + pub(super) finished_header_rows: Vec, + /// 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
], + pub(super) upcoming_headers: &'a [Repeatable
], + /// 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, + /// 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, - /// The amount and height of header rows on each finished region. - pub(super) finished_header_rows: Vec, - /// 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
], - pub(super) upcoming_headers: &'a [Repeatable
], - /// 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, - /// 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, /// 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, /// 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()); } } diff --git a/crates/typst-layout/src/grid/repeated.rs b/crates/typst-layout/src/grid/repeated.rs index 0d39659c5..1ec3725ee 100644 --- a/crates/typst-layout/src/grid/repeated.rs +++ b/crates/typst-layout/src/grid/repeated.rs @@ -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; diff --git a/crates/typst-layout/src/grid/rowspans.rs b/crates/typst-layout/src/grid/rowspans.rs index 22060c6aa..eaa6b5096 100644 --- a/crates/typst-layout/src/grid/rowspans.rs +++ b/crates/typst-layout/src/grid/rowspans.rs @@ -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::>() @@ -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;