mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
begin placing new headers
Considerations: - Need to change layout headers algorithm to 1. Place those headers 2. But in a new region, also place other repeating headers 3. Keep footer height up-to-date without much intervention
This commit is contained in:
parent
b420588c19
commit
6029e6f3bb
@ -55,11 +55,9 @@ pub struct GridLayouter<'a> {
|
|||||||
/// Note that some levels may be absent, in particular level 0, which does
|
/// Note that some levels may be absent, in particular level 0, which does
|
||||||
/// not exist (so the first level is >= 1).
|
/// not exist (so the first level is >= 1).
|
||||||
pub(super) repeating_headers: Vec<&'a Header>,
|
pub(super) repeating_headers: Vec<&'a Header>,
|
||||||
/// End of sequence of consecutive compatible headers found so far.
|
/// Headers, repeating or not, awaiting their first successful layout.
|
||||||
/// This is one position after the last index in `upcoming_headers`, so `0`
|
|
||||||
/// indicates no pending headers.
|
|
||||||
/// Sorted by increasing levels.
|
/// Sorted by increasing levels.
|
||||||
pub(super) pending_header_end: usize,
|
pub(super) pending_headers: &'a [Repeatable<Header>],
|
||||||
pub(super) upcoming_headers: &'a [Repeatable<Header>],
|
pub(super) upcoming_headers: &'a [Repeatable<Header>],
|
||||||
/// 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
|
||||||
@ -136,7 +134,7 @@ 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,
|
||||||
pending_header_end: 0,
|
pending_headers: Default::default(),
|
||||||
header_height: Abs::zero(),
|
header_height: Abs::zero(),
|
||||||
footer_height: Abs::zero(),
|
footer_height: Abs::zero(),
|
||||||
span,
|
span,
|
||||||
@ -151,31 +149,34 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Ensure rows in the first region will be aware of the possible
|
// Ensure rows in the first region will be aware of the possible
|
||||||
// presence of the footer.
|
// presence of the footer.
|
||||||
self.prepare_footer(footer, engine, 0)?;
|
self.prepare_footer(footer, engine, 0)?;
|
||||||
if !matches!(
|
|
||||||
self.grid.headers.first(),
|
|
||||||
Some(Repeatable::Repeated(Header { start: 0, .. }))
|
|
||||||
) {
|
|
||||||
// No repeatable header at the very beginning, so we won't
|
|
||||||
// subtract it later.
|
|
||||||
self.regions.size.y -= self.footer_height;
|
self.regions.size.y -= self.footer_height;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let mut y = 0;
|
let mut y = 0;
|
||||||
|
let mut consecutive_header_count = 0;
|
||||||
while y < self.grid.rows.len() {
|
while y < self.grid.rows.len() {
|
||||||
if let Some(first_header) = self.upcoming_headers.first() {
|
if let Some(first_header) =
|
||||||
|
self.upcoming_headers.get(consecutive_header_count)
|
||||||
|
{
|
||||||
if first_header.unwrap().range().contains(&y) {
|
if first_header.unwrap().range().contains(&y) {
|
||||||
self.bump_pending_headers();
|
consecutive_header_count += 1;
|
||||||
|
|
||||||
if self.peek_upcoming_header().is_none_or(|h| {
|
if self.upcoming_headers.get(consecutive_header_count).is_none_or(
|
||||||
|
|h| {
|
||||||
h.unwrap().start > y + 1
|
h.unwrap().start > y + 1
|
||||||
|| h.unwrap().level <= first_header.unwrap().level
|
|| h.unwrap().level <= first_header.unwrap().level
|
||||||
}) {
|
},
|
||||||
|
) {
|
||||||
// Next row either isn't a header. or is in a
|
// Next row either isn't a header. or is in a
|
||||||
// conflicting one, which is the sign that we need to go.
|
// conflicting one, which is the sign that we need to go.
|
||||||
self.layout_headers(next_header, engine, 0)?;
|
self.place_new_headers(
|
||||||
|
first_header,
|
||||||
|
consecutive_header_count,
|
||||||
|
engine,
|
||||||
|
);
|
||||||
|
consecutive_header_count = 0;
|
||||||
}
|
}
|
||||||
y = first_header.end;
|
y = first_header.unwrap().end;
|
||||||
// Skip header rows during normal layout.
|
// Skip header rows during normal layout.
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::ops::ControlFlow;
|
|
||||||
|
|
||||||
use typst_library::diag::SourceResult;
|
use typst_library::diag::SourceResult;
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
@ -9,20 +8,74 @@ use super::layouter::GridLayouter;
|
|||||||
use super::rowspans::UnbreakableRowGroup;
|
use super::rowspans::UnbreakableRowGroup;
|
||||||
|
|
||||||
impl<'a> GridLayouter<'a> {
|
impl<'a> GridLayouter<'a> {
|
||||||
#[inline]
|
pub fn place_new_headers(
|
||||||
fn pending_headers(&self) -> &'a [Repeatable<Header>] {
|
&mut self,
|
||||||
&self.upcoming_headers[..self.pending_header_end]
|
first_header: &Repeatable<Header>,
|
||||||
|
consecutive_header_count: usize,
|
||||||
|
engine: &mut Engine,
|
||||||
|
) {
|
||||||
|
// Next row either isn't a header. or is in a
|
||||||
|
// conflicting one, which is the sign that we need to go.
|
||||||
|
let (consecutive_headers, new_upcoming_headers) =
|
||||||
|
self.upcoming_headers.split_at(consecutive_header_count);
|
||||||
|
self.upcoming_headers = new_upcoming_headers;
|
||||||
|
|
||||||
|
let (non_conflicting_headers, conflicting_headers) = match self
|
||||||
|
.upcoming_headers
|
||||||
|
.get(consecutive_header_count)
|
||||||
|
.map(Repeatable::unwrap)
|
||||||
|
{
|
||||||
|
Some(next_header) if next_header.level <= first_header.unwrap().level => {
|
||||||
|
// All immediately conflicting headers will
|
||||||
|
// be placed as normal rows.
|
||||||
|
consecutive_headers.split_at(
|
||||||
|
consecutive_headers
|
||||||
|
.partition_point(|h| next_header.level > h.unwrap().level),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => (consecutive_headers, Default::default()),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.layout_new_pending_headers(non_conflicting_headers, engine);
|
||||||
|
|
||||||
|
self.layout_headers(non_conflicting_headers, engine, 0)?;
|
||||||
|
for conflicting_header in conflicting_headers {
|
||||||
|
self.simulate();
|
||||||
|
self.layout_headers(headers, engine, disambiguator)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Queues new pending headers for layout. Headers remain pending until
|
||||||
pub fn bump_pending_headers(&mut self) {
|
/// they are successfully laid out in some page once. Then, they will be
|
||||||
debug_assert!(!self.upcoming_headers.is_empty());
|
/// moved to `repeating_headers`, at which point it is safe to stop them
|
||||||
self.pending_header_end += 1;
|
/// from repeating at any time.
|
||||||
}
|
fn layout_new_pending_headers(
|
||||||
|
&mut self,
|
||||||
|
headers: &'a [Repeatable<Header>],
|
||||||
|
engine: &mut Engine,
|
||||||
|
) {
|
||||||
|
let [first_header, ..] = headers else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// 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.unwrap().level;
|
||||||
|
|
||||||
#[inline]
|
// Stop repeating conflicting headers.
|
||||||
pub fn peek_upcoming_header(&self) -> Option<&'a Repeatable<Header>> {
|
// If we go to a new region before the pending headers fit alongside
|
||||||
self.upcoming_headers.get(self.pending_header_end)
|
// their children, the old headers should not be displayed anymore.
|
||||||
|
self.repeating_headers
|
||||||
|
.truncate(self.repeating_headers.partition_point(|h| h.level < first_level));
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
self.layout_headers(headers.iter().map(Repeatable::unwrap), true, 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.
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush_pending_headers(&mut self) {
|
pub fn flush_pending_headers(&mut self) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user