mirror of
https://github.com/typst/typst
synced 2025-05-17 02:25:27 +08:00
154 lines
5.3 KiB
Rust
154 lines
5.3 KiB
Rust
use typst_library::diag::SourceResult;
|
|
use typst_library::engine::Engine;
|
|
use typst_library::layout::grid::resolve::{Footer, Header, Repeatable};
|
|
use typst_library::layout::{Abs, Axes, Frame, Regions};
|
|
|
|
use super::layouter::GridLayouter;
|
|
use super::rowspans::UnbreakableRowGroup;
|
|
|
|
impl GridLayouter<'_> {
|
|
/// Layouts the header's rows.
|
|
/// Skips regions as necessary.
|
|
pub fn layout_header(
|
|
&mut self,
|
|
header: &Header,
|
|
engine: &mut Engine,
|
|
disambiguator: usize,
|
|
) -> SourceResult<()> {
|
|
let header_rows =
|
|
self.simulate_header(header, &self.regions, engine, disambiguator)?;
|
|
let mut skipped_region = false;
|
|
while self.unbreakable_rows_left == 0
|
|
&& !self.regions.size.y.fits(header_rows.height + self.footer_height)
|
|
&& self.regions.may_progress()
|
|
{
|
|
// Advance regions without any output until we can place the
|
|
// header and the footer.
|
|
self.finish_region_internal(Frame::soft(Axes::splat(Abs::zero())), vec![]);
|
|
skipped_region = true;
|
|
}
|
|
|
|
// Reset the header height for this region.
|
|
// It will be re-calculated when laying out each header row.
|
|
self.header_height = Abs::zero();
|
|
|
|
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
|
|
if skipped_region {
|
|
// Simulate the footer again; the region's 'full' might have
|
|
// changed.
|
|
self.footer_height = self
|
|
.simulate_footer(footer, &self.regions, engine, disambiguator)?
|
|
.height;
|
|
}
|
|
}
|
|
|
|
// Header is unbreakable.
|
|
// Thus, no risk of 'finish_region' being recursively called from
|
|
// within 'layout_row'.
|
|
self.unbreakable_rows_left += header.end;
|
|
for y in 0..header.end {
|
|
self.layout_row(y, engine, disambiguator)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Simulate the header's group of rows.
|
|
pub fn simulate_header(
|
|
&self,
|
|
header: &Header,
|
|
regions: &Regions<'_>,
|
|
engine: &mut Engine,
|
|
disambiguator: usize,
|
|
) -> SourceResult<UnbreakableRowGroup> {
|
|
// Note that we assume the invariant that any rowspan in a header is
|
|
// fully contained within that header. Therefore, there won't be any
|
|
// unbreakable rowspans exceeding the header's rows, and we can safely
|
|
// assume that the amount of unbreakable rows following the first row
|
|
// in the header will be precisely the rows in the header.
|
|
self.simulate_unbreakable_row_group(
|
|
0,
|
|
Some(header.end),
|
|
regions,
|
|
engine,
|
|
disambiguator,
|
|
)
|
|
}
|
|
|
|
/// Updates `self.footer_height` by simulating the footer, and skips to fitting region.
|
|
pub fn prepare_footer(
|
|
&mut self,
|
|
footer: &Footer,
|
|
engine: &mut Engine,
|
|
disambiguator: usize,
|
|
) -> SourceResult<()> {
|
|
let footer_height = self
|
|
.simulate_footer(footer, &self.regions, engine, disambiguator)?
|
|
.height;
|
|
let mut skipped_region = false;
|
|
while self.unbreakable_rows_left == 0
|
|
&& !self.regions.size.y.fits(footer_height)
|
|
&& self.regions.may_progress()
|
|
{
|
|
// Advance regions without any output until we can place the
|
|
// footer.
|
|
self.finish_region_internal(Frame::soft(Axes::splat(Abs::zero())), vec![]);
|
|
skipped_region = true;
|
|
}
|
|
|
|
self.footer_height = if skipped_region {
|
|
// Simulate the footer again; the region's 'full' might have
|
|
// changed.
|
|
self.simulate_footer(footer, &self.regions, engine, disambiguator)?
|
|
.height
|
|
} else {
|
|
footer_height
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Lays out all rows in the footer.
|
|
/// They are unbreakable.
|
|
pub fn layout_footer(
|
|
&mut self,
|
|
footer: &Footer,
|
|
engine: &mut Engine,
|
|
disambiguator: usize,
|
|
) -> SourceResult<()> {
|
|
// Ensure footer rows have their own height available.
|
|
// Won't change much as we're creating an unbreakable row group
|
|
// anyway, so this is mostly for correctness.
|
|
self.regions.size.y += self.footer_height;
|
|
|
|
let footer_len = self.grid.rows.len() - footer.start;
|
|
self.unbreakable_rows_left += footer_len;
|
|
for y in footer.start..self.grid.rows.len() {
|
|
self.layout_row(y, engine, disambiguator)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Simulate the footer's group of rows.
|
|
pub fn simulate_footer(
|
|
&self,
|
|
footer: &Footer,
|
|
regions: &Regions<'_>,
|
|
engine: &mut Engine,
|
|
disambiguator: usize,
|
|
) -> SourceResult<UnbreakableRowGroup> {
|
|
// Note that we assume the invariant that any rowspan in a footer is
|
|
// fully contained within that footer. Therefore, there won't be any
|
|
// unbreakable rowspans exceeding the footer's rows, and we can safely
|
|
// assume that the amount of unbreakable rows following the first row
|
|
// in the footer will be precisely the rows in the footer.
|
|
self.simulate_unbreakable_row_group(
|
|
footer.start,
|
|
Some(self.grid.rows.len() - footer.start),
|
|
regions,
|
|
engine,
|
|
disambiguator,
|
|
)
|
|
}
|
|
}
|