1use {
2 crate::InsufficientWidthError,
3 std::cmp,
4};
5
6pub struct TblFit {
9 cols: Vec<ColData>,
10 available_sum_width: usize,
11}
12
13#[derive(Debug, Clone, Copy)]
15struct ColData {
16 sum_widths: usize,
17 width: usize,
18 count: usize,
19}
20
21impl Default for ColData {
22 fn default() -> Self {
23 Self {
24 sum_widths: 0,
25 width: 3, count: 0, }
28 }
29}
30
31impl ColData {
32 const fn avg_width(self) -> usize {
33 if self.count == 0 {
34 0
35 } else {
36 div_ceil(self.sum_widths, self.count)
37 }
38 }
39 pub fn see_cell(&mut self, cell_width: usize) {
40 self.sum_widths += cell_width;
41 self.width = self.width.max(cell_width);
42 self.count += 1;
43 }
44}
45
46pub struct TblFitResult {
48 pub reduced: bool,
50
51 pub col_widths: Vec<usize>,
54}
55
56impl TblFit {
57 pub fn new(cols_count: usize, available_width: usize) -> Result<Self, InsufficientWidthError> {
62 if available_width < cols_count * 4 + 1 {
63 return Err(InsufficientWidthError { available_width });
64 }
65 let cols = vec![ColData::default(); cols_count];
66 let available_sum_width = available_width - 1 - cols_count;
67 Ok(Self {
68 cols,
69 available_sum_width,
70 })
71 }
72 pub fn see_cell(&mut self, col_idx: usize, cell_width: usize) {
73 if let Some(col) = self.cols.get_mut(col_idx) {
74 col.see_cell(cell_width);
75 }
76 }
77 pub fn fit(&self) -> TblFitResult {
79 let sum_widths: usize = self.cols.iter().map(|c| c.width).sum();
80 if sum_widths <= self.available_sum_width {
81 return TblFitResult {
82 reduced: false,
83 col_widths: self.cols.iter().map(|c| c.width).collect(),
84 };
85 }
86 if self.cols.is_empty() {
87 return TblFitResult {
88 reduced: false,
89 col_widths: Vec::new(),
90 };
91 }
92 if self.cols.len() == 1 {
93 return TblFitResult {
94 reduced: false,
95 col_widths: vec![self.available_sum_width],
96 };
97 }
98 #[derive(Debug)]
99 struct ColFit {
100 idx: usize, std_width: usize, avg_width: usize, width: usize, }
105 let mut fits: Vec<ColFit> = self
106 .cols
107 .iter()
108 .enumerate()
109 .map(|(idx, c)| ColFit {
110 idx,
111 std_width: c.width,
112 avg_width: c.avg_width(),
113 width: c.width,
114 })
115 .collect();
116
117 if self.available_sum_width >= sum_widths {
118 return TblFitResult {
119 reduced: false,
120 col_widths: self.cols.iter().map(|c| c.width).collect(),
121 };
122 }
123
124 let mut excess = sum_widths - self.available_sum_width;
125 fits.sort_by_key(|c| cmp::Reverse(c.width));
126
127 let potential_uncut_gain_1 = fits
131 .iter()
132 .filter(|c| c.width > 4 && c.width > c.avg_width + 1)
133 .map(|c| (c.width - c.avg_width).min(4))
134 .sum::<usize>();
135 let potential_cut_gain_1 = potential_uncut_gain_1.min(excess);
136 if potential_cut_gain_1 > 0 {
137 for c in fits.iter_mut() {
138 if c.std_width > 4 && c.std_width > c.avg_width {
139 let gain_1 = div_ceil(
140 (c.width - c.avg_width) * potential_cut_gain_1,
141 potential_uncut_gain_1,
142 );
143 let gain_1 = gain_1.min(excess).min(c.width - 4);
144 c.width -= gain_1;
145 excess -= gain_1;
146 if excess == 0 {
147 break;
148 }
149 }
150 }
151 }
152
153 if excess > 0 {
156 let potential_total_gain_2 =
157 fits.iter().map(|c| c.width - 3).sum::<usize>().min(excess);
158 let excess_before_2 = excess;
159 for c in fits.iter_mut() {
160 let gain_2 = div_ceil((c.width - 3) * excess_before_2, potential_total_gain_2);
161 let gain_2 = gain_2.min(excess).min(c.width - 3);
162 c.width -= gain_2;
163 excess -= gain_2;
164 if excess == 0 {
165 break;
166 }
167 }
168 }
169
170 fits.sort_by_key(|c| c.idx);
173 TblFitResult {
174 reduced: true,
175 col_widths: fits.iter().map(|c| c.width).collect(),
176 }
177 }
178}
179
180const fn div_ceil(q: usize, r: usize) -> usize {
182 let mut res = q / r;
183 if q % r != 0 {
184 res += 1;
185 }
186 res
187}