begin placing new headers

Considerations:
- Need to change layout headers algorithm to
  1. Place those headers
  2. But in a new region, also place other repeating headers
  3. Keep footer height up-to-date without much intervention
This commit is contained in:
PgBiel 2025-04-04 00:58:49 -03:00
parent b420588c19
commit 6029e6f3bb
2 changed files with 87 additions and 33 deletions

View File

@ -55,11 +55,9 @@ pub struct GridLayouter<'a> {
/// Note that some levels may be absent, in particular level 0, which does /// Note that some levels may be absent, in particular level 0, which does
/// not exist (so the first level is >= 1). /// not exist (so the first level is >= 1).
pub(super) repeating_headers: Vec<&'a Header>, pub(super) repeating_headers: Vec<&'a Header>,
/// End of sequence of consecutive compatible headers found so far. /// Headers, repeating or not, awaiting their first successful layout.
/// This is one position after the last index in `upcoming_headers`, so `0`
/// indicates no pending headers.
/// Sorted by increasing levels. /// Sorted by increasing levels.
pub(super) pending_header_end: usize, pub(super) pending_headers: &'a [Repeatable<Header>],
pub(super) upcoming_headers: &'a [Repeatable<Header>], pub(super) upcoming_headers: &'a [Repeatable<Header>],
/// The simulated header height. /// The simulated header height.
/// This field is reset in `layout_header` and properly updated by /// This field is reset in `layout_header` and properly updated by
@ -136,7 +134,7 @@ impl<'a> GridLayouter<'a> {
is_rtl: TextElem::dir_in(styles) == Dir::RTL, is_rtl: TextElem::dir_in(styles) == Dir::RTL,
repeating_headers: vec![], repeating_headers: vec![],
upcoming_headers: &grid.headers, upcoming_headers: &grid.headers,
pending_header_end: 0, pending_headers: Default::default(),
header_height: Abs::zero(), header_height: Abs::zero(),
footer_height: Abs::zero(), footer_height: Abs::zero(),
span, span,
@ -151,31 +149,34 @@ impl<'a> GridLayouter<'a> {
// Ensure rows in the first region will be aware of the possible // Ensure rows in the first region will be aware of the possible
// presence of the footer. // presence of the footer.
self.prepare_footer(footer, engine, 0)?; self.prepare_footer(footer, engine, 0)?;
if !matches!(
self.grid.headers.first(),
Some(Repeatable::Repeated(Header { start: 0, .. }))
) {
// No repeatable header at the very beginning, so we won't
// subtract it later.
self.regions.size.y -= self.footer_height; self.regions.size.y -= self.footer_height;
} }
}
let mut y = 0; let mut y = 0;
let mut consecutive_header_count = 0;
while y < self.grid.rows.len() { while y < self.grid.rows.len() {
if let Some(first_header) = self.upcoming_headers.first() { if let Some(first_header) =
self.upcoming_headers.get(consecutive_header_count)
{
if first_header.unwrap().range().contains(&y) { if first_header.unwrap().range().contains(&y) {
self.bump_pending_headers(); consecutive_header_count += 1;
if self.peek_upcoming_header().is_none_or(|h| { if self.upcoming_headers.get(consecutive_header_count).is_none_or(
|h| {
h.unwrap().start > y + 1 h.unwrap().start > y + 1
|| h.unwrap().level <= first_header.unwrap().level || h.unwrap().level <= first_header.unwrap().level
}) { },
) {
// Next row either isn't a header. or is in a // Next row either isn't a header. or is in a
// conflicting one, which is the sign that we need to go. // conflicting one, which is the sign that we need to go.
self.layout_headers(next_header, engine, 0)?; self.place_new_headers(
first_header,
consecutive_header_count,
engine,
);
consecutive_header_count = 0;
} }
y = first_header.end; y = first_header.unwrap().end;
// Skip header rows during normal layout. // Skip header rows during normal layout.
continue; continue;

View File

@ -1,4 +1,3 @@
use std::ops::ControlFlow;
use typst_library::diag::SourceResult; use typst_library::diag::SourceResult;
use typst_library::engine::Engine; use typst_library::engine::Engine;
@ -9,20 +8,74 @@ use super::layouter::GridLayouter;
use super::rowspans::UnbreakableRowGroup; use super::rowspans::UnbreakableRowGroup;
impl<'a> GridLayouter<'a> { impl<'a> GridLayouter<'a> {
#[inline] pub fn place_new_headers(
fn pending_headers(&self) -> &'a [Repeatable<Header>] { &mut self,
&self.upcoming_headers[..self.pending_header_end] first_header: &Repeatable<Header>,
consecutive_header_count: usize,
engine: &mut Engine,
) {
// Next row either isn't a header. or is in a
// conflicting one, which is the sign that we need to go.
let (consecutive_headers, new_upcoming_headers) =
self.upcoming_headers.split_at(consecutive_header_count);
self.upcoming_headers = new_upcoming_headers;
let (non_conflicting_headers, conflicting_headers) = match self
.upcoming_headers
.get(consecutive_header_count)
.map(Repeatable::unwrap)
{
Some(next_header) if next_header.level <= first_header.unwrap().level => {
// All immediately conflicting headers will
// be placed as normal rows.
consecutive_headers.split_at(
consecutive_headers
.partition_point(|h| next_header.level > h.unwrap().level),
)
}
_ => (consecutive_headers, Default::default()),
};
self.layout_new_pending_headers(non_conflicting_headers, engine);
self.layout_headers(non_conflicting_headers, engine, 0)?;
for conflicting_header in conflicting_headers {
self.simulate();
self.layout_headers(headers, engine, disambiguator)
}
} }
#[inline] /// Queues new pending headers for layout. Headers remain pending until
pub fn bump_pending_headers(&mut self) { /// they are successfully laid out in some page once. Then, they will be
debug_assert!(!self.upcoming_headers.is_empty()); /// moved to `repeating_headers`, at which point it is safe to stop them
self.pending_header_end += 1; /// from repeating at any time.
} fn layout_new_pending_headers(
&mut self,
headers: &'a [Repeatable<Header>],
engine: &mut Engine,
) {
let [first_header, ..] = headers else {
return;
};
// Assuming non-conflicting headers sorted by increasing y, this must
// be the header with the lowest level (sorted by increasing levels).
let first_level = first_header.unwrap().level;
#[inline] // Stop repeating conflicting headers.
pub fn peek_upcoming_header(&self) -> Option<&'a Repeatable<Header>> { // If we go to a new region before the pending headers fit alongside
self.upcoming_headers.get(self.pending_header_end) // their children, the old headers should not be displayed anymore.
self.repeating_headers
.truncate(self.repeating_headers.partition_point(|h| h.level < first_level));
// Let's try to place them at least once.
// 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
// for every new region anyway, so it's rather unavoidable.
self.layout_headers(headers.iter().map(Repeatable::unwrap), true, engine);
// 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
// prevention anymore.
} }
pub fn flush_pending_headers(&mut self) { pub fn flush_pending_headers(&mut self) {