1//! The module contains a [`SpannedGridDimension`] for [`Grid`] height/width estimation.
2//!
3//! [`Grid`]: crate::grid::iterable::Grid
4
5use std::{
6 cmp::{max, Ordering},
7 collections::HashMap,
8};
9
10use crate::{
11 config::Position,
12 dimension::{Dimension, Estimate},
13 records::Records,
14 util::string::{count_lines, string_dimension, string_width_multiline},
15};
16
17use 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)]
23pub struct SpannedGridDimension {
24 height: Vec<usize>,
25 width: Vec<usize>,
26}
27
28impl 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
45impl 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
55impl<R> Estimate<R, SpannedConfig> for SpannedGridDimension
56where
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
66fn 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
115fn 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
139fn 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
155fn 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
167fn 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
174fn 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
180fn 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
201fn 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
228fn 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
244fn 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
250fn 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
255fn 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
267fn 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
274fn 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
303fn 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