mirror of
https://github.com/typst/typst
synced 2025-05-17 02:25:27 +08:00
separate layout_active_headers and new_headers
- Active headers will push empty regions when skipping regions, whereas new_headers may be called from a non-empty region, so we need to finish the region in full. The code separation also makes it all clearer.
This commit is contained in:
parent
fe957e804a
commit
cefa7fc72c
@ -6,21 +6,6 @@ use typst_library::layout::{Abs, Axes, Frame, Regions};
|
||||
use super::layouter::{may_progress_with_offset, GridLayouter};
|
||||
use super::rowspans::UnbreakableRowGroup;
|
||||
|
||||
pub enum HeadersToLayout<'a> {
|
||||
RepeatingAndPending,
|
||||
NewHeaders {
|
||||
headers: &'a [Repeatable<Header>],
|
||||
|
||||
/// Whether this new header will become a pending header. If false, we
|
||||
/// assume it simply won't repeat and so its header height is ignored.
|
||||
/// Later on, cells can assume that this header won't occupy any height
|
||||
/// in a future region, and indeed, since it won't be pending, it won't
|
||||
/// have orphan prevention, so it will be placed immediately and stay
|
||||
/// where it is.
|
||||
short_lived: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> GridLayouter<'a> {
|
||||
pub fn place_new_headers(
|
||||
&mut self,
|
||||
@ -51,15 +36,13 @@ impl<'a> GridLayouter<'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_new_headers(
|
||||
// Using 'chunks_exact", we pass a slice of length one instead
|
||||
// of a reference for type consistency.
|
||||
// In addition, this is the only place where we layout
|
||||
// short-lived headers.
|
||||
HeadersToLayout::NewHeaders {
|
||||
headers: conflicting_header,
|
||||
short_lived: true,
|
||||
},
|
||||
conflicting_header,
|
||||
true,
|
||||
engine,
|
||||
)?
|
||||
}
|
||||
@ -155,10 +138,7 @@ impl<'a> GridLayouter<'a> {
|
||||
// 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(
|
||||
HeadersToLayout::NewHeaders { headers, short_lived: false },
|
||||
engine,
|
||||
)?;
|
||||
self.layout_new_headers(headers, false, 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
|
||||
@ -190,25 +170,18 @@ impl<'a> GridLayouter<'a> {
|
||||
self.pending_headers = Default::default();
|
||||
}
|
||||
|
||||
/// Layouts the headers' rows.
|
||||
/// Lays out the rows of repeating and pending headers at the top of the
|
||||
/// region.
|
||||
///
|
||||
/// 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,
|
||||
headers: HeadersToLayout<'a>,
|
||||
engine: &mut Engine,
|
||||
) -> SourceResult<()> {
|
||||
pub fn layout_active_headers(&mut self, engine: &mut Engine) -> SourceResult<()> {
|
||||
// Generate different locations for content in headers across its
|
||||
// repetitions by assigning a unique number for each one.
|
||||
let mut disambiguator = self.finished.len();
|
||||
let disambiguator = self.finished.len();
|
||||
|
||||
// At first, only consider the height of the given headers. However,
|
||||
// for upcoming regions, we will have to consider repeating headers as
|
||||
// well.
|
||||
let mut header_height = match headers {
|
||||
HeadersToLayout::RepeatingAndPending => self.simulate_header_height(
|
||||
let header_height = self.simulate_header_height(
|
||||
self.repeating_headers
|
||||
.iter()
|
||||
.copied()
|
||||
@ -216,14 +189,7 @@ impl<'a> GridLayouter<'a> {
|
||||
&self.regions,
|
||||
engine,
|
||||
disambiguator,
|
||||
)?,
|
||||
HeadersToLayout::NewHeaders { headers, .. } => self.simulate_header_height(
|
||||
headers.iter().map(Repeatable::unwrap),
|
||||
&self.regions,
|
||||
engine,
|
||||
disambiguator,
|
||||
)?,
|
||||
};
|
||||
)?;
|
||||
|
||||
// We already take the footer into account below.
|
||||
// While skipping regions, footer height won't be automatically
|
||||
@ -252,26 +218,6 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
// TODO: re-calculate heights of headers and footers on each region
|
||||
// if 'full' changes? (Assuming height doesn't change for now...)
|
||||
if !skipped_region {
|
||||
if let HeadersToLayout::NewHeaders { headers, .. } = headers {
|
||||
// Update disambiguator as we are re-measuring headers
|
||||
// which were already laid out.
|
||||
disambiguator = self.finished.len();
|
||||
header_height =
|
||||
// Laying out new headers, so we have to consider the
|
||||
// combined height of already repeating headers as well
|
||||
// when beginning a new region.
|
||||
self.simulate_header_height(
|
||||
self.repeating_headers
|
||||
.iter().copied()
|
||||
.chain(self.pending_headers.iter().chain(headers).map(Repeatable::unwrap)),
|
||||
&self.regions,
|
||||
engine,
|
||||
disambiguator,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
skipped_region = true;
|
||||
|
||||
self.regions.size.y -= self.footer_height;
|
||||
@ -290,29 +236,15 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Group of headers is unbreakable.
|
||||
// Thus, no risk of 'finish_region' being recursively called from
|
||||
// within 'layout_row'.
|
||||
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.iter().map(Repeatable::unwrap));
|
||||
}
|
||||
|
||||
// Need to relayout ALL headers if we skip a region, not only the
|
||||
// provided headers.
|
||||
// TODO: maybe extract this into a function to share code with multiple
|
||||
// footers.
|
||||
if matches!(headers, HeadersToLayout::RepeatingAndPending) || skipped_region {
|
||||
let repeating_header_rows =
|
||||
total_header_row_count(self.repeating_headers.iter().copied());
|
||||
|
||||
let pending_header_rows = total_header_row_count(
|
||||
self.pending_headers.iter().map(Repeatable::unwrap),
|
||||
);
|
||||
let pending_header_rows =
|
||||
total_header_row_count(self.pending_headers.iter().map(Repeatable::unwrap));
|
||||
|
||||
// Group of headers is unbreakable.
|
||||
// Thus, no risk of 'finish_region' being recursively called from
|
||||
// within 'layout_row'.
|
||||
self.unbreakable_rows_left += repeating_header_rows + pending_header_rows;
|
||||
|
||||
self.current_last_repeated_header_end =
|
||||
@ -328,8 +260,7 @@ impl<'a> GridLayouter<'a> {
|
||||
// 'layout_row' so this is fine.
|
||||
let mut i = 0;
|
||||
while let Some(&header) = self.repeating_headers.get(i) {
|
||||
let header_height =
|
||||
self.layout_header_rows(header, engine, disambiguator)?;
|
||||
let header_height = self.layout_header_rows(header, engine, disambiguator)?;
|
||||
self.header_height += header_height;
|
||||
self.repeating_header_height += header_height;
|
||||
|
||||
@ -371,13 +302,65 @@ impl<'a> GridLayouter<'a> {
|
||||
// Include both repeating and pending header rows as this number is
|
||||
// used for orphan prevention.
|
||||
self.current_header_rows = self.lrows.len();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if let HeadersToLayout::NewHeaders { headers, short_lived } = headers {
|
||||
let placing_at_the_start = skipped_region || self.lrows.is_empty();
|
||||
/// Lays out headers found for the first time during row layout.
|
||||
///
|
||||
/// If 'short_lived' is true, these headers are immediately followed by
|
||||
/// a conflicting header, so it is assumed they will not be pushed to
|
||||
/// pending headers.
|
||||
pub fn layout_new_headers(
|
||||
&mut self,
|
||||
headers: &'a [Repeatable<Header>],
|
||||
short_lived: bool,
|
||||
engine: &mut Engine,
|
||||
) -> SourceResult<()> {
|
||||
// At first, only consider the height of the given headers. However,
|
||||
// for upcoming regions, we will have to consider repeating headers as
|
||||
// well.
|
||||
let header_height = self.simulate_header_height(
|
||||
headers.iter().map(Repeatable::unwrap),
|
||||
&self.regions,
|
||||
engine,
|
||||
0,
|
||||
)?;
|
||||
|
||||
// We already take the footer into account below.
|
||||
// While skipping regions, footer height won't be automatically
|
||||
// re-calculated until the end.
|
||||
let mut skipped_region = false;
|
||||
|
||||
// TODO: remove this 'unbreakable rows left check',
|
||||
// consider if we can already be in an unbreakable row group?
|
||||
while self.unbreakable_rows_left == 0
|
||||
&& !self.regions.size.y.fits(header_height)
|
||||
&& may_progress_with_offset(
|
||||
self.regions,
|
||||
// 'finish_region' will place currently active headers and
|
||||
// footers again. We assume previous pending headers have
|
||||
// already been flushed, so in principle
|
||||
// 'header_height == repeating_header_height' here
|
||||
// (there won't be any pending headers at this point, other
|
||||
// than the ones we are about to place).
|
||||
self.header_height + self.footer_height,
|
||||
)
|
||||
{
|
||||
// Note that, after the first region skip, the new headers will go
|
||||
// at the top of the region, but after the repeating headers that
|
||||
// remained (which will be automatically placed in 'finish_region').
|
||||
self.finish_region(engine, true)?;
|
||||
skipped_region = true;
|
||||
}
|
||||
|
||||
self.unbreakable_rows_left +=
|
||||
total_header_row_count(headers.iter().map(Repeatable::unwrap));
|
||||
|
||||
let placing_at_the_start =
|
||||
skipped_region || self.lrows.len() == self.current_header_rows;
|
||||
for header in headers {
|
||||
let header_height =
|
||||
self.layout_header_rows(header.unwrap(), engine, disambiguator)?;
|
||||
let header_height = self.layout_header_rows(header.unwrap(), engine, 0)?;
|
||||
|
||||
// Only store this header height if it is actually going to
|
||||
// become a pending header. Otherwise, pretend it's not a
|
||||
@ -398,7 +381,6 @@ impl<'a> GridLayouter<'a> {
|
||||
// Track header rows at the start of the region.
|
||||
self.current_header_rows = self.lrows.len();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user