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