mirror of
https://github.com/typst/typst
synced 2025-07-01 17:52:52 +08:00
initial multi heading progress
pending headers change
This commit is contained in:
parent
20179bbe7a
commit
4bd3abf44d
@ -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.
|
||||
|
@ -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.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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user