mirror of
https://github.com/typst/typst
synced 2025-05-22 04:55:29 +08:00
ensure short-lived headers also replace predecessors
Even if they don't repeat, they should cause previous headers to stop repeating.
This commit is contained in:
parent
302e93fb4b
commit
6e6f21f78b
@ -49,6 +49,33 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.upcoming_headers = new_upcoming_headers;
|
self.upcoming_headers = new_upcoming_headers;
|
||||||
*consecutive_header_count = 0;
|
*consecutive_header_count = 0;
|
||||||
|
|
||||||
|
let [first_header, ..] = consecutive_headers else {
|
||||||
|
self.flush_orphans();
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assuming non-conflicting headers sorted by increasing y, this must
|
||||||
|
// be the header with the lowest level (sorted by increasing levels).
|
||||||
|
let first_level = first_header.level;
|
||||||
|
|
||||||
|
// Stop repeating conflicting headers, even if the new headers are
|
||||||
|
// short-lived or won't repeat.
|
||||||
|
//
|
||||||
|
// If we go to a new region before the new headers fit alongside their
|
||||||
|
// children (or in general, for short-lived), the old headers should
|
||||||
|
// not be displayed anymore.
|
||||||
|
let first_conflicting_pos =
|
||||||
|
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.current.repeating_header_heights.drain(first_conflicting_pos..)
|
||||||
|
{
|
||||||
|
self.current.repeating_header_height -= removed_height;
|
||||||
|
}
|
||||||
|
|
||||||
// Layout short-lived headers immediately.
|
// Layout short-lived headers immediately.
|
||||||
if consecutive_headers.last().is_some_and(|h| h.short_lived) {
|
if consecutive_headers.last().is_some_and(|h| h.short_lived) {
|
||||||
// No chance of orphans as we're immediately placing conflicting
|
// No chance of orphans as we're immediately placing conflicting
|
||||||
@ -63,11 +90,32 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// header of the same or lower level, such that they never actually get
|
// header of the same or lower level, such that they never actually get
|
||||||
// to repeat.
|
// to repeat.
|
||||||
self.layout_new_headers(consecutive_headers, true, engine)?;
|
self.layout_new_headers(consecutive_headers, true, engine)?;
|
||||||
|
} else {
|
||||||
|
// Let's try to place pending headers at least once.
|
||||||
|
// 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.
|
||||||
|
let snapshot_created =
|
||||||
|
self.layout_new_headers(consecutive_headers, false, engine)?;
|
||||||
|
|
||||||
|
// Queue the new headers for layout. They will remain in this
|
||||||
|
// vector due to orphan prevention.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// prevention anymore.
|
||||||
|
self.pending_headers = consecutive_headers;
|
||||||
|
|
||||||
|
if !snapshot_created {
|
||||||
|
// Region probably couldn't progress.
|
||||||
|
//
|
||||||
|
// Mark new pending headers as final and ensure there isn't a
|
||||||
|
// snapshot.
|
||||||
|
self.flush_orphans();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
|
||||||
self.layout_new_pending_headers(consecutive_headers, engine)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lays out rows belonging to a header, returning the calculated header
|
/// Lays out rows belonging to a header, returning the calculated header
|
||||||
@ -100,66 +148,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
Ok(header_height)
|
Ok(header_height)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queues new pending headers for layout. Headers remain pending until
|
|
||||||
/// 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
|
|
||||||
/// from repeating at any time.
|
|
||||||
fn layout_new_pending_headers(
|
|
||||||
&mut self,
|
|
||||||
headers: &'a [Repeatable<Header>],
|
|
||||||
engine: &mut Engine,
|
|
||||||
) -> SourceResult<()> {
|
|
||||||
let [first_header, ..] = headers else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Should be impossible to have two consecutive chunks of pending
|
|
||||||
// headers since they are always as long as possible, only being
|
|
||||||
// interrupted by direct conflict between consecutive headers, in which
|
|
||||||
// case we flush pending headers immediately.
|
|
||||||
assert!(self.pending_headers.is_empty());
|
|
||||||
|
|
||||||
// Assuming non-conflicting headers sorted by increasing y, this must
|
|
||||||
// be the header with the lowest level (sorted by increasing levels).
|
|
||||||
let first_level = first_header.level;
|
|
||||||
|
|
||||||
// Stop repeating conflicting headers.
|
|
||||||
// If we go to a new region before the pending headers fit alongside
|
|
||||||
// their children, the old headers should not be displayed anymore.
|
|
||||||
let first_conflicting_pos =
|
|
||||||
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.current.repeating_header_heights.drain(first_conflicting_pos..)
|
|
||||||
{
|
|
||||||
self.current.repeating_header_height -= removed_height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's try to place them at least once.
|
|
||||||
// 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.
|
|
||||||
let snapshot_created = 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
|
|
||||||
// prevention anymore.
|
|
||||||
self.pending_headers = headers;
|
|
||||||
|
|
||||||
if !snapshot_created {
|
|
||||||
// Region probably couldn't progress.
|
|
||||||
//
|
|
||||||
// Mark new pending headers as final and ensure there isn't a
|
|
||||||
// snapshot.
|
|
||||||
self.flush_orphans();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function should be called each time an additional row has been
|
/// This function should be called each time an additional row has been
|
||||||
/// laid out in a region to indicate that orphan prevention has succeeded.
|
/// laid out in a region to indicate that orphan prevention has succeeded.
|
||||||
///
|
///
|
||||||
|
BIN
tests/ref/grid-subheaders-repeat-short-lived-also-replaces.png
Normal file
BIN
tests/ref/grid-subheaders-repeat-short-lived-also-replaces.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 899 B |
@ -389,6 +389,59 @@
|
|||||||
..([z],) * 10,
|
..([z],) * 10,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
--- grid-subheaders-repeat-short-lived-also-replaces ---
|
||||||
|
// Short-lived subheaders must still replace their conflicting predecessors.
|
||||||
|
#set page(height: 8em)
|
||||||
|
#grid(
|
||||||
|
// This has to go
|
||||||
|
grid.header(
|
||||||
|
[a],
|
||||||
|
level: 3,
|
||||||
|
),
|
||||||
|
[w],
|
||||||
|
grid.header(
|
||||||
|
level: 2,
|
||||||
|
[b]
|
||||||
|
),
|
||||||
|
grid.header(
|
||||||
|
level: 2,
|
||||||
|
[c]
|
||||||
|
),
|
||||||
|
grid.header(
|
||||||
|
level: 2,
|
||||||
|
[d]
|
||||||
|
),
|
||||||
|
grid.header(
|
||||||
|
level: 2,
|
||||||
|
[e]
|
||||||
|
),
|
||||||
|
grid.header(
|
||||||
|
level: 2,
|
||||||
|
[f]
|
||||||
|
),
|
||||||
|
grid.header(
|
||||||
|
level: 2,
|
||||||
|
[g]
|
||||||
|
),
|
||||||
|
grid.header(
|
||||||
|
level: 2,
|
||||||
|
[h]
|
||||||
|
),
|
||||||
|
grid.header(
|
||||||
|
level: 2,
|
||||||
|
[i]
|
||||||
|
),
|
||||||
|
grid.header(
|
||||||
|
level: 2,
|
||||||
|
[j]
|
||||||
|
),
|
||||||
|
grid.header(
|
||||||
|
level: 3,
|
||||||
|
[k]
|
||||||
|
),
|
||||||
|
..([z],) * 10,
|
||||||
|
)
|
||||||
|
|
||||||
--- grid-subheaders-multi-page-row ---
|
--- grid-subheaders-multi-page-row ---
|
||||||
#set page(height: 8em)
|
#set page(height: 8em)
|
||||||
#grid(
|
#grid(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user