use std::collections::BTreeMap;
use super::borders::{
should_draw_left_border, should_draw_right_border, should_draw_vertical_lines,
};
use super::column_display_info::ColumnDisplayInfo;
use crate::style::ColumnConstraint::*;
use crate::style::{ColumnConstraint, ContentArrangement};
use crate::table::Table;
pub(crate) fn arrange_content(table: &Table) -> Vec<ColumnDisplayInfo> {
let table_width = table.get_table_width();
let mut display_infos = Vec::new();
for column in table.columns.iter() {
let mut info = ColumnDisplayInfo::new(column);
if let Some(constraint) = column.constraint {
evaluate_constraint(&mut info, constraint, table_width);
}
display_infos.push(info);
}
if table_width.is_none() {
disabled_arrangement(&mut display_infos);
return display_infos;
}
match &table.arrangement {
ContentArrangement::Disabled => disabled_arrangement(&mut display_infos),
ContentArrangement::Dynamic | ContentArrangement::DynamicFullWidth => {
dynamic_arrangement(table, &mut display_infos, table_width.unwrap());
}
}
display_infos
}
fn evaluate_constraint(
info: &mut ColumnDisplayInfo,
constraint: ColumnConstraint,
table_width: Option<u16>,
) {
match constraint {
ContentWidth => {
info.set_content_width(info.max_content_width);
info.fixed = true;
}
Width(width) => {
let width = info.without_padding(width);
info.set_content_width(width);
info.fixed = true;
}
MinWidth(min_width) => {
if info.max_width() <= min_width {
let width = info.without_padding(min_width);
info.set_content_width(width);
info.fixed = true;
}
}
MaxWidth(max_width) => info.constraint = Some(MaxWidth(max_width)),
Percentage(percent) => {
if let Some(table_width) = table_width {
let mut width = (table_width as i32 * percent as i32 / 100) as u16;
width = info.without_padding(width as u16);
info.set_content_width(width);
info.fixed = true;
}
}
MinPercentage(percent) => {
if let Some(table_width) = table_width {
let min_width = (table_width as i32 * percent as i32 / 100) as u16;
if info.max_width() <= min_width {
let width = info.without_padding(min_width);
info.set_content_width(width);
info.fixed = true;
}
}
}
MaxPercentage(percent) => {
if let Some(table_width) = table_width {
let max_width = (table_width as i32 * percent as i32 / 100) as u16;
info.constraint = Some(MaxWidth(max_width));
}
}
Hidden => {
info.constraint = Some(ColumnConstraint::Hidden);
}
}
}
fn disabled_arrangement(infos: &mut Vec<ColumnDisplayInfo>) {
for info in infos.iter_mut() {
if info.fixed {
continue;
}
if let Some(ColumnConstraint::MaxWidth(max_width)) = info.constraint {
if max_width < info.max_width() {
let width = info.without_padding(max_width);
info.set_content_width(width);
info.fixed = true;
continue;
}
}
info.set_content_width(info.max_content_width);
info.fixed = true;
}
}
fn dynamic_arrangement(table: &Table, infos: &mut Vec<ColumnDisplayInfo>, table_width: u16) {
let mut remaining_width = table_width as i32;
let column_count = count_visible_columns(infos);
if should_draw_left_border(table) {
remaining_width -= 1;
}
if should_draw_right_border(table) {
remaining_width -= 1;
}
if should_draw_vertical_lines(table) {
remaining_width -= column_count as i32 - 1;
}
let mut checked = Vec::new();
for (id, info) in infos.iter().enumerate() {
if info.is_hidden() {
continue;
}
remaining_width -= info.padding_width() as i32;
if info.fixed {
remaining_width -= info.content_width() as i32;
checked.push(id);
}
}
remaining_width =
find_columns_less_than_average(remaining_width, column_count, infos, &mut checked);
let mut remaining_columns = column_count - checked.len();
if remaining_columns != 0 && remaining_width > (2 * remaining_columns as i32) {
remaining_width = optimize_space_after_split(
remaining_width,
remaining_columns,
infos,
&mut checked,
table,
);
}
remaining_columns = column_count - checked.len();
if remaining_columns == 0 {
if remaining_width > 0 && matches!(table.arrangement, ContentArrangement::DynamicFullWidth)
{
distribute_remaining_space(
infos,
&mut checked,
column_count,
remaining_width as usize,
false,
);
}
return;
}
if remaining_width < remaining_columns as i32 {
remaining_width = remaining_columns as i32;
}
distribute_remaining_space(
infos,
&mut checked,
column_count,
remaining_width as usize,
true,
);
}
fn find_columns_less_than_average(
mut remaining_width: i32,
column_count: usize,
infos: &mut [ColumnDisplayInfo],
checked: &mut Vec<usize>,
) -> i32 {
let mut found_smaller = true;
while found_smaller {
found_smaller = false;
let remaining_columns = column_count - checked.len();
if remaining_columns == 0 {
break;
}
let average_space = remaining_width / remaining_columns as i32;
if average_space <= 0 {
break;
}
for (id, info) in infos.iter_mut().enumerate() {
if info.is_hidden() {
continue;
}
if checked.contains(&id) {
continue;
}
if let Some(ColumnConstraint::MaxWidth(max_width)) = info.constraint {
let space_after_padding = average_space + info.padding_width() as i32;
if max_width as i32 <= space_after_padding && info.max_width() >= max_width {
let width = info.without_padding(max_width);
info.set_content_width(width);
info.fixed = true;
checked.push(id);
remaining_width -= info.width() as i32;
found_smaller = true;
continue;
}
}
if (info.max_content_width as i32) < average_space {
info.set_content_width(info.max_content_width);
info.fixed = true;
checked.push(id);
remaining_width -= info.max_content_width as i32;
found_smaller = true;
}
}
}
remaining_width
}
fn optimize_space_after_split(
mut remaining_width: i32,
mut remaining_columns: usize,
infos: &mut [ColumnDisplayInfo],
checked: &mut Vec<usize>,
table: &Table,
) -> i32 {
let mut distribution = BTreeMap::new();
for (id, info) in infos.iter_mut().enumerate() {
if info.is_hidden() {
continue;
}
if checked.contains(&id) {
continue;
}
let average_space = remaining_width / remaining_columns as i32;
let longest_line = get_longest_line_after_split(average_space, id, info, table);
let remaining_space = average_space - longest_line as i32;
if remaining_space >= 3 {
distribution.insert(id, longest_line);
checked.push(id);
info.set_content_width(longest_line as u16);
remaining_width -= longest_line as i32;
remaining_columns -= 1;
}
}
remaining_width
}
fn get_longest_line_after_split(
average_space: i32,
id: usize,
info: &mut ColumnDisplayInfo,
table: &Table,
) -> usize {
let mut column_lines = Vec::new();
for cell in table.column_cells_iter(id) {
let cell = if let Some(cell) = cell {
cell
} else {
continue;
};
let delimiter = if let Some(delimiter) = cell.delimiter {
delimiter
} else if let Some(delimiter) = info.delimiter {
delimiter
} else if let Some(delimiter) = table.delimiter {
delimiter
} else {
' '
};
info.set_content_width(average_space as u16);
for line in cell.content.iter() {
if (line.chars().count() as u16) > info.content_width() {
let mut splitted = super::split::split_line(&line, &info, delimiter);
column_lines.append(&mut splitted);
} else {
column_lines.push(line.into());
}
}
}
let mut longest_line = 0;
for line in column_lines {
if line.len() > longest_line {
longest_line = line.len();
}
}
longest_line
}
fn distribute_remaining_space(
infos: &mut [ColumnDisplayInfo],
checked: &mut Vec<usize>,
column_count: usize,
remaining_width: usize,
unchecked_only: bool,
) {
let remaining_columns = if unchecked_only {
column_count - checked.len()
} else {
count_visible_columns(infos)
};
if remaining_columns == 0 {
return;
}
let average_space = remaining_width / remaining_columns;
let mut excess = remaining_width - (average_space * remaining_columns);
for (id, info) in infos.iter_mut().enumerate() {
if info.is_hidden() {
continue;
}
if unchecked_only && checked.contains(&id) {
continue;
}
let mut width = if excess > 0 {
excess -= 1;
(average_space + 1) as u16
} else {
average_space as u16
};
if !unchecked_only {
width += info.content_width();
}
info.set_content_width(width);
info.fixed = true;
}
}
fn count_visible_columns(infos: &[ColumnDisplayInfo]) -> usize {
let mut count = 0;
for info in infos {
if !info.is_hidden() {
count += 1;
}
}
count
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_disabled_arrangement() {
let mut table = Table::new();
table.set_header(&vec!["head", "head", "head"]);
table.add_row(&vec!["four", "fivef", "sixsix"]);
let display_infos = arrange_content(&table);
let max_widths: Vec<u16> = display_infos
.iter()
.map(|info| info.max_content_width)
.collect();
assert_eq!(max_widths, vec![4, 5, 6]);
let widths: Vec<u16> = display_infos.iter().map(|info| info.width()).collect();
assert_eq!(widths, vec![6, 7, 8]);
}
}