Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 52 additions & 12 deletions crates/layout/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,9 @@ fn finish_table_rows(ctx: &mut Ctx, input: TableRows<'_>) -> TableLayout {
VAlign::Center => ((span_h - align_h) / 2.0).max(0.0),
VAlign::Bottom => (span_h - align_h).max(0.0),
};
// A middle or bottom cell as tall as its row moves nowhere, and still is lined up in
// the part of a split row left on a page (see `align_in_part`).
let aligned = c.valign != VAlign::Top && c.turned.is_none();
let end = (c.vmerge != VMerge::Continue && c.turned.is_none()).then_some(c.end + dy);
let mut cell_items = c.items;
if let Some(tn) = c.turned {
Expand Down Expand Up @@ -830,7 +833,7 @@ fn finish_table_rows(ctx: &mut Ctx, input: TableRows<'_>) -> TableLayout {
it.translate(0.0, dy);
items.push(it);
}
let align = (dy > 0.0).then_some(CellAlign { content: content..items.len(), valign: c.valign, dy, height: align_h });
let align = (dy > 0.0 || aligned).then_some(CellAlign { content: content..items.len(), valign: c.valign, dy, height: align_h });
let b = c.borders;
let (x0, x1, y0, y1) = (c.x, c.x + c.w, 0.0, rh);
// Horizontal edges run on over half of each visible vertical edge, so the corners
Expand Down Expand Up @@ -952,7 +955,7 @@ fn split_row_with(row: &RowLayout, cut: f32, whole_cells: bool) -> Option<(RowLa
return None;
}
}
align_in_part(row, &mut cells, &splits, &realign, &mut ends, cut);
let lowered = align_in_part(row, &mut cells, &splits, &realign, &mut ends, cut);
// The top of the first thing that moves on in each cell.
let mut moved = vec![f32::MAX; cells.len()];
// Whether each cell keeps anything on this page, and whether a line or picture of it that
Expand Down Expand Up @@ -1021,7 +1024,8 @@ fn split_row_with(row: &RowLayout, cut: f32, whole_cells: bool) -> Option<(RowLa
let shifts: Vec<f32> = moved.iter().zip(&row.cells).map(|(&m, c)| ((if m < f32::MAX { m } else { first_moved }) - c.top).max(0.0)).collect();
// The continuation is as tall as the cell needing the most room (what it moves on and the
// bottom margin, the rest of the row for turned text), and at least the rest of the row.
let full = (row.height - shifts.iter().copied().fold(f32::MAX, f32::min)).max(4.0);
// A cell lined up lower in the part left here goes on with as much as if it had not moved.
let full = (row.height - shifts.iter().zip(&lowered).map(|(s, d)| s - d).fold(f32::MAX, f32::min)).max(4.0);
let mut need = row.height - cut;
for ((end, &m), &s) in ends.iter().zip(&moved).zip(&shifts) {
if m < f32::MAX {
Expand Down Expand Up @@ -1188,20 +1192,32 @@ fn cells_from_top(row: &RowLayout, limit: f32) -> (CellItems<'_>, Vec<Option<f32

/// Line up the middle and bottom cells marked in `realign` within the part of the row left on the
/// page: as tall as the other cells' text kept there (by `splits`) and the bottom margin.
///
/// A middle or bottom cell whose text goes on to the next page is lined up in that part too, by
/// the text it keeps here. Measured in Word for Windows with a row cut after 17 lines: a cell
/// that keeps 15 of them (its paragraphs of three lines move on whole) starts one line down when
/// it is centred and two lines down when it is bottom-aligned, beside cells that keep all 17;
/// on the following pages every cell starts at the top. Returns how far each cell that goes on
/// was moved down here.
fn align_in_part(
row: &RowLayout,
cells: &mut [std::borrow::Cow<[Placed]>],
splits: &[Vec<usize>],
realign: &[Option<&CellAlign>],
ends: &mut [Option<f32>],
cut: f32,
) {
if realign.iter().all(Option::is_none) {
return;
) -> Vec<f32> {
let mut lowered = vec![0.0; cells.len()];
if row.cells.iter().all(|c| c.align.is_none()) {
return lowered;
}
let limit = cut - row.bottom.clamp(0.0, cut);
let mut kept = f32::MIN;
for (((items, splits), _), end) in cells.iter().zip(splits).zip(realign).zip(ends.iter()).filter(|((_, al), _)| al.is_none()) {
// Where what each of those cells keeps on the page ends.
let mut own = vec![f32::MIN; cells.len()];
for ((((items, splits), _), end), own) in
cells.iter().zip(splits).zip(realign).zip(ends.iter()).zip(&mut own).filter(|(((_, al), _), _)| al.is_none())
{
// No further than the cell's content: its last paragraph may have dropped its space after.
let end = end.unwrap_or(f32::MAX);
for (it, &split) in items.iter().zip(splits) {
Expand All @@ -1214,22 +1230,46 @@ fn align_in_part(
(None, Placed::Image { rect, .. } | Placed::Shape { rect, .. } | Placed::Graphic { rect, .. } | Placed::Clip { rect, .. }) => {
Some(rect.bottom())
}
// A row of a table in the cell that stays: down to its bottom border, which is
// below its text. The cell's own area runs to the row's bottom, past the limit.
(None, Placed::Cell { rect, .. }) => Some(rect.bottom()),
_ => None,
};
if let Some(b) = bottom.filter(|&b| b <= limit + 0.01) {
kept = kept.max(b);
*own = own.max(b);
}
}
}
// A cell aligned to the middle or bottom whose text all stays is text kept as well, and the
// others line up against it too. Measured in Word for Windows: beside a centred cell of four
// lines that stays whole and a top cell that keeps three, a centred cell of two lines starts
// one line down, one of three half a line, and a bottom cell of one line three lines down.
if kept > f32::MIN {
kept = ends.iter().zip(realign).filter(|(_, al)| al.is_some()).filter_map(|(end, _)| *end).fold(kept, f32::max);
}
let part = if kept > f32::MIN { kept + row.bottom } else { cut };
for (((items, al), c), end) in cells.iter_mut().zip(realign).zip(&row.cells).zip(ends) {
let (Some(al), Some(e)) = (al, *end) else { continue };
let room = (part - al.height).max(0.0);
let dy = if al.valign == VAlign::Center { room / 2.0 } else { room };
let dy = dy.min((limit - e).max(0.0));
for (((((items, al), c), end), own), lowered) in cells.iter_mut().zip(realign).zip(&row.cells).zip(ends).zip(own).zip(&mut lowered) {
let Some(e) = *end else { continue };
let dy = match (al, c.align.as_ref()) {
(Some(al), _) => {
let room = (part - al.height).max(0.0);
let dy = if al.valign == VAlign::Center { room / 2.0 } else { room };
dy.min((limit - e).max(0.0))
}
// Its text goes on: what it keeps here, against the tallest text kept.
(None, Some(al)) if own > f32::MIN => {
let room = (kept - own).max(0.0);
*lowered = if al.valign == VAlign::Center { room / 2.0 } else { room };
*lowered
}
_ => continue,
};
let Some(al) = al.or(c.align.as_ref()) else { continue };
shift_content(items, c, al, dy);
*end = Some(e + dy);
}
lowered
}

/// Move the content of cell `c` (its items `items`) down by `dy`.
Expand Down
Loading
Loading