mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
store height of each repeating header
So we can update in the middle of the region
This commit is contained in:
parent
ecc93297f8
commit
d172eccfd9
@ -73,6 +73,16 @@ pub struct GridLayouter<'a> {
|
|||||||
/// Sorted by increasing levels.
|
/// Sorted by increasing levels.
|
||||||
pub(super) pending_headers: &'a [Repeatable<Header>],
|
pub(super) pending_headers: &'a [Repeatable<Header>],
|
||||||
pub(super) upcoming_headers: &'a [Repeatable<Header>],
|
pub(super) upcoming_headers: &'a [Repeatable<Header>],
|
||||||
|
/// The height for each repeating header that was placed in this region.
|
||||||
|
/// Note that this includes headers not at the top of the region (pending
|
||||||
|
/// headers), and excludes headers removed by virtue of a new, conflicting
|
||||||
|
/// header being found.
|
||||||
|
pub(super) repeating_header_heights: Vec<Abs>,
|
||||||
|
/// If this is `Some`, this will receive the currently laid out row's
|
||||||
|
/// height if it is auto or relative. This is used for header height
|
||||||
|
/// calculation.
|
||||||
|
/// TODO: consider refactoring this into something nicer.
|
||||||
|
pub(super) current_row_height: Option<Abs>,
|
||||||
/// 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
|
||||||
@ -158,7 +168,9 @@ 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,
|
||||||
|
repeating_header_heights: vec![],
|
||||||
pending_headers: Default::default(),
|
pending_headers: Default::default(),
|
||||||
|
current_row_height: None,
|
||||||
header_height: Abs::zero(),
|
header_height: Abs::zero(),
|
||||||
repeating_header_height: Abs::zero(),
|
repeating_header_height: Abs::zero(),
|
||||||
footer_height: Abs::zero(),
|
footer_height: Abs::zero(),
|
||||||
@ -1003,9 +1015,9 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let frame = self.layout_single_row(engine, disambiguator, first, y)?;
|
let frame = self.layout_single_row(engine, disambiguator, first, y)?;
|
||||||
self.push_row(frame, y, true);
|
self.push_row(frame, y, true);
|
||||||
|
|
||||||
if self.lrows.len() < self.current_header_rows {
|
if let Some(row_height) = &mut self.current_row_height {
|
||||||
// Add to header height, as we are in a header row.
|
// Add to header height, as we are in a header row.
|
||||||
self.header_height += first;
|
*row_height += first;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -1237,10 +1249,9 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let resolved = v.resolve(self.styles).relative_to(self.regions.base().y);
|
let resolved = v.resolve(self.styles).relative_to(self.regions.base().y);
|
||||||
let frame = self.layout_single_row(engine, disambiguator, resolved, y)?;
|
let frame = self.layout_single_row(engine, disambiguator, resolved, y)?;
|
||||||
|
|
||||||
if self.lrows.len() < self.current_header_rows {
|
if let Some(row_height) = &mut self.current_row_height {
|
||||||
// Add to header height (not all headers were laid out yet, so this
|
// Add to header height, as we are in a header row.
|
||||||
// must be a repeated or pending header at the top of the region).
|
*row_height += resolved;
|
||||||
self.header_height += resolved;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip to fitting region, but only if we aren't part of an unbreakable
|
// Skip to fitting region, but only if we aren't part of an unbreakable
|
||||||
@ -1598,6 +1609,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.current_header_rows = 0;
|
self.current_header_rows = 0;
|
||||||
self.header_height = Abs::zero();
|
self.header_height = Abs::zero();
|
||||||
self.repeating_header_height = Abs::zero();
|
self.repeating_header_height = Abs::zero();
|
||||||
|
self.repeating_header_heights.clear();
|
||||||
|
|
||||||
let disambiguator = self.finished.len();
|
let disambiguator = self.finished.len();
|
||||||
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
|
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
|
||||||
|
@ -10,7 +10,17 @@ use super::rowspans::UnbreakableRowGroup;
|
|||||||
|
|
||||||
pub enum HeadersToLayout<'a> {
|
pub enum HeadersToLayout<'a> {
|
||||||
RepeatingAndPending,
|
RepeatingAndPending,
|
||||||
NewHeaders(&'a [Repeatable<Header>]),
|
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> {
|
impl<'a> GridLayouter<'a> {
|
||||||
@ -53,7 +63,12 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.layout_headers(
|
self.layout_headers(
|
||||||
// Using 'chunks_exact", we pass a slice of length one instead
|
// Using 'chunks_exact", we pass a slice of length one instead
|
||||||
// of a reference for type consistency.
|
// of a reference for type consistency.
|
||||||
HeadersToLayout::NewHeaders(conflicting_header),
|
// In addition, this is the only place where we layout
|
||||||
|
// short-lived headers.
|
||||||
|
HeadersToLayout::NewHeaders {
|
||||||
|
headers: conflicting_header,
|
||||||
|
short_lived: true,
|
||||||
|
},
|
||||||
engine,
|
engine,
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
@ -61,6 +76,40 @@ impl<'a> GridLayouter<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lays out a row while indicating that it should store its persistent
|
||||||
|
/// height as a header row, which will be its height if relative or auto,
|
||||||
|
/// or zero otherwise (fractional).
|
||||||
|
#[inline]
|
||||||
|
fn layout_header_row(
|
||||||
|
&mut self,
|
||||||
|
y: usize,
|
||||||
|
engine: &mut Engine,
|
||||||
|
disambiguator: usize,
|
||||||
|
) -> SourceResult<Option<Abs>> {
|
||||||
|
let previous_row_height =
|
||||||
|
std::mem::replace(&mut self.current_row_height, Some(Abs::zero()));
|
||||||
|
self.layout_row(y, engine, disambiguator)?;
|
||||||
|
|
||||||
|
Ok(std::mem::replace(&mut self.current_row_height, previous_row_height))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lays out rows belonging to a header, returning the calculated header
|
||||||
|
/// height only for that header.
|
||||||
|
#[inline]
|
||||||
|
fn layout_header_rows(
|
||||||
|
&mut self,
|
||||||
|
header: &Header,
|
||||||
|
engine: &mut Engine,
|
||||||
|
disambiguator: usize,
|
||||||
|
) -> SourceResult<Abs> {
|
||||||
|
let mut header_height = Abs::zero();
|
||||||
|
for y in header.range() {
|
||||||
|
header_height +=
|
||||||
|
self.layout_header_row(y, engine, disambiguator)?.unwrap_or_default();
|
||||||
|
}
|
||||||
|
Ok(header_height)
|
||||||
|
}
|
||||||
|
|
||||||
/// Queues new pending headers for layout. Headers remain pending until
|
/// Queues new pending headers for layout. Headers remain pending until
|
||||||
/// they are successfully laid out in some page once. Then, they will be
|
/// they are successfully laid out in some page once. Then, they will be
|
||||||
/// moved to `repeating_headers`, at which point it is safe to stop them
|
/// moved to `repeating_headers`, at which point it is safe to stop them
|
||||||
@ -80,14 +129,26 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Stop repeating conflicting headers.
|
// Stop repeating conflicting headers.
|
||||||
// If we go to a new region before the pending headers fit alongside
|
// If we go to a new region before the pending headers fit alongside
|
||||||
// their children, the old headers should not be displayed anymore.
|
// their children, the old headers should not be displayed anymore.
|
||||||
self.repeating_headers
|
let first_conflicting_pos =
|
||||||
.truncate(self.repeating_headers.partition_point(|h| h.level < first_level));
|
self.repeating_headers.partition_point(|h| h.level < first_level);
|
||||||
|
self.repeating_headers.truncate(first_conflicting_pos);
|
||||||
|
|
||||||
|
// Ensure upcoming rows won't see that these headers will occupy any
|
||||||
|
// space in future regions anymore.
|
||||||
|
for removed_height in self.repeating_header_heights.drain(first_conflicting_pos..)
|
||||||
|
{
|
||||||
|
self.header_height -= removed_height;
|
||||||
|
self.repeating_header_height -= removed_height;
|
||||||
|
}
|
||||||
|
|
||||||
// Let's try to place them at least once.
|
// Let's try to place them at least once.
|
||||||
// This might be a waste as we could generate an orphan and thus have
|
// 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
|
// to try to place old and new headers all over again, but that happens
|
||||||
// for every new region anyway, so it's rather unavoidable.
|
// for every new region anyway, so it's rather unavoidable.
|
||||||
self.layout_headers(HeadersToLayout::NewHeaders(headers), engine);
|
self.layout_headers(
|
||||||
|
HeadersToLayout::NewHeaders { headers, short_lived: false },
|
||||||
|
engine,
|
||||||
|
);
|
||||||
|
|
||||||
// After the first subsequent row is laid out, move to repeating, as
|
// 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
|
// it's then confirmed the headers won't be moved due to orphan
|
||||||
@ -171,7 +232,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
engine,
|
engine,
|
||||||
disambiguator,
|
disambiguator,
|
||||||
)?,
|
)?,
|
||||||
HeadersToLayout::NewHeaders(headers) => self.simulate_header_height(
|
HeadersToLayout::NewHeaders { headers, .. } => self.simulate_header_height(
|
||||||
headers.into_iter().map(Repeatable::unwrap),
|
headers.into_iter().map(Repeatable::unwrap),
|
||||||
&self.regions,
|
&self.regions,
|
||||||
engine,
|
engine,
|
||||||
@ -198,7 +259,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// TODO: re-calculate heights of headers and footers on each region
|
// TODO: re-calculate heights of headers and footers on each region
|
||||||
// if 'full'changes? (Assuming height doesn't change for now...)
|
// if 'full'changes? (Assuming height doesn't change for now...)
|
||||||
if !skipped_region {
|
if !skipped_region {
|
||||||
if let HeadersToLayout::NewHeaders(headers) = headers {
|
if let HeadersToLayout::NewHeaders { headers, .. } = headers {
|
||||||
header_height =
|
header_height =
|
||||||
// Laying out new headers, so we have to consider the
|
// Laying out new headers, so we have to consider the
|
||||||
// combined height of already repeating headers as well
|
// combined height of already repeating headers as well
|
||||||
@ -221,11 +282,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.regions.size.y -= self.footer_height;
|
self.regions.size.y -= self.footer_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the header height for this region.
|
|
||||||
// It will be re-calculated when laying out each header row.
|
|
||||||
self.header_height = Abs::zero();
|
|
||||||
self.repeating_header_height = Abs::zero();
|
|
||||||
|
|
||||||
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
|
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
|
||||||
if skipped_region {
|
if skipped_region {
|
||||||
// Simulate the footer again; the region's 'full' might have
|
// Simulate the footer again; the region's 'full' might have
|
||||||
@ -242,7 +298,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Group of headers 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'.
|
||||||
if let HeadersToLayout::NewHeaders(headers) = headers {
|
if let HeadersToLayout::NewHeaders { headers, .. } = headers {
|
||||||
// Do this before laying out repeating and pending headers from a
|
// Do this before laying out repeating and pending headers from a
|
||||||
// new region to make sure row code is aware that all of those
|
// new region to make sure row code is aware that all of those
|
||||||
// headers should stay together!
|
// headers should stay together!
|
||||||
@ -267,33 +323,72 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.current_header_rows = repeating_header_rows + pending_header_rows;
|
self.current_header_rows = repeating_header_rows + pending_header_rows;
|
||||||
self.unbreakable_rows_left += repeating_header_rows + pending_header_rows;
|
self.unbreakable_rows_left += repeating_header_rows + pending_header_rows;
|
||||||
|
|
||||||
|
// Reset the header height for this region.
|
||||||
|
// It will be re-calculated when laying out each header row.
|
||||||
|
self.header_height = Abs::zero();
|
||||||
|
self.repeating_header_height = Abs::zero();
|
||||||
|
self.repeating_header_heights.clear();
|
||||||
|
|
||||||
// Use indices to avoid double borrow. We don't mutate headers in
|
// Use indices to avoid double borrow. We don't mutate headers in
|
||||||
// 'layout_row' so this is fine.
|
// 'layout_row' so this is fine.
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while let Some(&header) = self.repeating_headers.get(i) {
|
while let Some(&header) = self.repeating_headers.get(i) {
|
||||||
for y in header.range() {
|
let header_height =
|
||||||
self.layout_row(y, engine, disambiguator)?;
|
self.layout_header_rows(header, engine, disambiguator)?;
|
||||||
}
|
self.header_height += header_height;
|
||||||
|
self.repeating_header_height += header_height;
|
||||||
|
|
||||||
|
// We assume that this vector will be sorted according
|
||||||
|
// to increasing levels like 'repeating_headers' and
|
||||||
|
// 'pending_headers' - and, in particular, their union, as this
|
||||||
|
// vector is pushed repeating heights from both.
|
||||||
|
//
|
||||||
|
// This is guaranteed by:
|
||||||
|
// 1. We always push pending headers after repeating headers,
|
||||||
|
// as we assume they don't conflict because we remove
|
||||||
|
// conflicting repeating headers when pushing a new pending
|
||||||
|
// header.
|
||||||
|
//
|
||||||
|
// 2. We push in the same order as each.
|
||||||
|
//
|
||||||
|
// 3. This vector is also modified when pushing a new pending
|
||||||
|
// header, where we remove heights for conflicting repeating
|
||||||
|
// headers which have now stopped repeating. They are always at
|
||||||
|
// the end and new pending headers respect the existing sort,
|
||||||
|
// so the vector will remain sorted.
|
||||||
|
self.repeating_header_heights.push(header_height);
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// All rows so far were repeating headers at the top of the region.
|
|
||||||
self.repeating_header_height = self.header_height;
|
|
||||||
for header in self.pending_headers {
|
for header in self.pending_headers {
|
||||||
let header_height = self.header_height;
|
let header_height =
|
||||||
for y in header.unwrap().range() {
|
self.layout_header_rows(header.unwrap(), engine, disambiguator)?;
|
||||||
self.layout_row(y, engine, disambiguator)?;
|
self.header_height += header_height;
|
||||||
}
|
|
||||||
if matches!(header, Repeatable::Repeated(_)) {
|
if matches!(header, Repeatable::Repeated(_)) {
|
||||||
self.repeating_header_height += self.header_height - header_height;
|
self.repeating_header_height += header_height;
|
||||||
|
self.repeating_header_heights.push(header_height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let HeadersToLayout::NewHeaders(headers) = headers {
|
if let HeadersToLayout::NewHeaders { headers, short_lived } = headers {
|
||||||
for header in headers {
|
for header in headers {
|
||||||
for y in header.unwrap().range() {
|
let header_height =
|
||||||
self.layout_row(y, engine, disambiguator)?;
|
self.layout_header_rows(header.unwrap(), engine, disambiguator)?;
|
||||||
|
|
||||||
|
// Only store this header height if it is actually going to
|
||||||
|
// become a pending header. Otherwise, pretend it's not a
|
||||||
|
// header... This is fine for consumers of 'header_height' as
|
||||||
|
// it is guaranteed this header won't appear in a future
|
||||||
|
// region, so multi-page rows and cells can effectively ignore
|
||||||
|
// this header.
|
||||||
|
if !short_lived {
|
||||||
|
self.header_height += header_height;
|
||||||
|
if matches!(header, Repeatable::Repeated(_)) {
|
||||||
|
self.repeating_header_height = header_height;
|
||||||
|
self.repeating_header_heights.push(header_height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user