mirror of
https://github.com/typst/typst
synced 2025-05-17 02:25:27 +08:00
finish region adjustments
- flush pending headers - properly layout headers at region start
This commit is contained in:
parent
63055c8c83
commit
e586cffa6c
@ -15,6 +15,7 @@ use typst_library::visualize::Geometry;
|
|||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
use typst_utils::{MaybeReverseIter, Numeric};
|
use typst_utils::{MaybeReverseIter, Numeric};
|
||||||
|
|
||||||
|
use super::repeated::HeadersToLayout;
|
||||||
use super::{
|
use super::{
|
||||||
generate_line_segments, hline_stroke_at_column, layout_cell, vline_stroke_at_row,
|
generate_line_segments, hline_stroke_at_column, layout_cell, vline_stroke_at_row,
|
||||||
LineSegment, Rowspan, UnbreakableRowGroup,
|
LineSegment, Rowspan, UnbreakableRowGroup,
|
||||||
@ -1388,30 +1389,19 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.lrows.pop().unwrap();
|
self.lrows.pop().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no rows other than the footer have been laid out so far, and
|
let footer_would_be_widow = if let Some(last_header) = self
|
||||||
// there are rows beside the footer, then don't lay it out at all.
|
.pending_headers
|
||||||
// This check doesn't apply, and is thus overridden, when there is a
|
.last()
|
||||||
// header.
|
.map(Repeatable::unwrap)
|
||||||
let mut footer_would_be_orphan = self.lrows.is_empty()
|
.or_else(|| self.repeating_headers.last().map(|h| *h))
|
||||||
&& !in_last_with_offset(
|
{
|
||||||
self.regions,
|
|
||||||
self.header_height + self.footer_height,
|
|
||||||
)
|
|
||||||
&& self
|
|
||||||
.grid
|
|
||||||
.footer
|
|
||||||
.as_ref()
|
|
||||||
.and_then(Repeatable::as_repeated)
|
|
||||||
.is_some_and(|footer| footer.start != 0);
|
|
||||||
|
|
||||||
if let Some(last_header) = self.repeating_headers.last() {
|
|
||||||
if self.grid.rows.len() > last_header.end
|
if self.grid.rows.len() > last_header.end
|
||||||
&& self
|
&& self
|
||||||
.grid
|
.grid
|
||||||
.footer
|
.footer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(Repeatable::as_repeated)
|
.and_then(Repeatable::as_repeated)
|
||||||
.is_none_or(|footer| footer.start != header.end)
|
.is_none_or(|footer| footer.start != last_header.end)
|
||||||
&& self.lrows.last().is_some_and(|row| row.index() < last_header.end)
|
&& self.lrows.last().is_some_and(|row| row.index() < last_header.end)
|
||||||
&& !in_last_with_offset(
|
&& !in_last_with_offset(
|
||||||
self.regions,
|
self.regions,
|
||||||
@ -1421,21 +1411,43 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Header and footer would be alone in this region, but there are more
|
// Header and footer would be alone in this region, but there are more
|
||||||
// rows beyond the header and the footer. Push an empty region.
|
// rows beyond the header and the footer. Push an empty region.
|
||||||
self.lrows.clear();
|
self.lrows.clear();
|
||||||
footer_would_be_orphan = true;
|
true
|
||||||
}
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
} else if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
|
||||||
|
// If no rows other than the footer have been laid out so far, and
|
||||||
|
// there are rows beside the footer, then don't lay it out at all.
|
||||||
|
// (Similar check from above, but for the case without headers.)
|
||||||
|
// TODO: widow prevention for non-repeated footers with a similar
|
||||||
|
// mechanism / when implementing multiple footers.
|
||||||
|
self.lrows.is_empty()
|
||||||
|
&& !in_last_with_offset(
|
||||||
|
self.regions,
|
||||||
|
self.header_height + self.footer_height,
|
||||||
|
)
|
||||||
|
&& footer.start != 0
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let mut laid_out_footer_start = None;
|
let mut laid_out_footer_start = None;
|
||||||
|
if !footer_would_be_widow {
|
||||||
|
// Did not trigger automatic header orphan / footer widow check.
|
||||||
|
// This means pending headers have successfully been placed once
|
||||||
|
// without hitting orphan prevention, so they may now be moved into
|
||||||
|
// repeating headers.
|
||||||
|
self.flush_pending_headers();
|
||||||
|
|
||||||
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
|
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
|
||||||
// Don't layout the footer if it would be alone with the header in
|
// Don't layout the footer if it would be alone with the header in
|
||||||
// the page, and don't layout it twice.
|
// the page (hence the widow check), and don't layout it twice.
|
||||||
if !footer_would_be_orphan
|
if self.lrows.iter().all(|row| row.index() < footer.start) {
|
||||||
&& self.lrows.iter().all(|row| row.index() < footer.start)
|
|
||||||
{
|
|
||||||
laid_out_footer_start = Some(footer.start);
|
laid_out_footer_start = Some(footer.start);
|
||||||
self.layout_footer(footer, engine, self.finished.len())?;
|
self.layout_footer(footer, engine, self.finished.len())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the height of existing rows in the region.
|
// Determine the height of existing rows in the region.
|
||||||
let mut used = Abs::zero();
|
let mut used = Abs::zero();
|
||||||
@ -1569,13 +1581,16 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.prepare_footer(footer, engine, disambiguator)?;
|
self.prepare_footer(footer, engine, disambiguator)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.repeating_headers.is_empty() {
|
|
||||||
// Add a header to the new region.
|
|
||||||
self.layout_headers(engine, disambiguator)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure rows don't try to overrun the footer.
|
// Ensure rows don't try to overrun the footer.
|
||||||
|
// Note that header layout will only subtract this again if it has
|
||||||
|
// to skip regions to fit headers, so there is no risk of
|
||||||
|
// subtracting this twice.
|
||||||
self.regions.size.y -= self.footer_height;
|
self.regions.size.y -= self.footer_height;
|
||||||
|
|
||||||
|
if !self.repeating_headers.is_empty() || !self.pending_headers.is_empty() {
|
||||||
|
// Add headers to the new region.
|
||||||
|
self.layout_headers(HeadersToLayout::RepeatingAndPending, engine)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -96,23 +96,16 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush_pending_headers(&mut self) {
|
pub fn flush_pending_headers(&mut self) {
|
||||||
debug_assert!(!self.upcoming_headers.is_empty());
|
for header in self.pending_headers {
|
||||||
debug_assert!(self.pending_header_end > 0);
|
|
||||||
let headers = self.pending_headers();
|
|
||||||
|
|
||||||
let [first_header, ..] = headers else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.repeating_headers.truncate(
|
|
||||||
self.repeating_headers
|
|
||||||
.partition_point(|h| h.level < first_header.unwrap().level),
|
|
||||||
);
|
|
||||||
|
|
||||||
for header in self.pending_headers() {
|
|
||||||
if let Repeatable::Repeated(header) = header {
|
if let Repeatable::Repeated(header) = header {
|
||||||
// Vector remains sorted by increasing levels:
|
// Vector remains sorted by increasing levels:
|
||||||
// - It was sorted before, so the truncation above only keeps
|
// - 'pending_headers' themselves are sorted, since we only
|
||||||
|
// push non-mutually-conflicting headers at a time.
|
||||||
|
// - Before pushing new pending headers in
|
||||||
|
// 'layout_new_pending_headers', we truncate repeating headers
|
||||||
|
// to remove anything with the same or higher levels as the
|
||||||
|
// first pending header.
|
||||||
|
// - Assuming it was sorted before, that truncation only keeps
|
||||||
// elements with a lower level.
|
// elements with a lower level.
|
||||||
// - Therefore, by pushing this header to the end, it will have
|
// - Therefore, by pushing this header to the end, it will have
|
||||||
// a level larger than all the previous headers, and is thus
|
// a level larger than all the previous headers, and is thus
|
||||||
@ -121,12 +114,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.upcoming_headers = self
|
self.pending_headers = Default::default();
|
||||||
.upcoming_headers
|
|
||||||
.get(self.pending_header_end..)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
self.pending_header_end = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bump_repeating_headers(&mut self) {
|
pub fn bump_repeating_headers(&mut self) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user