mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
mark headers as short-lived during resolve
This commit is contained in:
parent
af0c27cb98
commit
6e21eae3eb
@ -236,54 +236,12 @@ impl<'a> GridLayouter<'a> {
|
||||
let mut y = 0;
|
||||
let mut consecutive_header_count = 0;
|
||||
while y < self.grid.rows.len() {
|
||||
if let Some(first_header) =
|
||||
self.upcoming_headers.get(consecutive_header_count)
|
||||
if let Some(next_header) = self.upcoming_headers.get(consecutive_header_count)
|
||||
{
|
||||
if first_header.unwrap().range().contains(&y) {
|
||||
consecutive_header_count += 1;
|
||||
if next_header.unwrap().range().contains(&y) {
|
||||
self.place_new_headers(&mut consecutive_header_count, engine)?;
|
||||
y = next_header.unwrap().end;
|
||||
|
||||
// TODO: surely there is a better way to do this
|
||||
match self.upcoming_headers.get(consecutive_header_count) {
|
||||
// No more headers, so place the latest headers.
|
||||
None => {
|
||||
self.place_new_headers(
|
||||
consecutive_header_count,
|
||||
None,
|
||||
engine,
|
||||
)?;
|
||||
consecutive_header_count = 0;
|
||||
}
|
||||
// Next header is not consecutive, so place the latest headers.
|
||||
Some(next_header)
|
||||
if next_header.unwrap().start > first_header.unwrap().end =>
|
||||
{
|
||||
self.place_new_headers(
|
||||
consecutive_header_count,
|
||||
None,
|
||||
engine,
|
||||
)?;
|
||||
consecutive_header_count = 0;
|
||||
}
|
||||
// Next header is consecutive and conflicts with one or
|
||||
// more of the latest consecutive headers, so we must
|
||||
// place them before proceeding.
|
||||
Some(next_header)
|
||||
if next_header.unwrap().level
|
||||
<= first_header.unwrap().level =>
|
||||
{
|
||||
self.place_new_headers(
|
||||
consecutive_header_count,
|
||||
Some(next_header),
|
||||
engine,
|
||||
)?;
|
||||
consecutive_header_count = 0;
|
||||
}
|
||||
// Next header is a non-conflicting consecutive header.
|
||||
// Keep collecting more headers.
|
||||
_ => {}
|
||||
}
|
||||
|
||||
y = first_header.unwrap().end;
|
||||
// Skip header rows during normal layout.
|
||||
continue;
|
||||
}
|
||||
|
@ -9,71 +9,44 @@ use super::rowspans::UnbreakableRowGroup;
|
||||
impl<'a> GridLayouter<'a> {
|
||||
pub fn place_new_headers(
|
||||
&mut self,
|
||||
consecutive_header_count: usize,
|
||||
conflicting_header: Option<&Repeatable<Header>>,
|
||||
consecutive_header_count: &mut usize,
|
||||
engine: &mut Engine,
|
||||
) -> SourceResult<()> {
|
||||
*consecutive_header_count += 1;
|
||||
let (consecutive_headers, new_upcoming_headers) =
|
||||
self.upcoming_headers.split_at(consecutive_header_count);
|
||||
self.upcoming_headers.split_at(*consecutive_header_count);
|
||||
|
||||
if new_upcoming_headers.first().is_some_and(|next_header| {
|
||||
consecutive_headers.last().is_none_or(|latest_header| {
|
||||
!latest_header.unwrap().short_lived
|
||||
&& next_header.unwrap().start == latest_header.unwrap().end
|
||||
}) && !next_header.unwrap().short_lived
|
||||
}) {
|
||||
// More headers coming, so wait until we reach them.
|
||||
// TODO: refactor
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.upcoming_headers = new_upcoming_headers;
|
||||
*consecutive_header_count = 0;
|
||||
|
||||
let (non_conflicting_headers, conflicting_headers) = match conflicting_header {
|
||||
// Headers succeeded by end of grid or footer are short lived and
|
||||
// can be placed in separate regions (no orphan prevention).
|
||||
// TODO: do this during grid resolving?
|
||||
// might be needed for multiple footers. Or maybe not if we check
|
||||
// "upcoming_footers" (O(1) here), however that looks like.
|
||||
_ if consecutive_headers
|
||||
.last()
|
||||
.is_some_and(|x| x.unwrap().end == self.grid.rows.len())
|
||||
|| self
|
||||
.grid
|
||||
.footer
|
||||
.as_ref()
|
||||
.zip(consecutive_headers.last())
|
||||
.is_some_and(|(f, h)| f.unwrap().start == h.unwrap().end) =>
|
||||
{
|
||||
(Default::default(), consecutive_headers)
|
||||
}
|
||||
|
||||
Some(conflicting_header) => {
|
||||
// All immediately conflicting headers will
|
||||
// be laid out without orphan prevention.
|
||||
consecutive_headers.split_at(consecutive_headers.partition_point(|h| {
|
||||
conflicting_header.unwrap().level > h.unwrap().level
|
||||
}))
|
||||
}
|
||||
_ => (consecutive_headers, Default::default()),
|
||||
};
|
||||
|
||||
self.layout_new_pending_headers(non_conflicting_headers, engine)?;
|
||||
|
||||
// Layout each conflicting header independently, without orphan
|
||||
// prevention (as they don't go into 'pending_headers').
|
||||
// These headers are short-lived as they are immediately followed by 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_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.
|
||||
conflicting_header,
|
||||
true,
|
||||
engine,
|
||||
)?
|
||||
}
|
||||
|
||||
// No chance of orphans as we're immediately placing conflicting
|
||||
// headers afterwards, which basically are not headers, for all intents
|
||||
// and purposes. It is therefore guaranteed that all new headers have
|
||||
// been placed at least once.
|
||||
if !conflicting_headers.is_empty() {
|
||||
// Layout short-lived headers immediately.
|
||||
if consecutive_headers.last().is_some_and(|h| h.unwrap().short_lived) {
|
||||
// No chance of orphans as we're immediately placing conflicting
|
||||
// headers afterwards, which basically are not headers, for all intents
|
||||
// and purposes. It is therefore guaranteed that all new headers have
|
||||
// been placed at least once.
|
||||
self.flush_pending_headers();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Layout each conflicting header independently, without orphan
|
||||
// prevention (as they don't go into 'pending_headers').
|
||||
// These headers are short-lived as they are immediately followed by a
|
||||
// header of the same or lower level, such that they never actually get
|
||||
// to repeat.
|
||||
self.layout_new_headers(consecutive_headers, true, engine)
|
||||
} else {
|
||||
self.layout_new_pending_headers(consecutive_headers, engine)
|
||||
}
|
||||
}
|
||||
|
||||
/// Lays out a row while indicating that it should store its persistent
|
||||
|
@ -438,6 +438,12 @@ pub struct Header {
|
||||
/// lower level header stops repeating, all higher level headers do as
|
||||
/// well.
|
||||
pub level: u32,
|
||||
/// Whether this header cannot be repeated nor should have orphan
|
||||
/// prevention because it would be about to cease repetition, either
|
||||
/// because it is followed by headers of conflicting levels, or because
|
||||
/// it is at the end of the table (possibly followed by some footers at the
|
||||
/// end).
|
||||
pub short_lived: bool,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
@ -469,12 +475,15 @@ impl Footer {
|
||||
}
|
||||
}
|
||||
|
||||
/// A possibly repeatable grid object.
|
||||
/// A possibly repeatable grid child.
|
||||
///
|
||||
/// It still exists even when not repeatable, but must not have additional
|
||||
/// considerations by grid layout, other than for consistency (such as making
|
||||
/// a certain group of rows unbreakable).
|
||||
pub enum Repeatable<T> {
|
||||
/// The user asked this grid child to repeat.
|
||||
Repeated(T),
|
||||
/// The user asked this grid child to not repeat.
|
||||
NotRepeated(T),
|
||||
}
|
||||
|
||||
@ -490,7 +499,7 @@ impl<T> Repeatable<T> {
|
||||
}
|
||||
|
||||
/// Gets the value inside this repeatable, regardless of whether
|
||||
/// it repeats.
|
||||
/// it repeats (mutably).
|
||||
#[inline]
|
||||
pub fn unwrap_mut(&mut self) -> &mut T {
|
||||
match self {
|
||||
@ -1540,8 +1549,29 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
||||
end: group_range.end,
|
||||
|
||||
level: row_group.repeatable_level.get(),
|
||||
|
||||
// This can only change at a later iteration, if we
|
||||
// find a conflicting header or footer right away.
|
||||
short_lived: false,
|
||||
};
|
||||
|
||||
// Mark consecutive headers right before this one as short
|
||||
// lived if they would have a higher or equal level, as
|
||||
// then they would immediately stop repeating during
|
||||
// layout.
|
||||
let mut consecutive_header_start = data.start;
|
||||
for conflicting_header in
|
||||
headers.iter_mut().rev().take_while(move |h| {
|
||||
let conflicts = h.unwrap().end == consecutive_header_start
|
||||
&& h.unwrap().level >= data.level;
|
||||
|
||||
consecutive_header_start = h.unwrap().start;
|
||||
conflicts
|
||||
})
|
||||
{
|
||||
conflicting_header.unwrap_mut().short_lived = true;
|
||||
}
|
||||
|
||||
headers.push(if row_group.repeat {
|
||||
Repeatable::Repeated(data)
|
||||
} else {
|
||||
@ -1826,6 +1856,20 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
||||
}
|
||||
});
|
||||
|
||||
// Mark consecutive headers right before the end of the table, or the
|
||||
// final footer, as short lived, given that there are no normal rows
|
||||
// after them, so repeating them is pointless.
|
||||
let mut consecutive_header_start =
|
||||
footer.as_ref().map(|f| f.unwrap().start).unwrap_or(row_amount);
|
||||
for header_at_the_end in headers.iter_mut().rev().take_while(move |h| {
|
||||
let at_the_end = h.unwrap().end == consecutive_header_start;
|
||||
|
||||
consecutive_header_start = h.unwrap().start;
|
||||
at_the_end
|
||||
}) {
|
||||
header_at_the_end.unwrap_mut().short_lived = true;
|
||||
}
|
||||
|
||||
Ok(footer)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user