mirror of
https://github.com/typst/typst
synced 2025-05-15 01:25:28 +08:00
count header rows in each region
- Currently also includes non-repeatable header row heights, which will lead to bugs on rowspans; needs fix
This commit is contained in:
parent
e586cffa6c
commit
1301b901b7
@ -46,8 +46,21 @@ pub struct GridLayouter<'a> {
|
|||||||
pub(super) rowspans: Vec<Rowspan>,
|
pub(super) rowspans: Vec<Rowspan>,
|
||||||
/// The initial size of the current region before we started subtracting.
|
/// The initial size of the current region before we started subtracting.
|
||||||
pub(super) initial: Size,
|
pub(super) initial: Size,
|
||||||
|
/// The amount of header rows at the start of the current region. Note that
|
||||||
|
/// `repeating_headers` and `pending_headers` can change if we find a new
|
||||||
|
/// header inside the region, so this has to be stored separately for each
|
||||||
|
/// new region - we can't just reuse those.
|
||||||
|
///
|
||||||
|
/// This is used for orphan prevention checks (if there are no rows other
|
||||||
|
/// than header rows upon finishing a region, we'd have an orphan). In
|
||||||
|
/// addition, this information is stored for each finished region so
|
||||||
|
/// rowspans placed later can know which rows to skip at the top of the
|
||||||
|
/// region when spanning more than one page.
|
||||||
|
pub(super) current_header_rows: usize,
|
||||||
/// Frames for finished regions.
|
/// Frames for finished regions.
|
||||||
pub(super) finished: Vec<Frame>,
|
pub(super) finished: Vec<Frame>,
|
||||||
|
/// The height of header rows on each finished region.
|
||||||
|
pub(super) finished_header_rows: Vec<Abs>,
|
||||||
/// Whether this is an RTL grid.
|
/// Whether this is an RTL grid.
|
||||||
pub(super) is_rtl: bool,
|
pub(super) is_rtl: bool,
|
||||||
/// Currently repeating headers, one per level.
|
/// Currently repeating headers, one per level.
|
||||||
@ -131,7 +144,9 @@ impl<'a> GridLayouter<'a> {
|
|||||||
unbreakable_rows_left: 0,
|
unbreakable_rows_left: 0,
|
||||||
rowspans: vec![],
|
rowspans: vec![],
|
||||||
initial: regions.size,
|
initial: regions.size,
|
||||||
|
current_header_rows: 0,
|
||||||
finished: vec![],
|
finished: vec![],
|
||||||
|
finished_header_rows: vec![],
|
||||||
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,
|
||||||
@ -979,16 +994,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let frame = self.layout_single_row(engine, disambiguator, first, y)?;
|
let frame = self.layout_single_row(engine, disambiguator, first, y)?;
|
||||||
self.push_row(frame, y, true);
|
self.push_row(frame, y, true);
|
||||||
|
|
||||||
if self
|
if self.lrows.len() < self.current_header_rows {
|
||||||
.grid
|
|
||||||
.header
|
|
||||||
.as_ref()
|
|
||||||
.and_then(Repeatable::as_repeated)
|
|
||||||
.is_some_and(|header| y < header.end)
|
|
||||||
{
|
|
||||||
// Add to header height, as we are in a header row.
|
// Add to header height, as we are in a header row.
|
||||||
// TODO: Should we only bump from upcoming_headers to
|
|
||||||
// repeating_headers AFTER the header height calculation?
|
|
||||||
self.header_height += first;
|
self.header_height += first;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1221,14 +1228,9 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let resolved = v.resolve(self.styles).relative_to(self.regions.base().y);
|
let resolved = v.resolve(self.styles).relative_to(self.regions.base().y);
|
||||||
let frame = self.layout_single_row(engine, disambiguator, resolved, y)?;
|
let frame = self.layout_single_row(engine, disambiguator, resolved, y)?;
|
||||||
|
|
||||||
if self
|
if self.lrows.len() < self.current_header_rows {
|
||||||
.grid
|
// Add to header height (not all headers were laid out yet, so this
|
||||||
.header
|
// must be a repeated or pending header at the top of the region).
|
||||||
.as_ref()
|
|
||||||
.and_then(Repeatable::as_repeated)
|
|
||||||
.is_some_and(|header| y < header.end)
|
|
||||||
{
|
|
||||||
// Add to header height.
|
|
||||||
self.header_height += resolved;
|
self.header_height += resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1389,20 +1391,20 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.lrows.pop().unwrap();
|
self.lrows.pop().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let footer_would_be_widow = if let Some(last_header) = self
|
let footer_would_be_widow = if let Some(last_header_row) = self
|
||||||
.pending_headers
|
.current_header_rows
|
||||||
.last()
|
.checked_sub(1)
|
||||||
.map(Repeatable::unwrap)
|
.and_then(|last_header_index| self.lrows.get(last_header_index))
|
||||||
.or_else(|| self.repeating_headers.last().map(|h| *h))
|
|
||||||
{
|
{
|
||||||
if self.grid.rows.len() > last_header.end
|
let last_header_end = last_header_row.index();
|
||||||
|
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 != last_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,
|
||||||
self.header_height + self.footer_height,
|
self.header_height + self.footer_height,
|
||||||
@ -1471,9 +1473,10 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let mut pos = Point::zero();
|
let mut pos = Point::zero();
|
||||||
let mut rrows = vec![];
|
let mut rrows = vec![];
|
||||||
let current_region = self.finished.len();
|
let current_region = self.finished.len();
|
||||||
|
let mut header_row_height = Abs::zero();
|
||||||
|
|
||||||
// Place finished rows and layout fractional rows.
|
// Place finished rows and layout fractional rows.
|
||||||
for row in std::mem::take(&mut self.lrows) {
|
for (i, row) in std::mem::take(&mut self.lrows).into_iter().enumerate() {
|
||||||
let (frame, y, is_last) = match row {
|
let (frame, y, is_last) = match row {
|
||||||
Row::Frame(frame, y, is_last) => (frame, y, is_last),
|
Row::Frame(frame, y, is_last) => (frame, y, is_last),
|
||||||
Row::Fr(v, y, disambiguator) => {
|
Row::Fr(v, y, disambiguator) => {
|
||||||
@ -1484,6 +1487,9 @@ impl<'a> GridLayouter<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let height = frame.height();
|
let height = frame.height();
|
||||||
|
if i < self.current_header_rows {
|
||||||
|
header_row_height += height;
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure rowspans which span this row will have enough space to
|
// Ensure rowspans which span this row will have enough space to
|
||||||
// be laid out over it later.
|
// be laid out over it later.
|
||||||
@ -1562,7 +1568,11 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// we have to check the same index again in the next
|
// we have to check the same index again in the next
|
||||||
// iteration.
|
// iteration.
|
||||||
let rowspan = self.rowspans.remove(i);
|
let rowspan = self.rowspans.remove(i);
|
||||||
self.layout_rowspan(rowspan, Some((&mut output, &rrows)), engine)?;
|
self.layout_rowspan(
|
||||||
|
rowspan,
|
||||||
|
Some((&mut output, header_row_height)),
|
||||||
|
engine,
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
@ -1573,9 +1583,10 @@ impl<'a> GridLayouter<'a> {
|
|||||||
pos.y += height;
|
pos.y += height;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.finish_region_internal(output, rrows);
|
self.finish_region_internal(output, rrows, header_row_height);
|
||||||
|
|
||||||
if !last {
|
if !last {
|
||||||
|
self.current_header_rows = 0;
|
||||||
let disambiguator = self.finished.len();
|
let disambiguator = self.finished.len();
|
||||||
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
|
if let Some(Repeatable::Repeated(footer)) = &self.grid.footer {
|
||||||
self.prepare_footer(footer, engine, disambiguator)?;
|
self.prepare_footer(footer, engine, disambiguator)?;
|
||||||
@ -1602,11 +1613,16 @@ impl<'a> GridLayouter<'a> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
output: Frame,
|
output: Frame,
|
||||||
resolved_rows: Vec<RowPiece>,
|
resolved_rows: Vec<RowPiece>,
|
||||||
|
header_row_height: Abs,
|
||||||
) {
|
) {
|
||||||
self.finished.push(output);
|
self.finished.push(output);
|
||||||
self.rrows.push(resolved_rows);
|
self.rrows.push(resolved_rows);
|
||||||
self.regions.next();
|
self.regions.next();
|
||||||
self.initial = self.regions.size;
|
self.initial = self.regions.size;
|
||||||
|
|
||||||
|
if !self.grid.headers.is_empty() {
|
||||||
|
self.finished_header_rows.push(header_row_height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +189,11 @@ impl<'a> GridLayouter<'a> {
|
|||||||
{
|
{
|
||||||
// Advance regions without any output until we can place the
|
// Advance regions without any output until we can place the
|
||||||
// header and the footer.
|
// header and the footer.
|
||||||
self.finish_region_internal(Frame::soft(Axes::splat(Abs::zero())), vec![]);
|
self.finish_region_internal(
|
||||||
|
Frame::soft(Axes::splat(Abs::zero())),
|
||||||
|
vec![],
|
||||||
|
Abs::zero(),
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: re-calculate heights of headers and footers on each region
|
// TODO: re-calculate heights of headers and footers on each region
|
||||||
// if 'full'changes? (Assuming height doesn't change for now...)
|
// if 'full'changes? (Assuming height doesn't change for now...)
|
||||||
@ -250,9 +254,18 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// TODO: maybe extract this into a function to share code with multiple
|
// TODO: maybe extract this into a function to share code with multiple
|
||||||
// footers.
|
// footers.
|
||||||
if matches!(headers, HeadersToLayout::RepeatingAndPending) || skipped_region {
|
if matches!(headers, HeadersToLayout::RepeatingAndPending) || skipped_region {
|
||||||
self.unbreakable_rows_left +=
|
let repeating_header_rows =
|
||||||
total_header_row_count(self.repeating_headers.iter().map(Deref::deref));
|
total_header_row_count(self.repeating_headers.iter().map(Deref::deref));
|
||||||
|
|
||||||
|
let pending_header_rows = total_header_row_count(
|
||||||
|
self.pending_headers.into_iter().map(Repeatable::unwrap),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Include both repeating and pending header rows as this number is
|
||||||
|
// used for orphan prevention.
|
||||||
|
self.current_header_rows = repeating_header_rows + pending_header_rows;
|
||||||
|
self.unbreakable_rows_left += repeating_header_rows + pending_header_rows;
|
||||||
|
|
||||||
// Use indices to avoid double borrow. We don't mutate headers in
|
// Use indices to avoid double borrow. We don't mutate headers in
|
||||||
// 'layout_row' so this is fine.
|
// 'layout_row' so this is fine.
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
@ -336,7 +349,11 @@ impl<'a> GridLayouter<'a> {
|
|||||||
{
|
{
|
||||||
// Advance regions without any output until we can place the
|
// Advance regions without any output until we can place the
|
||||||
// footer.
|
// footer.
|
||||||
self.finish_region_internal(Frame::soft(Axes::splat(Abs::zero())), vec![]);
|
self.finish_region_internal(
|
||||||
|
Frame::soft(Axes::splat(Abs::zero())),
|
||||||
|
vec![],
|
||||||
|
Abs::zero(),
|
||||||
|
);
|
||||||
skipped_region = true;
|
skipped_region = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use typst_library::layout::grid::resolve::Repeatable;
|
|||||||
use typst_library::layout::{Abs, Axes, Frame, Point, Region, Regions, Size, Sizing};
|
use typst_library::layout::{Abs, Axes, Frame, Point, Region, Regions, Size, Sizing};
|
||||||
use typst_utils::MaybeReverseIter;
|
use typst_utils::MaybeReverseIter;
|
||||||
|
|
||||||
use super::layouter::{in_last_with_offset, points, Row, RowPiece};
|
use super::layouter::{in_last_with_offset, points, Row};
|
||||||
use super::{layout_cell, Cell, GridLayouter};
|
use super::{layout_cell, Cell, GridLayouter};
|
||||||
|
|
||||||
/// All information needed to layout a single rowspan.
|
/// All information needed to layout a single rowspan.
|
||||||
@ -87,10 +87,10 @@ pub struct CellMeasurementData<'layouter> {
|
|||||||
|
|
||||||
impl GridLayouter<'_> {
|
impl GridLayouter<'_> {
|
||||||
/// Layout a rowspan over the already finished regions, plus the current
|
/// Layout a rowspan over the already finished regions, plus the current
|
||||||
/// region's frame and resolved rows, if it wasn't finished yet (because
|
/// region's frame and height of resolved header rows, if it wasn't
|
||||||
/// we're being called from `finish_region`, but note that this function is
|
/// finished yet (because we're being called from `finish_region`, but note
|
||||||
/// also called once after all regions are finished, in which case
|
/// that this function is also called once after all regions are finished,
|
||||||
/// `current_region_data` is `None`).
|
/// in which case `current_region_data` is `None`).
|
||||||
///
|
///
|
||||||
/// We need to do this only once we already know the heights of all
|
/// We need to do this only once we already know the heights of all
|
||||||
/// spanned rows, which is only possible after laying out the last row
|
/// spanned rows, which is only possible after laying out the last row
|
||||||
@ -98,7 +98,7 @@ impl GridLayouter<'_> {
|
|||||||
pub fn layout_rowspan(
|
pub fn layout_rowspan(
|
||||||
&mut self,
|
&mut self,
|
||||||
rowspan_data: Rowspan,
|
rowspan_data: Rowspan,
|
||||||
current_region_data: Option<(&mut Frame, &[RowPiece])>,
|
current_region_data: Option<(&mut Frame, Abs)>,
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let Rowspan {
|
let Rowspan {
|
||||||
@ -142,11 +142,31 @@ impl GridLayouter<'_> {
|
|||||||
|
|
||||||
// Push the layouted frames directly into the finished frames.
|
// Push the layouted frames directly into the finished frames.
|
||||||
let fragment = layout_cell(cell, engine, disambiguator, self.styles, pod)?;
|
let fragment = layout_cell(cell, engine, disambiguator, self.styles, pod)?;
|
||||||
let (current_region, current_rrows) = current_region_data.unzip();
|
let (current_region, current_header_row_height) = current_region_data.unzip();
|
||||||
for ((i, finished), frame) in self
|
|
||||||
|
// Clever trick to process finished header rows:
|
||||||
|
// - If there are grid headers, the vector will be filled with one
|
||||||
|
// finished header row height per region, so, chaining with the height
|
||||||
|
// for the current one, we get the header row height for each region.
|
||||||
|
//
|
||||||
|
// - But if there are no grid headers, the vector will be empty, so in
|
||||||
|
// theory the regions and resolved header row heights wouldn't match.
|
||||||
|
// But that's fine - 'current_header_row_height' can only be either
|
||||||
|
// 'Some(zero)' or 'None' in such a case, and for all other rows we
|
||||||
|
// append infinite zeros. That is, in such a case, the resolved header
|
||||||
|
// row height is always zero, so that's our fallback.
|
||||||
|
let finished_header_rows = self
|
||||||
|
.finished_header_rows
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.chain(current_header_row_height)
|
||||||
|
.chain(std::iter::repeat(Abs::zero()));
|
||||||
|
|
||||||
|
for ((i, (finished, header_dy)), frame) in self
|
||||||
.finished
|
.finished
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.chain(current_region.into_iter())
|
.chain(current_region.into_iter())
|
||||||
|
.zip(finished_header_rows)
|
||||||
.skip(first_region)
|
.skip(first_region)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.zip(fragment)
|
.zip(fragment)
|
||||||
@ -158,25 +178,9 @@ impl GridLayouter<'_> {
|
|||||||
} else {
|
} else {
|
||||||
// The rowspan continuation starts after the header (thus,
|
// The rowspan continuation starts after the header (thus,
|
||||||
// at a position after the sum of the laid out header
|
// at a position after the sum of the laid out header
|
||||||
// rows).
|
// rows). Without a header, this is zero, so the rowspan can
|
||||||
if let Some(Repeatable::Repeated(header)) = &self.grid.header {
|
// start at the very top of the region as usual.
|
||||||
// TODO: Need a way to distinguish header 'rrows' for each
|
header_dy
|
||||||
// region, as this calculation - i.e., header height at
|
|
||||||
// each region - will change depending on'i'.
|
|
||||||
let header_rows = self
|
|
||||||
.rrows
|
|
||||||
.get(i)
|
|
||||||
.map(Vec::as_slice)
|
|
||||||
.or(current_rrows)
|
|
||||||
.unwrap_or(&[])
|
|
||||||
.iter()
|
|
||||||
.take_while(|row| row.y < header.end);
|
|
||||||
|
|
||||||
header_rows.map(|row| row.height).sum()
|
|
||||||
} else {
|
|
||||||
// Without a header, start at the very top of the region.
|
|
||||||
Abs::zero()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
finished.push_frame(Point::new(dx, dy), frame);
|
finished.push_frame(Point::new(dx, dy), frame);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user