use headers to layout enum on layout_headers

- This will allow us to tell layout_headers to just layout headers that are already repeating, plus pending headers which are waiting for layout.
This commit is contained in:
PgBiel 2025-04-04 21:22:12 -03:00
parent af35e287af
commit 63055c8c83

View File

@ -8,6 +8,11 @@ use typst_library::layout::{Abs, Axes, Frame, Regions};
use super::layouter::GridLayouter; use super::layouter::GridLayouter;
use super::rowspans::UnbreakableRowGroup; use super::rowspans::UnbreakableRowGroup;
pub enum HeadersToLayout<'a> {
RepeatingAndPending,
NewHeaders(&'a [Repeatable<Header>]),
}
impl<'a> GridLayouter<'a> { impl<'a> GridLayouter<'a> {
pub fn place_new_headers( pub fn place_new_headers(
&mut self, &mut self,
@ -39,15 +44,16 @@ impl<'a> GridLayouter<'a> {
self.layout_new_pending_headers(non_conflicting_headers, engine); 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_headers( self.layout_headers(
non_conflicting_headers.into_iter().map(Repeatable::unwrap), // Using 'chunks_exact", we pass a slice of length one instead
true, // of a reference for type consistency.
engine, HeadersToLayout::NewHeaders(conflicting_header),
)?;
for conflicting_header in conflicting_headers {
self.layout_headers(
std::iter::once(conflicting_header.unwrap()),
true,
engine, engine,
)? )?
} }
@ -81,11 +87,12 @@ impl<'a> GridLayouter<'a> {
// This might be a waste as we could generate an orphan and thus have // This might be a waste as we could generate an orphan and thus have
// to try to place old and new headers all over again, but that happens // to try to place old and new headers all over again, but that happens
// for every new region anyway, so it's rather unavoidable. // for every new region anyway, so it's rather unavoidable.
self.layout_headers(headers.iter().map(Repeatable::unwrap), true, engine); self.layout_headers(HeadersToLayout::NewHeaders(headers), engine);
// After the first subsequent row is laid out, move to repeating, as // After the first subsequent row is laid out, move to repeating, as
// it's then confirmed the headers won't be moved due to orphan // it's then confirmed the headers won't be moved due to orphan
// prevention anymore. // prevention anymore.
self.pending_headers = headers;
} }
pub fn flush_pending_headers(&mut self) { pub fn flush_pending_headers(&mut self) {
@ -156,22 +163,33 @@ impl<'a> GridLayouter<'a> {
/// footers. /// footers.
pub fn layout_headers( pub fn layout_headers(
&mut self, &mut self,
headers: impl Clone + IntoIterator<Item = &'a Header>, headers: HeadersToLayout<'a>,
include_repeating: bool,
engine: &mut Engine, engine: &mut Engine,
) -> SourceResult<()> { ) -> SourceResult<()> {
// Generate different locations for content in headers across its // Generate different locations for content in headers across its
// repetitions by assigning a unique number for each one. // repetitions by assigning a unique number for each one.
let disambiguator = self.finished.len(); let disambiguator = self.finished.len();
// At first, only consider the height of the given headers. However, // At first, only consider the height of the given headers. However,
// for upcoming regions, we will have to consider repeating headers as // for upcoming regions, we will have to consider repeating headers as
// well. // well.
let mut header_height = self.simulate_header_height( let mut header_height = match headers {
headers.clone(), HeadersToLayout::RepeatingAndPending => self.simulate_header_height(
self.repeating_headers
.iter()
.map(Deref::deref)
.chain(self.pending_headers.into_iter().map(Repeatable::unwrap)),
&self.regions, &self.regions,
engine, engine,
disambiguator, disambiguator,
)?; )?,
HeadersToLayout::NewHeaders(headers) => self.simulate_header_height(
headers.into_iter().map(Repeatable::unwrap),
&self.regions,
engine,
disambiguator,
)?,
};
// We already take the footer into account below. // We already take the footer into account below.
// While skipping regions, footer height won't be automatically // While skipping regions, footer height won't be automatically
@ -187,17 +205,23 @@ impl<'a> GridLayouter<'a> {
// TODO: re-calculate heights of headers and footers on each region // TODO: re-calculate heights of headers and footers on each region
// if 'full'changes? (Assuming height doesn't change for now...) // if 'full'changes? (Assuming height doesn't change for now...)
if include_repeating && !skipped_region { if !skipped_region {
if let HeadersToLayout::NewHeaders(headers) = headers {
header_height = header_height =
// Laying out pending headers, so we have to consider the // Laying out new headers, so we have to consider the
// combined height of already repeating headers as well. // combined height of already repeating headers as well
// when beginning a new region.
self.simulate_header_height( self.simulate_header_height(
self.repeating_headers.iter().map(|h| *h).chain(headers.clone()), self.repeating_headers
.iter()
.map(|h| *h)
.chain(self.pending_headers.into_iter().chain(headers).map(Repeatable::unwrap)),
&self.regions, &self.regions,
engine, engine,
disambiguator, disambiguator,
)?; )?;
} }
}
skipped_region = true; skipped_region = true;
@ -225,16 +249,21 @@ impl<'a> GridLayouter<'a> {
// Group of headers is unbreakable. // Group of headers is unbreakable.
// Thus, no risk of 'finish_region' being recursively called from // Thus, no risk of 'finish_region' being recursively called from
// within 'layout_row'. // within 'layout_row'.
self.unbreakable_rows_left += total_header_row_count(headers.clone()); if let HeadersToLayout::NewHeaders(headers) = headers {
// Do this before laying out repeating and pending headers from a
// new region to make sure row code is aware that all of those
// headers should stay together!
self.unbreakable_rows_left +=
total_header_row_count(headers.into_iter().map(Repeatable::unwrap));
}
// Need to relayout ALL headers if we skip a region, not only the // Need to relayout ALL headers if we skip a region, not only the
// provided headers. // provided headers.
// TODO: maybe extract this into a function to share code with multiple // TODO: maybe extract this into a function to share code with multiple
// footers. // footers.
if include_repeating && skipped_region { if matches!(headers, HeadersToLayout::RepeatingAndPending) || skipped_region {
self.unbreakable_rows_left += self.unbreakable_rows_left +=
total_header_row_count(self.repeating_headers.iter().map(Deref::deref)); total_header_row_count(self.repeating_headers.iter().map(Deref::deref));
}
// Use indices to avoid double borrow. We don't mutate headers in // Use indices to avoid double borrow. We don't mutate headers in
// 'layout_row' so this is fine. // 'layout_row' so this is fine.
@ -246,11 +275,21 @@ impl<'a> GridLayouter<'a> {
i += 1; i += 1;
} }
for header in headers { for header in self.pending_headers {
for y in header.range() { for y in header.unwrap().range() {
self.layout_row(y, engine, disambiguator)?; self.layout_row(y, engine, disambiguator)?;
} }
} }
}
if let HeadersToLayout::NewHeaders(headers) = headers {
for header in headers {
for y in header.unwrap().range() {
self.layout_row(y, engine, disambiguator)?;
}
}
}
Ok(()) Ok(())
} }