1 | use papergrid::records::vec_records::{CellInfo, VecRecords}; |
2 | |
3 | use crate::grid::{ |
4 | config::SpannedConfig, |
5 | dimension::{Dimension, Estimate}, |
6 | records::Records, |
7 | }; |
8 | |
9 | /// PeekableDimension is a [`Dimension`] implementation for a [`Table`] |
10 | /// |
11 | /// [`Table`]: crate::Table |
12 | #[derive (Debug, Default, Clone)] |
13 | pub struct PeekableDimension { |
14 | width: Vec<usize>, |
15 | height: Vec<usize>, |
16 | } |
17 | |
18 | impl PeekableDimension { |
19 | /// Calculates height of rows. |
20 | pub fn height<T: AsRef<str>>( |
21 | records: &VecRecords<CellInfo<T>>, |
22 | cfg: &SpannedConfig, |
23 | ) -> Vec<usize> { |
24 | estimation::build_height(records, cfg) |
25 | } |
26 | |
27 | /// Calculates width of columns. |
28 | pub fn width<T: AsRef<str>>( |
29 | records: &VecRecords<CellInfo<T>>, |
30 | cfg: &SpannedConfig, |
31 | ) -> Vec<usize> { |
32 | estimation::build_width(records, cfg) |
33 | } |
34 | |
35 | /// Return width and height lists. |
36 | pub fn get_values(self) -> (Vec<usize>, Vec<usize>) { |
37 | (self.width, self.height) |
38 | } |
39 | } |
40 | |
41 | impl Dimension for PeekableDimension { |
42 | fn get_width(&self, column: usize) -> usize { |
43 | self.width[column] |
44 | } |
45 | |
46 | fn get_height(&self, row: usize) -> usize { |
47 | self.height[row] |
48 | } |
49 | } |
50 | |
51 | impl<T> Estimate<&VecRecords<CellInfo<T>>, SpannedConfig> for PeekableDimension |
52 | where |
53 | T: AsRef<str>, |
54 | { |
55 | fn estimate(&mut self, records: &VecRecords<CellInfo<T>>, cfg: &SpannedConfig) { |
56 | let (width: Vec, height: Vec) = estimation::build_dimensions(records, cfg); |
57 | self.width = width; |
58 | self.height = height; |
59 | } |
60 | } |
61 | |
62 | mod estimation { |
63 | use core::cmp::{max, Ordering}; |
64 | use std::collections::HashMap; |
65 | |
66 | use papergrid::{ |
67 | config::Position, |
68 | records::vec_records::{Cell, CellInfo, VecRecords}, |
69 | }; |
70 | |
71 | use super::*; |
72 | |
73 | pub(super) fn build_dimensions<T: AsRef<str>>( |
74 | records: &VecRecords<CellInfo<T>>, |
75 | cfg: &SpannedConfig, |
76 | ) -> (Vec<usize>, Vec<usize>) { |
77 | let count_columns = records.count_columns(); |
78 | |
79 | let mut widths = vec![0; count_columns]; |
80 | let mut heights = vec![]; |
81 | |
82 | let mut vspans = HashMap::new(); |
83 | let mut hspans = HashMap::new(); |
84 | |
85 | for (row, columns) in records.iter_rows().enumerate() { |
86 | let mut row_height = 0; |
87 | for (col, cell) in columns.iter().enumerate() { |
88 | let pos = (row, col); |
89 | if !cfg.is_cell_visible(pos) { |
90 | continue; |
91 | } |
92 | |
93 | let height = cell.count_lines(); |
94 | let width = cell.width(); |
95 | |
96 | let pad = cfg.get_padding(pos.into()); |
97 | let width = width + pad.left.size + pad.right.size; |
98 | let height = height + pad.top.size + pad.bottom.size; |
99 | |
100 | match cfg.get_column_span(pos) { |
101 | Some(n) if n > 1 => { |
102 | let _ = vspans.insert(pos, (n, width)); |
103 | } |
104 | _ => widths[col] = max(widths[col], width), |
105 | } |
106 | |
107 | match cfg.get_row_span(pos) { |
108 | Some(n) if n > 1 => { |
109 | let _ = hspans.insert(pos, (n, height)); |
110 | } |
111 | _ => row_height = max(row_height, height), |
112 | } |
113 | } |
114 | |
115 | heights.push(row_height); |
116 | } |
117 | |
118 | let count_rows = heights.len(); |
119 | |
120 | adjust_vspans(cfg, count_columns, &vspans, &mut widths); |
121 | adjust_hspans(cfg, count_rows, &hspans, &mut heights); |
122 | |
123 | (widths, heights) |
124 | } |
125 | |
126 | fn adjust_hspans( |
127 | cfg: &SpannedConfig, |
128 | len: usize, |
129 | spans: &HashMap<Position, (usize, usize)>, |
130 | heights: &mut [usize], |
131 | ) { |
132 | if spans.is_empty() { |
133 | return; |
134 | } |
135 | |
136 | let mut spans_ordered = spans |
137 | .iter() |
138 | .map(|(k, v)| ((k.0, k.1), *v)) |
139 | .collect::<Vec<_>>(); |
140 | spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) { |
141 | Ordering::Equal => acol.cmp(bcol), |
142 | ord => ord, |
143 | }); |
144 | |
145 | for ((row, _), (span, height)) in spans_ordered { |
146 | adjust_row_range(cfg, height, len, row, row + span, heights); |
147 | } |
148 | } |
149 | |
150 | fn adjust_row_range( |
151 | cfg: &SpannedConfig, |
152 | max_span_height: usize, |
153 | len: usize, |
154 | start: usize, |
155 | end: usize, |
156 | heights: &mut [usize], |
157 | ) { |
158 | let range_height = range_height(cfg, len, start, end, heights); |
159 | if range_height >= max_span_height { |
160 | return; |
161 | } |
162 | |
163 | inc_range(heights, max_span_height - range_height, start, end); |
164 | } |
165 | |
166 | fn range_height( |
167 | cfg: &SpannedConfig, |
168 | len: usize, |
169 | start: usize, |
170 | end: usize, |
171 | heights: &[usize], |
172 | ) -> usize { |
173 | let count_borders = count_horizontal_borders(cfg, len, start, end); |
174 | let range_height = heights[start..end].iter().sum::<usize>(); |
175 | count_borders + range_height |
176 | } |
177 | |
178 | fn count_horizontal_borders( |
179 | cfg: &SpannedConfig, |
180 | len: usize, |
181 | start: usize, |
182 | end: usize, |
183 | ) -> usize { |
184 | (start..end) |
185 | .skip(1) |
186 | .filter(|&i| cfg.has_horizontal(i, len)) |
187 | .count() |
188 | } |
189 | |
190 | fn inc_range(list: &mut [usize], size: usize, start: usize, end: usize) { |
191 | if list.is_empty() { |
192 | return; |
193 | } |
194 | |
195 | let span = end - start; |
196 | let one = size / span; |
197 | let rest = size - span * one; |
198 | |
199 | let mut i = start; |
200 | while i < end { |
201 | if i == start { |
202 | list[i] += one + rest; |
203 | } else { |
204 | list[i] += one; |
205 | } |
206 | |
207 | i += 1; |
208 | } |
209 | } |
210 | |
211 | fn adjust_vspans( |
212 | cfg: &SpannedConfig, |
213 | len: usize, |
214 | spans: &HashMap<Position, (usize, usize)>, |
215 | widths: &mut [usize], |
216 | ) { |
217 | if spans.is_empty() { |
218 | return; |
219 | } |
220 | |
221 | // The overall width distribution will be different depend on the order. |
222 | // |
223 | // We sort spans in order to prioritize the smaller spans first. |
224 | let mut spans_ordered = spans |
225 | .iter() |
226 | .map(|(k, v)| ((k.0, k.1), *v)) |
227 | .collect::<Vec<_>>(); |
228 | spans_ordered.sort_unstable_by(|a, b| match a.1 .0.cmp(&b.1 .0) { |
229 | Ordering::Equal => a.0.cmp(&b.0), |
230 | o => o, |
231 | }); |
232 | |
233 | for ((_, col), (span, width)) in spans_ordered { |
234 | adjust_column_range(cfg, width, len, col, col + span, widths); |
235 | } |
236 | } |
237 | |
238 | fn adjust_column_range( |
239 | cfg: &SpannedConfig, |
240 | max_span_width: usize, |
241 | len: usize, |
242 | start: usize, |
243 | end: usize, |
244 | widths: &mut [usize], |
245 | ) { |
246 | let range_width = range_width(cfg, len, start, end, widths); |
247 | if range_width >= max_span_width { |
248 | return; |
249 | } |
250 | |
251 | inc_range(widths, max_span_width - range_width, start, end); |
252 | } |
253 | |
254 | fn range_width( |
255 | cfg: &SpannedConfig, |
256 | len: usize, |
257 | start: usize, |
258 | end: usize, |
259 | widths: &[usize], |
260 | ) -> usize { |
261 | let count_borders = count_vertical_borders(cfg, len, start, end); |
262 | let range_width = widths[start..end].iter().sum::<usize>(); |
263 | count_borders + range_width |
264 | } |
265 | |
266 | fn count_vertical_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize { |
267 | (start..end) |
268 | .skip(1) |
269 | .filter(|&i| cfg.has_vertical(i, len)) |
270 | .count() |
271 | } |
272 | |
273 | pub(super) fn build_height<T: AsRef<str>>( |
274 | records: &VecRecords<CellInfo<T>>, |
275 | cfg: &SpannedConfig, |
276 | ) -> Vec<usize> { |
277 | let mut heights = vec![]; |
278 | let mut hspans = HashMap::new(); |
279 | |
280 | for (row, columns) in records.iter_rows().enumerate() { |
281 | let mut row_height = 0; |
282 | for (col, cell) in columns.iter().enumerate() { |
283 | let pos = (row, col); |
284 | if !cfg.is_cell_visible(pos) { |
285 | continue; |
286 | } |
287 | |
288 | let height = cell.count_lines(); |
289 | match cfg.get_row_span(pos) { |
290 | Some(n) if n > 1 => { |
291 | let _ = hspans.insert(pos, (n, height)); |
292 | } |
293 | _ => row_height = max(row_height, height), |
294 | } |
295 | } |
296 | |
297 | heights.push(row_height); |
298 | } |
299 | |
300 | adjust_hspans(cfg, heights.len(), &hspans, &mut heights); |
301 | |
302 | heights |
303 | } |
304 | |
305 | pub(super) fn build_width<T: AsRef<str>>( |
306 | records: &VecRecords<CellInfo<T>>, |
307 | cfg: &SpannedConfig, |
308 | ) -> Vec<usize> { |
309 | let count_columns = records.count_columns(); |
310 | |
311 | let mut widths = vec![0; count_columns]; |
312 | let mut vspans = HashMap::new(); |
313 | |
314 | for (row, columns) in records.iter_rows().enumerate() { |
315 | for (col, cell) in columns.iter().enumerate() { |
316 | let pos = (row, col); |
317 | if !cfg.is_cell_visible(pos) { |
318 | continue; |
319 | } |
320 | |
321 | let width = cell.width(); |
322 | match cfg.get_column_span(pos) { |
323 | Some(n) if n > 1 => { |
324 | let _ = vspans.insert(pos, (n, width)); |
325 | } |
326 | _ => widths[col] = max(widths[col], width), |
327 | } |
328 | } |
329 | } |
330 | |
331 | adjust_vspans(cfg, count_columns, &vspans, &mut widths); |
332 | |
333 | widths |
334 | } |
335 | } |
336 | |