mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +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 y = 0;
|
||||||
let mut consecutive_header_count = 0;
|
let mut consecutive_header_count = 0;
|
||||||
while y < self.grid.rows.len() {
|
while y < self.grid.rows.len() {
|
||||||
if let Some(first_header) =
|
if let Some(next_header) = self.upcoming_headers.get(consecutive_header_count)
|
||||||
self.upcoming_headers.get(consecutive_header_count)
|
|
||||||
{
|
{
|
||||||
if first_header.unwrap().range().contains(&y) {
|
if next_header.unwrap().range().contains(&y) {
|
||||||
consecutive_header_count += 1;
|
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.
|
// Skip header rows during normal layout.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -9,71 +9,44 @@ use super::rowspans::UnbreakableRowGroup;
|
|||||||
impl<'a> GridLayouter<'a> {
|
impl<'a> GridLayouter<'a> {
|
||||||
pub fn place_new_headers(
|
pub fn place_new_headers(
|
||||||
&mut self,
|
&mut self,
|
||||||
consecutive_header_count: usize,
|
consecutive_header_count: &mut usize,
|
||||||
conflicting_header: Option<&Repeatable<Header>>,
|
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
|
*consecutive_header_count += 1;
|
||||||
let (consecutive_headers, new_upcoming_headers) =
|
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;
|
self.upcoming_headers = new_upcoming_headers;
|
||||||
|
*consecutive_header_count = 0;
|
||||||
|
|
||||||
let (non_conflicting_headers, conflicting_headers) = match conflicting_header {
|
// Layout short-lived headers immediately.
|
||||||
// Headers succeeded by end of grid or footer are short lived and
|
if consecutive_headers.last().is_some_and(|h| h.unwrap().short_lived) {
|
||||||
// can be placed in separate regions (no orphan prevention).
|
// No chance of orphans as we're immediately placing conflicting
|
||||||
// TODO: do this during grid resolving?
|
// headers afterwards, which basically are not headers, for all intents
|
||||||
// might be needed for multiple footers. Or maybe not if we check
|
// and purposes. It is therefore guaranteed that all new headers have
|
||||||
// "upcoming_footers" (O(1) here), however that looks like.
|
// been placed at least once.
|
||||||
_ 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() {
|
|
||||||
self.flush_pending_headers();
|
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
|
/// 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
|
/// lower level header stops repeating, all higher level headers do as
|
||||||
/// well.
|
/// well.
|
||||||
pub level: u32,
|
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 {
|
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
|
/// It still exists even when not repeatable, but must not have additional
|
||||||
/// considerations by grid layout, other than for consistency (such as making
|
/// considerations by grid layout, other than for consistency (such as making
|
||||||
/// a certain group of rows unbreakable).
|
/// a certain group of rows unbreakable).
|
||||||
pub enum Repeatable<T> {
|
pub enum Repeatable<T> {
|
||||||
|
/// The user asked this grid child to repeat.
|
||||||
Repeated(T),
|
Repeated(T),
|
||||||
|
/// The user asked this grid child to not repeat.
|
||||||
NotRepeated(T),
|
NotRepeated(T),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,7 +499,7 @@ impl<T> Repeatable<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the value inside this repeatable, regardless of whether
|
/// Gets the value inside this repeatable, regardless of whether
|
||||||
/// it repeats.
|
/// it repeats (mutably).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn unwrap_mut(&mut self) -> &mut T {
|
pub fn unwrap_mut(&mut self) -> &mut T {
|
||||||
match self {
|
match self {
|
||||||
@ -1540,8 +1549,29 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
|||||||
end: group_range.end,
|
end: group_range.end,
|
||||||
|
|
||||||
level: row_group.repeatable_level.get(),
|
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 {
|
headers.push(if row_group.repeat {
|
||||||
Repeatable::Repeated(data)
|
Repeatable::Repeated(data)
|
||||||
} else {
|
} 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)
|
Ok(footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user