initial multi heading progress

pending headers change
This commit is contained in:
PgBiel 2025-03-03 19:59:41 -03:00
parent 20179bbe7a
commit 4bd3abf44d
3 changed files with 170 additions and 26 deletions

View File

@ -3,7 +3,9 @@ use std::fmt::Debug;
use typst_library::diag::{bail, SourceResult};
use typst_library::engine::Engine;
use typst_library::foundations::{Resolve, StyleChain};
use typst_library::layout::grid::resolve::{Cell, CellGrid, LinePosition, Repeatable};
use typst_library::layout::grid::resolve::{
Cell, CellGrid, Header, LinePosition, Repeatable,
};
use typst_library::layout::{
Abs, Axes, Dir, Fr, Fragment, Frame, FrameItem, Length, Point, Region, Regions, Rel,
Size, Sizing,
@ -47,6 +49,18 @@ pub struct GridLayouter<'a> {
pub(super) finished: Vec<Frame>,
/// 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>,
/// End of sequence of consecutive compatible headers found so far.
/// This is one position after the last index in `upcoming_headers`, so `0`
/// indicates no pending headers.
/// Sorted by increasing levels.
pub(super) pending_header_end: usize,
pub(super) upcoming_headers: &'a [Repeatable<Header>],
/// The simulated header height.
/// This field is reset in `layout_header` and properly updated by
/// `layout_auto_row` and `layout_relative_row`, and should not be read
@ -120,6 +134,7 @@ impl<'a> GridLayouter<'a> {
initial: regions.size,
finished: vec![],
is_rtl: TextElem::dir_in(styles) == Dir::RTL,
upcoming_headers: &grid.headers,
header_height: Abs::zero(),
footer_height: Abs::zero(),
span,
@ -140,15 +155,31 @@ impl<'a> GridLayouter<'a> {
}
}
for y in 0..self.grid.rows.len() {
if let Some(Repeatable::Repeated(header)) = &self.grid.header {
if y < header.end {
if y == 0 {
self.layout_header(header, engine, 0)?;
self.regions.size.y -= self.footer_height;
let mut y = 0;
while y < self.grid.rows.len() {
if let Some(first_header) = self.upcoming_headers.first() {
if first_header.unwrap().range().contains(&y) {
self.bump_pending_headers();
if self.peek_upcoming_header().is_none_or(|h| {
h.unwrap().start > y + 1
|| h.unwrap().level <= first_header.unwrap().level
}) {
// Next row either isn't a header. or is in a
// conflicting one, which is the sign that we need to go.
self.layout_headers(next_header, engine, 0)?;
}
y = first_header.end;
// Skip header rows during normal layout.
continue;
self.bump_repeating_headers();
if let Repeatable::Repeated(next_header) = first_header {
if y == next_header.start {
self.layout_headers(next_header, engine, 0)?;
self.regions.size.y -= self.footer_height;
}
}
}
}
@ -162,6 +193,8 @@ impl<'a> GridLayouter<'a> {
}
self.layout_row(y, engine, 0)?;
y += 1;
}
self.finish_region(engine, true)?;
@ -953,7 +986,9 @@ impl<'a> GridLayouter<'a> {
.and_then(Repeatable::as_repeated)
.is_some_and(|header| y < header.end)
{
// Add to header height.
// Add to header height, as we are in a header row.
// TODO: Should we only bump from upcoming_headers to
// repeating_headers AFTER the header height calculation?
self.header_height += first;
}
@ -1370,15 +1405,15 @@ impl<'a> GridLayouter<'a> {
.and_then(Repeatable::as_repeated)
.is_some_and(|footer| footer.start != 0);
if let Some(Repeatable::Repeated(header)) = &self.grid.header {
if self.grid.rows.len() > header.end
if let Some(last_header) = self.repeating_headers.last() {
if self.grid.rows.len() > last_header.end
&& self
.grid
.footer
.as_ref()
.and_then(Repeatable::as_repeated)
.is_none_or(|footer| footer.start != header.end)
&& self.lrows.last().is_some_and(|row| row.index() < header.end)
&& self.lrows.last().is_some_and(|row| row.index() < last_header.end)
&& !in_last_with_offset(
self.regions,
self.header_height + self.footer_height,
@ -1535,9 +1570,9 @@ impl<'a> GridLayouter<'a> {
self.prepare_footer(footer, engine, disambiguator)?;
}
if let Some(Repeatable::Repeated(header)) = &self.grid.header {
if !self.repeating_headers.is_empty() {
// Add a header to the new region.
self.layout_header(header, engine, disambiguator)?;
self.layout_headers(engine, disambiguator)?;
}
// Ensure rows don't try to overrun the footer.

View File

@ -1,3 +1,5 @@
use std::ops::ControlFlow;
use typst_library::diag::SourceResult;
use typst_library::engine::Engine;
use typst_library::layout::grid::resolve::{Footer, Header, Repeatable};
@ -6,20 +8,100 @@ use typst_library::layout::{Abs, Axes, Frame, Regions};
use super::layouter::GridLayouter;
use super::rowspans::UnbreakableRowGroup;
impl GridLayouter<'_> {
/// Layouts the header's rows.
/// Skips regions as necessary.
pub fn layout_header(
impl<'a> GridLayouter<'a> {
#[inline]
fn pending_headers(&self) -> &'a [Repeatable<Header>] {
&self.upcoming_headers[..self.pending_header_end]
}
#[inline]
pub fn bump_pending_headers(&mut self) {
debug_assert!(!self.upcoming_headers.is_empty());
self.pending_header_end += 1;
}
#[inline]
pub fn peek_upcoming_header(&self) -> Option<&'a Repeatable<Header>> {
self.upcoming_headers.get(self.pending_header_end)
}
pub fn flush_pending_headers(&mut self) {
debug_assert!(!self.upcoming_headers.is_empty());
debug_assert!(self.pending_header_end > 0);
let headers = self.pending_headers();
let [first_header, ..] = headers else {
return;
};
self.repeating_headers.truncate(
self.repeating_headers
.partition_point(|h| h.level < first_header.unwrap().level),
);
for header in self.pending_headers() {
if let Repeatable::Repeated(header) = header {
// Vector remains sorted by increasing levels:
// - It was sorted before, so the truncation above only keeps
// elements with a lower level.
// - Therefore, by pushing this header to the end, it will have
// a level larger than all the previous headers, and is thus
// in its 'correct' position.
self.repeating_headers.push(header);
}
}
self.upcoming_headers = self
.upcoming_headers
.get(self.pending_header_end..)
.unwrap_or_default();
self.pending_header_end = 0;
}
pub fn bump_repeating_headers(&mut self) {
debug_assert!(!self.upcoming_headers.is_empty());
let [next_header, ..] = self.upcoming_headers else {
return;
};
// Keep only lower level headers. Assume sorted by increasing levels.
self.repeating_headers.truncate(
self.repeating_headers
.partition_point(|h| h.level < next_header.unwrap().level),
);
if let Repeatable::Repeated(next_header) = next_header {
// Vector remains sorted by increasing levels:
// - It was sorted before, so the truncation above only keeps
// elements with a lower level.
// - Therefore, by pushing this header to the end, it will have
// a level larger than all the previous headers, and is thus
// in its 'correct' position.
self.repeating_headers.push(next_header);
}
// Laying out the next header now.
self.upcoming_headers = self.upcoming_headers.get(1..).unwrap_or_default();
}
/// Layouts the headers' rows.
///
/// Assumes the footer height for the current region has already been
/// calculated. Skips regions as necessary to fit all headers and all
/// footers.
pub fn layout_headers(
&mut self,
header: &Header,
headers: &[&Header],
engine: &mut Engine,
disambiguator: usize,
) -> SourceResult<()> {
let header_rows =
self.simulate_header(header, &self.regions, engine, disambiguator)?;
let header_height =
self.simulate_header_height(&self.regions, engine, disambiguator)?;
let mut skipped_region = false;
while self.unbreakable_rows_left == 0
&& !self.regions.size.y.fits(header_rows.height + self.footer_height)
&& !self.regions.size.y.fits(header_height + self.footer_height)
&& self.regions.may_progress()
{
// Advance regions without any output until we can place the
@ -42,16 +124,34 @@ impl GridLayouter<'_> {
}
}
// Header is unbreakable.
// Group of headers is unbreakable.
// Thus, no risk of 'finish_region' being recursively called from
// within 'layout_row'.
self.unbreakable_rows_left += header.end;
for y in 0..header.end {
self.layout_row(y, engine, disambiguator)?;
self.unbreakable_rows_left += total_header_row_count(headers);
for header in headers {
for y in header.range() {
self.layout_row(y, engine, disambiguator)?;
}
}
Ok(())
}
/// Calculates the total expected height of several headers.
pub fn simulate_header_height(
&self,
headers: &[&Header],
regions: &Regions<'_>,
engine: &mut Engine,
disambiguator: usize,
) -> SourceResult<Abs> {
let mut height = Abs::zero();
for header in headers {
height +=
self.simulate_header(header, regions, engine, disambiguator)?.height;
}
Ok(height)
}
/// Simulate the header's group of rows.
pub fn simulate_header(
&self,
@ -66,7 +166,7 @@ impl GridLayouter<'_> {
// assume that the amount of unbreakable rows following the first row
// in the header will be precisely the rows in the header.
self.simulate_unbreakable_row_group(
0,
header.start,
Some(header.end),
regions,
engine,
@ -151,3 +251,9 @@ impl GridLayouter<'_> {
)
}
}
/// The total amount of rows in the given list of headers.
#[inline]
pub fn total_header_row_count(headers: &[&Header]) -> usize {
headers.iter().map(|h| h.end - h.start).sum()
}

View File

@ -160,6 +160,9 @@ impl GridLayouter<'_> {
// at a position after the sum of the laid out header
// rows).
if let Some(Repeatable::Repeated(header)) = &self.grid.header {
// TODO: Need a way to distinguish header 'rrows' for each
// region, as this calculation - i.e., header height at
// each region - will change depending on'i'.
let header_rows = self
.rrows
.get(i)