mirror of
https://github.com/typst/typst
synced 2025-05-22 04:55:29 +08:00
fix line layout with missing header bottoms
This commit is contained in:
parent
c04dedf470
commit
59dc458188
@ -57,10 +57,20 @@ pub struct GridLayouter<'a> {
|
|||||||
/// rowspans placed later can know which rows to skip at the top of the
|
/// rowspans placed later can know which rows to skip at the top of the
|
||||||
/// region when spanning more than one page.
|
/// region when spanning more than one page.
|
||||||
pub(super) current_header_rows: usize,
|
pub(super) current_header_rows: usize,
|
||||||
|
/// Similar to the above, but stopping after the last repeated row at the
|
||||||
|
/// top.
|
||||||
|
pub(super) current_repeating_header_rows: usize,
|
||||||
|
/// The end bound of the last repeating header at the start of the region.
|
||||||
|
/// The last row might have disappeared due to being empty, so this is how
|
||||||
|
/// we can become aware of that. Line layout uses this to determine when to
|
||||||
|
/// prioritize the last lines under a header.
|
||||||
|
///
|
||||||
|
/// A value of zero indicates no headers were placed.
|
||||||
|
pub(super) current_last_repeated_header_end: 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.
|
/// The amount and height of header rows on each finished region.
|
||||||
pub(super) finished_header_rows: Vec<Abs>,
|
pub(super) finished_header_rows: Vec<FinishedHeaderRowInfo>,
|
||||||
/// 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.
|
||||||
@ -105,6 +115,15 @@ pub struct GridLayouter<'a> {
|
|||||||
pub(super) span: Span,
|
pub(super) span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(super) struct FinishedHeaderRowInfo {
|
||||||
|
/// The amount of repeated headers at the top of the region.
|
||||||
|
pub(super) repeated: usize,
|
||||||
|
/// The end bound of the last repeated header at the top of the region.
|
||||||
|
pub(super) last_repeated_header_end: usize,
|
||||||
|
pub(super) height: Abs,
|
||||||
|
}
|
||||||
|
|
||||||
/// Details about a resulting row piece.
|
/// Details about a resulting row piece.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RowPiece {
|
pub struct RowPiece {
|
||||||
@ -163,6 +182,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
rowspans: vec![],
|
rowspans: vec![],
|
||||||
initial: regions.size,
|
initial: regions.size,
|
||||||
current_header_rows: 0,
|
current_header_rows: 0,
|
||||||
|
current_repeating_header_rows: 0,
|
||||||
|
current_last_repeated_header_end: 0,
|
||||||
finished: vec![],
|
finished: vec![],
|
||||||
finished_header_rows: vec![],
|
finished_header_rows: vec![],
|
||||||
is_rtl: TextElem::dir_in(styles) == Dir::RTL,
|
is_rtl: TextElem::dir_in(styles) == Dir::RTL,
|
||||||
@ -294,8 +315,13 @@ impl<'a> GridLayouter<'a> {
|
|||||||
fn render_fills_strokes(mut self) -> SourceResult<Fragment> {
|
fn render_fills_strokes(mut self) -> SourceResult<Fragment> {
|
||||||
let mut finished = std::mem::take(&mut self.finished);
|
let mut finished = std::mem::take(&mut self.finished);
|
||||||
let frame_amount = finished.len();
|
let frame_amount = finished.len();
|
||||||
for ((frame_index, frame), rows) in
|
for (((frame_index, frame), rows), finished_header_rows) in
|
||||||
finished.iter_mut().enumerate().zip(&self.rrows)
|
finished.iter_mut().enumerate().zip(&self.rrows).zip(
|
||||||
|
self.finished_header_rows
|
||||||
|
.iter()
|
||||||
|
.map(Some)
|
||||||
|
.chain(std::iter::repeat(None)),
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if self.rcols.is_empty() || rows.is_empty() {
|
if self.rcols.is_empty() || rows.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
@ -416,7 +442,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let hline_indices = rows
|
let hline_indices = rows
|
||||||
.iter()
|
.iter()
|
||||||
.map(|piece| piece.y)
|
.map(|piece| piece.y)
|
||||||
.chain(std::iter::once(self.grid.rows.len()));
|
.chain(std::iter::once(self.grid.rows.len()))
|
||||||
|
.enumerate();
|
||||||
|
|
||||||
// Converts a row to the corresponding index in the vector of
|
// Converts a row to the corresponding index in the vector of
|
||||||
// hlines.
|
// hlines.
|
||||||
@ -441,7 +468,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut prev_y = None;
|
let mut prev_y = None;
|
||||||
for (y, dy) in hline_indices.zip(hline_offsets) {
|
for ((i, y), dy) in hline_indices.zip(hline_offsets) {
|
||||||
// Position of lines below the row index in the previous iteration.
|
// Position of lines below the row index in the previous iteration.
|
||||||
let expected_prev_line_position = prev_y
|
let expected_prev_line_position = prev_y
|
||||||
.map(|prev_y| {
|
.map(|prev_y| {
|
||||||
@ -452,24 +479,10 @@ impl<'a> GridLayouter<'a> {
|
|||||||
})
|
})
|
||||||
.unwrap_or(LinePosition::Before);
|
.unwrap_or(LinePosition::Before);
|
||||||
|
|
||||||
// FIXME: In the future, directly specify in 'self.rrows' when
|
// Header's lines have priority when repeated.
|
||||||
// we place a repeated header rather than its original rows.
|
let end_under_repeated_header = finished_header_rows
|
||||||
// That would let us remove most of those verbose checks, both
|
.filter(|info| prev_y.is_some() && i == info.repeated)
|
||||||
// in 'lines.rs' and here. Those checks also aren't fully
|
.map(|info| info.last_repeated_header_end);
|
||||||
// accurate either, since they will also trigger when some rows
|
|
||||||
// have been removed between the header and what's below it.
|
|
||||||
let is_under_repeated_header = self
|
|
||||||
.grid
|
|
||||||
.header
|
|
||||||
.as_ref()
|
|
||||||
.and_then(Repeatable::as_repeated)
|
|
||||||
.zip(prev_y)
|
|
||||||
.is_some_and(|(header, prev_y)| {
|
|
||||||
// Note: 'y == header.end' would mean we're right below
|
|
||||||
// the NON-REPEATED header, so that case should return
|
|
||||||
// false.
|
|
||||||
prev_y < header.end && y > header.end
|
|
||||||
});
|
|
||||||
|
|
||||||
// If some grid rows were omitted between the previous resolved
|
// If some grid rows were omitted between the previous resolved
|
||||||
// row and the current one, we ensure lines below the previous
|
// row and the current one, we ensure lines below the previous
|
||||||
@ -483,13 +496,11 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let prev_lines = prev_y
|
let prev_lines = prev_y
|
||||||
.filter(|prev_y| {
|
.filter(|prev_y| {
|
||||||
prev_y + 1 != y
|
prev_y + 1 != y
|
||||||
&& (!is_under_repeated_header
|
&& end_under_repeated_header.is_none_or(
|
||||||
|| self
|
|last_repeated_header_end| {
|
||||||
.grid
|
prev_y + 1 != last_repeated_header_end
|
||||||
.header
|
},
|
||||||
.as_ref()
|
)
|
||||||
.and_then(Repeatable::as_repeated)
|
|
||||||
.is_some_and(|header| prev_y + 1 != header.end))
|
|
||||||
})
|
})
|
||||||
.map(|prev_y| get_hlines_at(prev_y + 1))
|
.map(|prev_y| get_hlines_at(prev_y + 1))
|
||||||
.unwrap_or(&[]);
|
.unwrap_or(&[]);
|
||||||
@ -510,15 +521,14 @@ impl<'a> GridLayouter<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut expected_header_line_position = LinePosition::Before;
|
let mut expected_header_line_position = LinePosition::Before;
|
||||||
let header_hlines = if let Some((Repeatable::Repeated(header), prev_y)) =
|
let header_hlines = if let Some((under_header_end, prev_y)) =
|
||||||
self.grid.header.as_ref().zip(prev_y)
|
end_under_repeated_header.zip(prev_y)
|
||||||
{
|
{
|
||||||
if is_under_repeated_header
|
if !self.grid.has_gutter
|
||||||
&& (!self.grid.has_gutter
|
|| matches!(
|
||||||
|| matches!(
|
self.grid.rows[prev_y],
|
||||||
self.grid.rows[prev_y],
|
Sizing::Rel(length) if length.is_zero()
|
||||||
Sizing::Rel(length) if length.is_zero()
|
)
|
||||||
))
|
|
||||||
{
|
{
|
||||||
// For lines below a header, give priority to the
|
// For lines below a header, give priority to the
|
||||||
// lines originally below the header rather than
|
// lines originally below the header rather than
|
||||||
@ -537,10 +547,10 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// column-gutter is specified, for example. In that
|
// column-gutter is specified, for example. In that
|
||||||
// case, we still repeat the line under the gutter.
|
// case, we still repeat the line under the gutter.
|
||||||
expected_header_line_position = expected_line_position(
|
expected_header_line_position = expected_line_position(
|
||||||
header.end,
|
under_header_end,
|
||||||
header.end == self.grid.rows.len(),
|
under_header_end == self.grid.rows.len(),
|
||||||
);
|
);
|
||||||
get_hlines_at(header.end)
|
get_hlines_at(under_header_end)
|
||||||
} else {
|
} else {
|
||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
@ -598,6 +608,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
grid,
|
grid,
|
||||||
rows,
|
rows,
|
||||||
local_top_y,
|
local_top_y,
|
||||||
|
end_under_repeated_header,
|
||||||
in_last_region,
|
in_last_region,
|
||||||
y,
|
y,
|
||||||
x,
|
x,
|
||||||
@ -1615,10 +1626,20 @@ impl<'a> GridLayouter<'a> {
|
|||||||
pos.y += height;
|
pos.y += height;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.finish_region_internal(output, rrows, header_row_height);
|
self.finish_region_internal(
|
||||||
|
output,
|
||||||
|
rrows,
|
||||||
|
FinishedHeaderRowInfo {
|
||||||
|
repeated: self.current_repeating_header_rows,
|
||||||
|
last_repeated_header_end: self.current_last_repeated_header_end,
|
||||||
|
height: header_row_height,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if !last {
|
if !last {
|
||||||
self.current_header_rows = 0;
|
self.current_header_rows = 0;
|
||||||
|
self.current_repeating_header_rows = 0;
|
||||||
|
self.current_last_repeated_header_end = 0;
|
||||||
self.header_height = Abs::zero();
|
self.header_height = Abs::zero();
|
||||||
self.repeating_header_height = Abs::zero();
|
self.repeating_header_height = Abs::zero();
|
||||||
self.repeating_header_heights.clear();
|
self.repeating_header_heights.clear();
|
||||||
@ -1649,7 +1670,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
output: Frame,
|
output: Frame,
|
||||||
resolved_rows: Vec<RowPiece>,
|
resolved_rows: Vec<RowPiece>,
|
||||||
header_row_height: Abs,
|
header_row_info: FinishedHeaderRowInfo,
|
||||||
) {
|
) {
|
||||||
self.finished.push(output);
|
self.finished.push(output);
|
||||||
self.rrows.push(resolved_rows);
|
self.rrows.push(resolved_rows);
|
||||||
@ -1657,7 +1678,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.initial = self.regions.size;
|
self.initial = self.regions.size;
|
||||||
|
|
||||||
if !self.grid.headers.is_empty() {
|
if !self.grid.headers.is_empty() {
|
||||||
self.finished_header_rows.push(header_row_height);
|
self.finished_header_rows.push(header_row_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -395,6 +395,7 @@ pub fn hline_stroke_at_column(
|
|||||||
grid: &CellGrid,
|
grid: &CellGrid,
|
||||||
rows: &[RowPiece],
|
rows: &[RowPiece],
|
||||||
local_top_y: Option<usize>,
|
local_top_y: Option<usize>,
|
||||||
|
end_under_repeated_header: Option<usize>,
|
||||||
in_last_region: bool,
|
in_last_region: bool,
|
||||||
y: usize,
|
y: usize,
|
||||||
x: usize,
|
x: usize,
|
||||||
@ -499,16 +500,11 @@ pub fn hline_stroke_at_column(
|
|||||||
// Top border stroke and header stroke are generally prioritized, unless
|
// Top border stroke and header stroke are generally prioritized, unless
|
||||||
// they don't have explicit hline overrides and one or more user-provided
|
// they don't have explicit hline overrides and one or more user-provided
|
||||||
// hlines would appear at the same position, which then are prioritized.
|
// hlines would appear at the same position, which then are prioritized.
|
||||||
let top_stroke_comes_from_header = grid
|
let top_stroke_comes_from_header = end_under_repeated_header
|
||||||
.header
|
|
||||||
.as_ref()
|
|
||||||
.and_then(Repeatable::as_repeated)
|
|
||||||
.zip(local_top_y)
|
.zip(local_top_y)
|
||||||
.is_some_and(|(header, local_top_y)| {
|
.is_some_and(|(last_repeated_header_end, local_top_y)| {
|
||||||
// Ensure the row above us is a repeated header.
|
// Ensure the row above us is a repeated header.
|
||||||
// FIXME: Make this check more robust when headers at arbitrary
|
local_top_y < last_repeated_header_end && y > last_repeated_header_end
|
||||||
// positions are added.
|
|
||||||
local_top_y < header.end && y > header.end
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prioritize the footer's top stroke as well where applicable.
|
// Prioritize the footer's top stroke as well where applicable.
|
||||||
@ -1268,6 +1264,7 @@ mod test {
|
|||||||
grid,
|
grid,
|
||||||
&rows,
|
&rows,
|
||||||
y.checked_sub(1),
|
y.checked_sub(1),
|
||||||
|
None,
|
||||||
true,
|
true,
|
||||||
y,
|
y,
|
||||||
x,
|
x,
|
||||||
@ -1461,6 +1458,7 @@ mod test {
|
|||||||
grid,
|
grid,
|
||||||
&rows,
|
&rows,
|
||||||
y.checked_sub(1),
|
y.checked_sub(1),
|
||||||
|
None,
|
||||||
true,
|
true,
|
||||||
y,
|
y,
|
||||||
x,
|
x,
|
||||||
@ -1506,6 +1504,7 @@ mod test {
|
|||||||
grid,
|
grid,
|
||||||
&rows,
|
&rows,
|
||||||
if y == 4 { Some(2) } else { y.checked_sub(1) },
|
if y == 4 { Some(2) } else { y.checked_sub(1) },
|
||||||
|
None,
|
||||||
true,
|
true,
|
||||||
y,
|
y,
|
||||||
x,
|
x,
|
||||||
|
@ -253,7 +253,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.finish_region_internal(
|
self.finish_region_internal(
|
||||||
Frame::soft(Axes::splat(Abs::zero())),
|
Frame::soft(Axes::splat(Abs::zero())),
|
||||||
vec![],
|
vec![],
|
||||||
Abs::zero(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: re-calculate heights of headers and footers on each region
|
// TODO: re-calculate heights of headers and footers on each region
|
||||||
@ -321,8 +321,12 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Include both repeating and pending header rows as this number is
|
// Include both repeating and pending header rows as this number is
|
||||||
// used for orphan prevention.
|
// used for orphan prevention.
|
||||||
self.current_header_rows = repeating_header_rows + pending_header_rows;
|
self.current_header_rows = repeating_header_rows + pending_header_rows;
|
||||||
|
self.current_repeating_header_rows = repeating_header_rows;
|
||||||
self.unbreakable_rows_left += repeating_header_rows + pending_header_rows;
|
self.unbreakable_rows_left += repeating_header_rows + pending_header_rows;
|
||||||
|
|
||||||
|
self.current_last_repeated_header_end =
|
||||||
|
self.repeating_headers.last().map(|h| h.end).unwrap_or_default();
|
||||||
|
|
||||||
// Reset the header height for this region.
|
// Reset the header height for this region.
|
||||||
// It will be re-calculated when laying out each header row.
|
// It will be re-calculated when laying out each header row.
|
||||||
self.header_height = Abs::zero();
|
self.header_height = Abs::zero();
|
||||||
@ -454,7 +458,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
self.finish_region_internal(
|
self.finish_region_internal(
|
||||||
Frame::soft(Axes::splat(Abs::zero())),
|
Frame::soft(Axes::splat(Abs::zero())),
|
||||||
vec![],
|
vec![],
|
||||||
Abs::zero(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
skipped_region = true;
|
skipped_region = true;
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ impl GridLayouter<'_> {
|
|||||||
let finished_header_rows = self
|
let finished_header_rows = self
|
||||||
.finished_header_rows
|
.finished_header_rows
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.map(|info| info.height)
|
||||||
.chain(current_header_row_height)
|
.chain(current_header_row_height)
|
||||||
.chain(std::iter::repeat(Abs::zero()));
|
.chain(std::iter::repeat(Abs::zero()));
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user