use super::{Cell, Column, Row, Table};
pub(super) struct PlacedCell<'a> {
pub(super) cell: Option<&'a Cell>,
pub(super) start: usize,
pub(super) colspan: usize,
}
pub(super) struct RowPlan<'a> {
pub(super) cells: Vec<PlacedCell<'a>>,
pub(super) footer: bool,
}
pub(super) fn build_plan(rows: &[Row], cols: usize) -> Vec<RowPlan<'_>> {
let mut occupied = vec![0usize; cols];
let mut plan = Vec::with_capacity(rows.len());
for row in rows {
let mut placed = Vec::new();
let mut cells = row.cells.iter();
let mut col = 0;
while col < cols {
if occupied[col] > 0 {
occupied[col] -= 1;
placed.push(PlacedCell {
cell: None,
start: col,
colspan: 1,
});
col += 1;
continue;
}
match cells.next() {
Some(cell) => {
let span = cell.colspan.min(cols - col).max(1);
if cell.rowspan > 1 {
for slot in occupied.iter_mut().skip(col).take(span) {
*slot = cell.rowspan - 1;
}
}
placed.push(PlacedCell {
cell: Some(cell),
start: col,
colspan: span,
});
col += span;
}
None => {
placed.push(PlacedCell {
cell: None,
start: col,
colspan: 1,
});
col += 1;
}
}
}
plan.push(RowPlan {
cells: placed,
footer: row.footer,
});
}
plan
}
pub(super) fn column_widths(
table: &Table,
plan: &[RowPlan],
max_width: usize,
) -> Vec<usize> {
let mut widths = vec![0usize; table.columns.len()];
for (index, column) in table.columns.iter().enumerate() {
if table.header {
widths[index] = column.header.width();
}
}
for row in plan {
for placed in &row.cells {
if let Some(cell) = placed.cell
&& placed.colspan == 1
{
widths[placed.start] =
widths[placed.start].max(cell.content.width());
}
}
}
for (index, column) in table.columns.iter().enumerate() {
widths[index] = clamp_width(widths[index], column);
}
fit_to_width(table, &mut widths, max_width);
widths
}
fn clamp_width(natural: usize, column: &Column) -> usize {
if let Some(fixed) = column.fixed_width {
return fixed;
}
let mut width = natural.max(column.min_width);
if let Some(max) = column.max_width {
width = width.min(max);
}
width.max(1)
}
#[derive(Clone, Copy)]
enum ShrinkPass {
Wrapping,
NonWrapping,
}
fn fit_to_width(table: &Table, widths: &mut [usize], max_width: usize) {
let pad = table.pad as usize;
let cells = widths.len();
let overhead = cells * (2 * pad + 1) + 1;
let budget = max_width.saturating_sub(overhead);
let content: usize = widths.iter().sum();
if content <= budget {
return;
}
let mut deficit = content - budget;
deficit = shrink(table, widths, deficit, ShrinkPass::Wrapping);
shrink(table, widths, deficit, ShrinkPass::NonWrapping);
}
fn shrink(
table: &Table,
widths: &mut [usize],
mut deficit: usize,
pass: ShrinkPass,
) -> usize {
while deficit > 0 {
let Some(index) = widest_shrinkable(table, widths, pass) else {
break;
};
widths[index] -= 1;
deficit -= 1;
}
deficit
}
fn widest_shrinkable(
table: &Table,
widths: &[usize],
pass: ShrinkPass,
) -> Option<usize> {
let mut best: Option<(usize, usize)> = None;
for (index, &width) in widths.iter().enumerate() {
let column = &table.columns[index];
if !eligible(column, pass) || width <= floor(column) {
continue;
}
if best.is_none_or(|(_, best_width)| width > best_width) {
best = Some((index, width));
}
}
best.map(|(index, _)| index)
}
fn eligible(column: &Column, pass: ShrinkPass) -> bool {
if column.fixed_width.is_some() {
return false;
}
match pass {
ShrinkPass::Wrapping => column.wrap,
ShrinkPass::NonWrapping => !column.wrap,
}
}
fn floor(column: &Column) -> usize {
column.min_width.max(1)
}