pub const MAX_COL_SPAN: u32 = 1000;
pub const MAX_ROW_SPAN: u32 = 65_534;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
pub rows: u32,
pub cols: u32,
}
impl Span {
pub fn new(rows: u32, cols: u32) -> Self {
Self { rows, cols }
}
pub fn single() -> Self {
Self::new(1, 1)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Placement {
pub columns: Vec<Vec<usize>>,
pub width: usize,
}
pub fn place<R, C>(rows: R) -> Placement
where
R: IntoIterator<Item = C>,
C: IntoIterator<Item = Span>,
{
let mut covered: Vec<usize> = Vec::new();
let mut placement = Placement::default();
for row in rows {
let mut columns = Vec::new();
let mut col = 0;
for span in row {
while covered.get(col).is_some_and(|&n| n > 0) {
covered[col] -= 1;
col += 1;
}
let (rows, cols) = (
span.rows.clamp(1, MAX_ROW_SPAN) as usize,
span.cols.clamp(1, MAX_COL_SPAN) as usize,
);
columns.push(col);
if covered.len() < col + cols {
covered.resize(col + cols, 0);
}
for c in &mut covered[col..col + cols] {
*c = rows - 1;
}
col += cols;
placement.width = placement.width.max(col);
}
for c in covered.iter_mut().skip(col) {
*c = c.saturating_sub(1);
}
placement.columns.push(columns);
}
placement
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_declared_span_past_the_bound_is_read_as_the_bound() {
let placement = place(spans(&[&[(u32::MAX, u32::MAX), (1, 1)], &[(1, 1)]]));
assert_eq!(
placement.columns,
vec![vec![0, MAX_COL_SPAN as usize], vec![MAX_COL_SPAN as usize]]
);
assert_eq!(placement.width, MAX_COL_SPAN as usize + 1);
}
fn spans(rows: &[&[(u32, u32)]]) -> Vec<Vec<Span>> {
rows.iter()
.map(|r| {
r.iter()
.map(|&(rows, cols)| Span::new(rows, cols))
.collect()
})
.collect()
}
#[test]
fn plain_cells_sit_at_their_index() {
let p = place(spans(&[&[(1, 1), (1, 1)], &[(1, 1), (1, 1)]]));
assert_eq!(p.columns, vec![vec![0, 1], vec![0, 1]]);
assert_eq!(p.width, 2);
}
#[test]
fn a_horizontal_span_in_the_first_row_widens_the_table() {
let p = place(spans(&[&[(2, 1), (1, 2)], &[(1, 1), (1, 1)]]));
assert_eq!(p.width, 3);
}
#[test]
fn a_vertical_span_pushes_the_rows_below_rightward() {
let p = place(spans(&[
&[(3, 1), (1, 1)],
&[(1, 1)],
&[(1, 1)],
&[(1, 1), (1, 1)],
]));
assert_eq!(p.columns, vec![vec![0, 1], vec![1], vec![1], vec![0, 1]]);
}
#[test]
fn a_cell_merged_down_at_the_row_end_is_used_up_by_the_rows_it_covers() {
let p = place(spans(&[&[(1, 1), (2, 1)], &[(1, 1)], &[(1, 1), (1, 1)]]));
assert_eq!(p.columns, vec![vec![0, 1], vec![0], vec![0, 1]]);
assert_eq!(p.width, 2);
}
#[test]
fn a_block_merge_covers_rows_and_columns() {
let p = place(spans(&[
&[(2, 2), (1, 1)],
&[(1, 1)],
&[(1, 1), (1, 1), (1, 1)],
]));
assert_eq!(p.columns, vec![vec![0, 2], vec![2], vec![0, 1, 2]]);
assert_eq!(p.width, 3);
}
#[test]
fn a_zero_span_is_read_as_one() {
let p = place(spans(&[&[(0, 0), (0, 0)]]));
assert_eq!(p.columns, vec![vec![0, 1]]);
assert_eq!(p.width, 2);
}
#[test]
fn an_empty_table_has_no_columns() {
assert_eq!(place(Vec::<Vec<Span>>::new()), Placement::default());
}
}