use unicode_width::UnicodeWidthStr;
use super::{ColumnDisplayInfo, DisplayInfos, constraint, helper::*};
use crate::{Column, Table, style::*, utils::formatting::content_split::split_line};
pub fn arrange(
table: &Table,
infos: &mut DisplayInfos,
table_width: usize,
max_content_widths: &[u16],
) {
let visible_columns = count_visible_columns(&table.columns);
let mut remaining_width: usize =
available_content_width(table, infos, visible_columns, table_width);
let mut remaining_columns = count_remaining_columns(visible_columns, infos);
#[cfg(feature = "_debug")]
println!(
"dynamic::arrange: Table width: {table_width}, Start remaining width {remaining_width}"
);
#[cfg(feature = "_debug")]
println!("dynamic::arrange: Max content widths: {max_content_widths:#?}");
if remaining_columns > 0 {
(remaining_width, remaining_columns) = enforce_lower_boundary_constraints(
table,
infos,
remaining_width,
remaining_columns,
visible_columns,
);
}
let (mut remaining_width, mut remaining_columns) = find_columns_that_fit_into_average(
table,
infos,
remaining_width,
remaining_columns,
visible_columns,
max_content_widths,
);
#[cfg(feature = "_debug")]
{
println!("After less than average: {infos:#?}");
println!("Remaining width {remaining_width}, column {remaining_columns}");
}
if remaining_columns > 0 {
(remaining_width, remaining_columns) =
optimize_space_after_split(table, infos, remaining_width, remaining_columns);
}
#[cfg(feature = "_debug")]
{
println!("dynamic::arrange: After optimize: {infos:#?}",);
println!("dynamic::arrange: Remaining width {remaining_width}, column {remaining_columns}",);
}
if remaining_columns == 0 {
if remaining_width > 0 && matches!(table.arrangement, ContentArrangement::DynamicFullWidth)
{
use_full_width(infos, remaining_width);
#[cfg(feature = "_debug")]
println!("dynamic::arrange: After full width: {infos:#?}");
}
return;
}
if remaining_width < remaining_columns {
remaining_width = remaining_columns;
}
distribute_remaining_space(&table.columns, infos, remaining_width, remaining_columns);
#[cfg(feature = "_debug")]
println!("dynamic::arrange: After distribute: {infos:#?}");
}
fn available_content_width(
table: &Table,
infos: &DisplayInfos,
visible_columns: usize,
mut width: usize,
) -> usize {
let border_count = count_border_columns(table, visible_columns);
width = width.saturating_sub(border_count);
for column in table.columns.iter() {
if infos.contains_key(&column.index) {
continue;
}
let (left, right) = column.padding;
width = width.saturating_sub((left + right).into());
}
for info in infos.values() {
if info.is_hidden {
continue;
}
width = width.saturating_sub(info.width().into());
}
width
}
fn find_columns_that_fit_into_average(
table: &Table,
infos: &mut DisplayInfos,
mut remaining_width: usize,
mut remaining_columns: usize,
visible_columns: usize,
max_content_widths: &[u16],
) -> (usize, usize) {
let mut found_smaller = true;
while found_smaller {
found_smaller = false;
if remaining_columns == 0 {
break;
}
let mut average_space = remaining_width / remaining_columns;
if average_space == 0 {
break;
}
for column in table.columns.iter() {
if infos.contains_key(&column.index) {
continue;
}
let max_column_width = max_content_widths[column.index];
if let Some(max_width) = constraint::max(table, &column.constraint, visible_columns) {
let average_space_with_padding =
average_space + usize::from(column.padding_width());
let width_with_padding = max_column_width + column.padding_width();
if usize::from(max_width) <= average_space_with_padding
&& width_with_padding >= max_width
{
let width = absolute_width_with_padding(column, max_width);
let info = ColumnDisplayInfo::new(column, width);
infos.insert(column.index, info);
#[cfg(feature = "_debug")]
println!(
"dynamic::find_columns_that_fit_into_average: Fixed column {} via MaxWidth constraint with size {}, as it's bigger than average {}",
column.index, width, average_space
);
remaining_width = remaining_width.saturating_sub(width.into());
remaining_columns -= 1;
if remaining_columns == 0 {
break;
}
average_space = remaining_width / remaining_columns;
found_smaller = true;
continue;
}
}
if usize::from(max_column_width) <= average_space {
let info = ColumnDisplayInfo::new(column, max_column_width);
infos.insert(column.index, info);
#[cfg(feature = "_debug")]
println!(
"dynamic::find_columns_that_fit_into_average: Fixed column {} with size {}, as it's smaller than average {}",
column.index, max_column_width, average_space
);
remaining_width = remaining_width.saturating_sub(max_column_width.into());
remaining_columns -= 1;
if remaining_columns == 0 {
break;
}
average_space = remaining_width / remaining_columns;
found_smaller = true;
}
}
}
(remaining_width, remaining_columns)
}
fn enforce_lower_boundary_constraints(
table: &Table,
infos: &mut DisplayInfos,
mut remaining_width: usize,
mut remaining_columns: usize,
visible_columns: usize,
) -> (usize, usize) {
let mut average_space = remaining_width / remaining_columns;
let mut try_again = true;
while try_again {
try_again = false;
for column in table.columns.iter() {
if infos.contains_key(&column.index) {
continue;
}
let Some(min_width) = constraint::min(table, &column.constraint, visible_columns)
else {
continue;
};
if average_space >= min_width.into() {
continue;
}
let width = absolute_width_with_padding(column, min_width);
let info = ColumnDisplayInfo::new(column, width);
infos.insert(column.index, info);
#[cfg(feature = "_debug")]
println!(
"dynamic::enforce_lower_boundary_constraints: Fixed column {} to min constraint width {}",
column.index, width
);
remaining_width = remaining_width.saturating_sub(width.into());
remaining_columns -= 1;
if remaining_columns == 0 {
break;
}
average_space = remaining_width / remaining_columns;
try_again = true;
}
}
(remaining_width, remaining_columns)
}
fn optimize_space_after_split(
table: &Table,
infos: &mut DisplayInfos,
mut remaining_width: usize,
mut remaining_columns: usize,
) -> (usize, usize) {
let mut found_smaller = true;
let mut average_space = remaining_width / remaining_columns;
#[cfg(feature = "_debug")]
println!(
"dynamic::optimize_space_after_split: Start with average_space {}",
average_space
);
while found_smaller {
found_smaller = false;
for column in table.columns.iter() {
if infos.contains_key(&column.index) {
continue;
}
let longest_line = longest_line_after_split(average_space, column, table);
#[cfg(feature = "_debug")]
println!(
"dynamic::optimize_space_after_split: Longest line after split for column {} is {}",
column.index, longest_line
);
let remaining_space = average_space.saturating_sub(longest_line);
if remaining_space >= 3 {
let info =
ColumnDisplayInfo::new(column, longest_line.try_into().unwrap_or(u16::MAX));
infos.insert(column.index, info);
remaining_width = remaining_width.saturating_sub(longest_line);
remaining_columns -= 1;
if remaining_columns == 0 {
break;
}
average_space = remaining_width / remaining_columns;
#[cfg(feature = "_debug")]
println!(
"dynamic::optimize_space_after_split: average_space is now {}",
average_space
);
found_smaller = true;
}
}
}
(remaining_width, remaining_columns)
}
fn longest_line_after_split(average_space: usize, column: &Column, table: &Table) -> usize {
let mut longest = 0;
for cell in table.column_cells_with_header_iter(column.index) {
let Some(cell) = cell else { continue };
let delimiter = delimiter(table, column, cell);
let info = ColumnDisplayInfo::new(column, average_space.try_into().unwrap_or(u16::MAX));
for line in cell.content.iter() {
if line.width() > average_space {
let parts = split_line(line, &info, delimiter);
#[cfg(feature = "_debug")]
println!(
"dynamic::longest_line_after_split: Splitting line with width {}. Original:\n {}\nSplitted:\n {:?}",
line.width(),
line,
parts
);
parts
.iter()
.for_each(|part| longest = longest.max(part.len()));
} else {
longest = longest.max(line.len())
}
}
}
longest
}
fn use_full_width(infos: &mut DisplayInfos, remaining_width: usize) {
let visible_columns = infos.iter().filter(|(_, info)| !info.is_hidden).count();
if visible_columns == 0 {
return;
}
let average_space = remaining_width / visible_columns;
let mut excess = remaining_width - (average_space * visible_columns);
for (_, info) in infos.iter_mut() {
if info.is_hidden {
continue;
}
let width = if excess > 0 {
excess -= 1;
(average_space + 1).try_into().unwrap_or(u16::MAX)
} else {
average_space.try_into().unwrap_or(u16::MAX)
};
info.content_width += width;
}
}
fn distribute_remaining_space(
columns: &[Column],
infos: &mut DisplayInfos,
remaining_width: usize,
remaining_columns: usize,
) {
let average_space = remaining_width / remaining_columns;
let mut excess = remaining_width - (average_space * remaining_columns);
for column in columns.iter() {
if infos.contains_key(&column.index) {
continue;
}
let width = if excess > 0 {
excess -= 1;
(average_space + 1).try_into().unwrap_or(u16::MAX)
} else {
average_space.try_into().unwrap_or(u16::MAX)
};
let info = ColumnDisplayInfo::new(column, width);
infos.insert(column.index, info);
}
}