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::{IntoRecords, 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: R, cfg: &SpannedConfig) -> Vec<usize>
31 where
32 R: Records,
33 <R::Iter as IntoRecords>::Cell: AsRef<str>,
34 {
35 build_height(records, cfg)
36 }
37
38 /// Calculates width of columns.
39 pub fn width<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
40 where
41 R: Records,
42 <R::Iter as IntoRecords>::Cell: AsRef<str>,
43 {
44 build_width(records, cfg)
45 }
46
47 /// Calculates width of columns.
48 pub fn width_total<R>(records: R, cfg: &SpannedConfig) -> usize
49 where
50 R: Records,
51 <R::Iter as IntoRecords>::Cell: AsRef<str>,
52 {
53 get_width_total(records, cfg)
54 }
55
56 /// Calculates height of rows.
57 pub fn height_total<R>(records: R, cfg: &SpannedConfig) -> usize
58 where
59 R: Records,
60 <R::Iter as IntoRecords>::Cell: AsRef<str>,
61 {
62 get_height_total(records, cfg)
63 }
64
65 /// Return width and height lists.
66 pub fn get_values(self) -> (Vec<usize>, Vec<usize>) {
67 (self.width, self.height)
68 }
69}
70
71impl Dimension for SpannedGridDimension {
72 fn get_width(&self, column: usize) -> usize {
73 self.width[column]
74 }
75
76 fn get_height(&self, row: usize) -> usize {
77 self.height[row]
78 }
79}
80
81impl<R> Estimate<R, SpannedConfig> for SpannedGridDimension
82where
83 R: Records,
84 <R::Iter as IntoRecords>::Cell: AsRef<str>,
85{
86 fn estimate(&mut self, records: R, cfg: &SpannedConfig) {
87 let (width: Vec, height: Vec) = build_dimensions(records, cfg);
88 self.width = width;
89 self.height = height;
90 }
91}
92
93fn build_dimensions<R>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, Vec<usize>)
94where
95 R: Records,
96 <R::Iter as IntoRecords>::Cell: AsRef<str>,
97{
98 let count_columns = records.count_columns();
99
100 let mut widths = vec![0; count_columns];
101 let mut heights = vec![];
102
103 let mut vspans = HashMap::new();
104 let mut hspans = HashMap::new();
105
106 for (row, columns) in records.iter_rows().into_iter().enumerate() {
107 let mut row_height = 0;
108 for (col, cell) in columns.into_iter().enumerate() {
109 let pos = (row, col);
110 if !cfg.is_cell_visible(pos) {
111 continue;
112 }
113
114 let text = cell.as_ref();
115 let (height, width) = string_dimension(text);
116 let pad = cfg.get_padding(pos.into());
117 let width = width + pad.left.size + pad.right.size;
118 let height = height + pad.top.size + pad.bottom.size;
119
120 match cfg.get_column_span(pos) {
121 Some(n) if n > 1 => {
122 vspans.insert(pos, (n, width));
123 }
124 _ => widths[col] = max(widths[col], width),
125 }
126
127 match cfg.get_row_span(pos) {
128 Some(n) if n > 1 => {
129 hspans.insert(pos, (n, height));
130 }
131 _ => row_height = max(row_height, height),
132 }
133 }
134
135 heights.push(row_height);
136 }
137
138 let count_rows = heights.len();
139
140 adjust_vspans(cfg, count_columns, &vspans, &mut widths);
141 adjust_hspans(cfg, count_rows, &hspans, &mut heights);
142
143 (widths, heights)
144}
145
146fn adjust_hspans(
147 cfg: &SpannedConfig,
148 len: usize,
149 spans: &HashMap<Position, (usize, usize)>,
150 heights: &mut [usize],
151) {
152 if spans.is_empty() {
153 return;
154 }
155
156 let mut spans_ordered: Vec<((usize, usize), (usize, …))> = spansimpl Iterator
157 .iter()
158 .map(|(k: &(usize, usize), v: &(usize, usize))| ((k.0, k.1), *v))
159 .collect::<Vec<_>>();
160 spans_ordered.sort_unstable_by(|(arow: &(usize, usize), acol: &(usize, usize)), (brow: &(usize, usize), bcol: &(usize, usize))| match arow.cmp(brow) {
161 Ordering::Equal => acol.cmp(bcol),
162 ord: Ordering => ord,
163 });
164
165 for ((row: usize, _), (span: usize, height: usize)) in spans_ordered {
166 adjust_row_range(cfg, height, len, start:row, end:row + span, heights);
167 }
168}
169
170fn adjust_row_range(
171 cfg: &SpannedConfig,
172 max_span_height: usize,
173 len: usize,
174 start: usize,
175 end: usize,
176 heights: &mut [usize],
177) {
178 let range_height: usize = range_height(cfg, len, start, end, heights);
179 if range_height >= max_span_height {
180 return;
181 }
182
183 inc_range(list:heights, size:max_span_height - range_height, start, end);
184}
185
186fn range_height(
187 cfg: &SpannedConfig,
188 len: usize,
189 start: usize,
190 end: usize,
191 heights: &[usize],
192) -> usize {
193 let count_borders: usize = count_horizontal_borders(cfg, len, start, end);
194 let range_height: usize = heights[start..end].iter().sum::<usize>();
195 count_borders + range_height
196}
197
198fn count_horizontal_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize {
199 (start..end)
200 .skip(1)
201 .filter(|&i: usize| cfg.has_horizontal(row:i, count_rows:len))
202 .count()
203}
204
205fn get_cell_height(cell: &str, cfg: &SpannedConfig, pos: Position) -> usize {
206 let count_lines: usize = max(v1:1, v2:count_lines(cell));
207 let padding: Sides = cfg.get_padding(entity:pos.into());
208 count_lines + padding.top.size + padding.bottom.size
209}
210
211fn inc_range(list: &mut [usize], size: usize, start: usize, end: usize) {
212 if list.is_empty() {
213 return;
214 }
215
216 let span: usize = end - start;
217 let one: usize = size / span;
218 let rest: usize = size - span * one;
219
220 let mut i: usize = start;
221 while i < end {
222 if i == start {
223 list[i] += one + rest;
224 } else {
225 list[i] += one;
226 }
227
228 i += 1;
229 }
230}
231
232fn adjust_vspans(
233 cfg: &SpannedConfig,
234 len: usize,
235 spans: &HashMap<Position, (usize, usize)>,
236 widths: &mut [usize],
237) {
238 if spans.is_empty() {
239 return;
240 }
241
242 // The overall width distribution will be different depend on the order.
243 //
244 // We sort spans in order to prioritize the smaller spans first.
245 let mut spans_ordered: Vec<((usize, usize), (usize, …))> = spansimpl Iterator
246 .iter()
247 .map(|(k: &(usize, usize), v: &(usize, usize))| ((k.0, k.1), *v))
248 .collect::<Vec<_>>();
249 spans_ordered.sort_unstable_by(|a: &((usize, usize), (usize, …)), b: &((usize, usize), (usize, …))| match a.1 .0.cmp(&b.1 .0) {
250 Ordering::Equal => a.0.cmp(&b.0),
251 o: Ordering => o,
252 });
253
254 for ((_, col: usize), (span: usize, width: usize)) in spans_ordered {
255 adjust_column_range(cfg, width, len, start:col, end:col + span, widths);
256 }
257}
258
259fn adjust_column_range(
260 cfg: &SpannedConfig,
261 max_span_width: usize,
262 len: usize,
263 start: usize,
264 end: usize,
265 widths: &mut [usize],
266) {
267 let range_width: usize = range_width(cfg, len, start, end, widths);
268 if range_width >= max_span_width {
269 return;
270 }
271
272 inc_range(list:widths, size:max_span_width - range_width, start, end);
273}
274
275fn get_cell_width(text: &str, cfg: &SpannedConfig, pos: Position) -> usize {
276 let padding: usize = get_cell_padding(cfg, pos);
277 let width: usize = string_width_multiline(text);
278 width + padding
279}
280
281fn get_cell_padding(cfg: &SpannedConfig, pos: Position) -> usize {
282 let padding: Sides = cfg.get_padding(entity:pos.into());
283 padding.left.size + padding.right.size
284}
285
286fn range_width(
287 cfg: &SpannedConfig,
288 len: usize,
289 start: usize,
290 end: usize,
291 widths: &[usize],
292) -> usize {
293 let count_borders: usize = count_vertical_borders(cfg, len, start, end);
294 let range_width: usize = widths[start..end].iter().sum::<usize>();
295 count_borders + range_width
296}
297
298fn count_vertical_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize {
299 (start..end)
300 .skip(1)
301 .filter(|&i: usize| cfg.has_vertical(col:i, count_columns:len))
302 .count()
303}
304
305fn build_height<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
306where
307 R: Records,
308 <R::Iter as IntoRecords>::Cell: AsRef<str>,
309{
310 let mut heights = vec![];
311 let mut hspans = HashMap::new();
312
313 for (row, columns) in records.iter_rows().into_iter().enumerate() {
314 let mut row_height = 0;
315 for (col, cell) in columns.into_iter().enumerate() {
316 let pos = (row, col);
317 if !cfg.is_cell_visible(pos) {
318 continue;
319 }
320
321 let height = get_cell_height(cell.as_ref(), cfg, pos);
322 match cfg.get_row_span(pos) {
323 Some(n) if n > 1 => {
324 hspans.insert(pos, (n, height));
325 }
326 _ => row_height = max(row_height, height),
327 }
328 }
329
330 heights.push(row_height);
331 }
332
333 adjust_hspans(cfg, heights.len(), &hspans, &mut heights);
334
335 heights
336}
337
338fn build_width<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
339where
340 R: Records,
341 <R::Iter as IntoRecords>::Cell: AsRef<str>,
342{
343 let count_columns = records.count_columns();
344
345 let mut widths = vec![0; count_columns];
346 let mut vspans = HashMap::new();
347
348 for (row, columns) in records.iter_rows().into_iter().enumerate() {
349 for (col, cell) in columns.into_iter().enumerate() {
350 let pos = (row, col);
351 if !cfg.is_cell_visible(pos) {
352 continue;
353 }
354
355 let width = get_cell_width(cell.as_ref(), cfg, pos);
356 match cfg.get_column_span(pos) {
357 Some(n) if n > 1 => {
358 vspans.insert(pos, (n, width));
359 }
360 _ => widths[col] = max(widths[col], width),
361 }
362 }
363 }
364
365 adjust_vspans(cfg, count_columns, &vspans, &mut widths);
366
367 widths
368}
369
370fn get_width_total<R>(records: R, cfg: &SpannedConfig) -> usize
371where
372 R: Records,
373 <R::Iter as IntoRecords>::Cell: AsRef<str>,
374{
375 let widths: Vec = build_width(records, cfg);
376 let count_columns: usize = widths.len();
377
378 let total: usize = widths.into_iter().sum::<usize>();
379 let count_verticals: usize = cfg.count_vertical(count_columns);
380
381 total + count_verticals
382}
383
384fn get_height_total<R>(records: R, cfg: &SpannedConfig) -> usize
385where
386 R: Records,
387 <R::Iter as IntoRecords>::Cell: AsRef<str>,
388{
389 let heights: Vec = build_height(records, cfg);
390 let count_rows: usize = heights.len();
391
392 let total: usize = heights.into_iter().sum::<usize>();
393 let count_horizontals: usize = cfg.count_horizontal(count_rows);
394
395 total + count_horizontals
396}
397