mirror of
https://github.com/typst/typst
synced 2025-07-02 02:02:53 +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::diag::{bail, SourceResult};
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{Resolve, StyleChain};
|
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::{
|
use typst_library::layout::{
|
||||||
Abs, Axes, Dir, Fr, Fragment, Frame, FrameItem, Length, Point, Region, Regions, Rel,
|
Abs, Axes, Dir, Fr, Fragment, Frame, FrameItem, Length, Point, Region, Regions, Rel,
|
||||||
Size, Sizing,
|
Size, Sizing,
|
||||||
@ -47,6 +49,18 @@ pub struct GridLayouter<'a> {
|
|||||||
pub(super) finished: Vec<Frame>,
|
pub(super) finished: Vec<Frame>,
|
||||||
/// Whether this is an RTL grid.
|
/// Whether this is an RTL grid.
|
||||||
pub(super) is_rtl: bool,
|
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.
|
/// 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
|
||||||
/// `layout_auto_row` and `layout_relative_row`, and should not be read
|
/// `layout_auto_row` and `layout_relative_row`, and should not be read
|
||||||
@ -120,6 +134,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
initial: regions.size,
|
initial: regions.size,
|
||||||
finished: vec![],
|
finished: vec![],
|
||||||
is_rtl: TextElem::dir_in(styles) == Dir::RTL,
|
is_rtl: TextElem::dir_in(styles) == Dir::RTL,
|
||||||
|
upcoming_headers: &grid.headers,
|
||||||
header_height: Abs::zero(),
|
header_height: Abs::zero(),
|
||||||
footer_height: Abs::zero(),
|
footer_height: Abs::zero(),
|
||||||
span,
|
span,
|
||||||
@ -140,15 +155,31 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for y in 0..self.grid.rows.len() {
|
let mut y = 0;
|
||||||
if let Some(Repeatable::Repeated(header)) = &self.grid.header {
|
while y < self.grid.rows.len() {
|
||||||
if y < header.end {
|
if let Some(first_header) = self.upcoming_headers.first() {
|
||||||
if y == 0 {
|
if first_header.unwrap().range().contains(&y) {
|
||||||
self.layout_header(header, engine, 0)?;
|
self.bump_pending_headers();
|
||||||
self.regions.size.y -= self.footer_height;
|
|
||||||
|
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.
|
// Skip header rows during normal layout.
|
||||||
continue;
|
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)?;
|
self.layout_row(y, engine, 0)?;
|
||||||
|
|
||||||
|
y += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.finish_region(engine, true)?;
|
self.finish_region(engine, true)?;
|
||||||
@ -953,7 +986,9 @@ impl<'a> GridLayouter<'a> {
|
|||||||
.and_then(Repeatable::as_repeated)
|
.and_then(Repeatable::as_repeated)
|
||||||
.is_some_and(|header| y < header.end)
|
.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;
|
self.header_height += first;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1370,15 +1405,15 @@ impl<'a> GridLayouter<'a> {
|
|||||||
.and_then(Repeatable::as_repeated)
|
.and_then(Repeatable::as_repeated)
|
||||||
.is_some_and(|footer| footer.start != 0);
|
.is_some_and(|footer| footer.start != 0);
|
||||||
|
|
||||||
if let Some(Repeatable::Repeated(header)) = &self.grid.header {
|
if let Some(last_header) = self.repeating_headers.last() {
|
||||||
if self.grid.rows.len() > header.end
|
if self.grid.rows.len() > last_header.end
|
||||||
&& self
|
&& self
|
||||||
.grid
|
.grid
|
||||||
.footer
|
.footer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(Repeatable::as_repeated)
|
.and_then(Repeatable::as_repeated)
|
||||||
.is_none_or(|footer| footer.start != header.end)
|
.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(
|
&& !in_last_with_offset(
|
||||||
self.regions,
|
self.regions,
|
||||||
self.header_height + self.footer_height,
|
self.header_height + self.footer_height,
|
||||||
@ -1535,9 +1570,9 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.prepare_footer(footer, engine, disambiguator)?;
|
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.
|
// 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.
|
// 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::diag::SourceResult;
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::layout::grid::resolve::{Footer, Header, Repeatable};
|
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::layouter::GridLayouter;
|
||||||
use super::rowspans::UnbreakableRowGroup;
|
use super::rowspans::UnbreakableRowGroup;
|
||||||
|
|
||||||
impl GridLayouter<'_> {
|
impl<'a> GridLayouter<'a> {
|
||||||
/// Layouts the header's rows.
|
#[inline]
|
||||||
/// Skips regions as necessary.
|
fn pending_headers(&self) -> &'a [Repeatable<Header>] {
|
||||||
pub fn layout_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,
|
&mut self,
|
||||||
header: &Header,
|
headers: &[&Header],
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
disambiguator: usize,
|
disambiguator: usize,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let header_rows =
|
let header_height =
|
||||||
self.simulate_header(header, &self.regions, engine, disambiguator)?;
|
self.simulate_header_height(&self.regions, engine, disambiguator)?;
|
||||||
let mut skipped_region = false;
|
let mut skipped_region = false;
|
||||||
while self.unbreakable_rows_left == 0
|
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()
|
&& self.regions.may_progress()
|
||||||
{
|
{
|
||||||
// Advance regions without any output until we can place the
|
// 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
|
// Thus, no risk of 'finish_region' being recursively called from
|
||||||
// within 'layout_row'.
|
// within 'layout_row'.
|
||||||
self.unbreakable_rows_left += header.end;
|
self.unbreakable_rows_left += total_header_row_count(headers);
|
||||||
for y in 0..header.end {
|
for header in headers {
|
||||||
self.layout_row(y, engine, disambiguator)?;
|
for y in header.range() {
|
||||||
|
self.layout_row(y, engine, disambiguator)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
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.
|
/// Simulate the header's group of rows.
|
||||||
pub fn simulate_header(
|
pub fn simulate_header(
|
||||||
&self,
|
&self,
|
||||||
@ -66,7 +166,7 @@ impl GridLayouter<'_> {
|
|||||||
// assume that the amount of unbreakable rows following the first row
|
// assume that the amount of unbreakable rows following the first row
|
||||||
// in the header will be precisely the rows in the header.
|
// in the header will be precisely the rows in the header.
|
||||||
self.simulate_unbreakable_row_group(
|
self.simulate_unbreakable_row_group(
|
||||||
0,
|
header.start,
|
||||||
Some(header.end),
|
Some(header.end),
|
||||||
regions,
|
regions,
|
||||||
engine,
|
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
|
// at a position after the sum of the laid out header
|
||||||
// rows).
|
// rows).
|
||||||
if let Some(Repeatable::Repeated(header)) = &self.grid.header {
|
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
|
let header_rows = self
|
||||||
.rrows
|
.rrows
|
||||||
.get(i)
|
.get(i)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user