papergrid/dimension/
iterable.rs

1//! The module contains a [`IterGridDimension`].
2
3use std::{
4    cmp::{max, Ordering},
5    collections::HashMap,
6};
7
8use crate::{
9    config::{spanned::SpannedConfig, Position},
10    dimension::{Dimension, Estimate},
11    records::{IntoRecords, Records},
12    util::string::{count_lines, get_text_dimension, get_text_width},
13};
14
15/// A [`Dimension`] implementation which calculates exact column/row width/height.
16#[derive(Debug, Default, Clone, PartialEq, Eq)]
17pub struct IterGridDimension {
18    height: Vec<usize>,
19    width: Vec<usize>,
20}
21
22impl IterGridDimension {
23    /// Calculates height of rows.
24    pub fn height<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
25    where
26        R: Records,
27        <R::Iter as IntoRecords>::Cell: AsRef<str>,
28    {
29        build_height(records, cfg)
30    }
31
32    /// Calculates width of columns.
33    pub fn width<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
34    where
35        R: Records,
36        <R::Iter as IntoRecords>::Cell: AsRef<str>,
37    {
38        build_width(records, cfg)
39    }
40
41    /// Calculates width of columns.
42    pub fn width_total<R>(records: R, cfg: &SpannedConfig) -> usize
43    where
44        R: Records,
45        <R::Iter as IntoRecords>::Cell: AsRef<str>,
46    {
47        get_width_total(records, cfg)
48    }
49
50    /// Calculates height of rows.
51    pub fn height_total<R>(records: R, cfg: &SpannedConfig) -> usize
52    where
53        R: Records,
54        <R::Iter as IntoRecords>::Cell: AsRef<str>,
55    {
56        get_height_total(records, cfg)
57    }
58
59    /// Return width and height lists.
60    pub fn get_values(self) -> (Vec<usize>, Vec<usize>) {
61        (self.width, self.height)
62    }
63}
64
65impl Dimension for IterGridDimension {
66    fn get_width(&self, column: usize) -> usize {
67        self.width[column]
68    }
69
70    fn get_height(&self, row: usize) -> usize {
71        self.height[row]
72    }
73}
74
75impl<R> Estimate<R, SpannedConfig> for IterGridDimension
76where
77    R: Records,
78    <R::Iter as IntoRecords>::Cell: AsRef<str>,
79{
80    fn estimate(&mut self, records: R, cfg: &SpannedConfig) {
81        let (width, height) = build_dimensions(records, cfg);
82        self.width = width;
83        self.height = height;
84    }
85}
86
87fn build_dimensions<R>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, Vec<usize>)
88where
89    R: Records,
90    <R::Iter as IntoRecords>::Cell: AsRef<str>,
91{
92    if cfg.has_row_spans() || cfg.has_column_spans() {
93        build_dimensions_spanned(records, cfg)
94    } else {
95        build_dimensions_basic(records, cfg)
96    }
97}
98
99fn build_dimensions_basic<R>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, Vec<usize>)
100where
101    R: Records,
102    <R::Iter as IntoRecords>::Cell: AsRef<str>,
103{
104    let count_columns = records.count_columns();
105
106    let mut widths = vec![0; count_columns];
107    let mut heights = vec![];
108
109    for (row, columns) in records.iter_rows().into_iter().enumerate() {
110        let mut row_height = 0;
111        for (col, cell) in columns.into_iter().enumerate() {
112            let (height, width) = get_text_dimension(cell.as_ref());
113
114            let pad = cfg.get_padding(Position::new(row, col));
115            let width = width + pad.left.size + pad.right.size;
116            let height = height + pad.top.size + pad.bottom.size;
117
118            widths[col] = max(widths[col], width);
119            row_height = max(row_height, height);
120        }
121
122        heights.push(row_height);
123    }
124
125    (widths, heights)
126}
127
128fn build_dimensions_spanned<R>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, Vec<usize>)
129where
130    R: Records,
131    <R::Iter as IntoRecords>::Cell: AsRef<str>,
132{
133    let count_columns = records.count_columns();
134
135    let mut widths = vec![0; count_columns];
136    let mut heights = vec![];
137
138    let mut vspans = HashMap::new();
139    let mut hspans = HashMap::new();
140
141    for (row, columns) in records.iter_rows().into_iter().enumerate() {
142        let mut row_height = 0;
143        for (col, cell) in columns.into_iter().enumerate() {
144            let pos = (row, col).into();
145            if !cfg.is_cell_visible(pos) {
146                continue;
147            }
148
149            let text = cell.as_ref();
150            let (height, width) = get_text_dimension(text);
151
152            let pad = cfg.get_padding(pos);
153            let width = width + pad.left.size + pad.right.size;
154            let height = height + pad.top.size + pad.bottom.size;
155
156            match cfg.get_column_span(pos) {
157                Some(n) if n > 1 => {
158                    vspans.insert(pos, (n, width));
159                }
160                _ => widths[col] = max(widths[col], width),
161            }
162
163            match cfg.get_row_span(pos) {
164                Some(n) if n > 1 => {
165                    hspans.insert(pos, (n, height));
166                }
167                _ => row_height = max(row_height, height),
168            }
169        }
170
171        heights.push(row_height);
172    }
173
174    let count_rows = heights.len();
175
176    adjust_vspans(cfg, count_columns, &vspans, &mut widths);
177    adjust_hspans(cfg, count_rows, &hspans, &mut heights);
178
179    (widths, heights)
180}
181
182fn adjust_hspans(
183    cfg: &SpannedConfig,
184    len: usize,
185    spans: &HashMap<Position, (usize, usize)>,
186    heights: &mut [usize],
187) {
188    if spans.is_empty() {
189        return;
190    }
191
192    let mut spans_ordered = spans.iter().map(|(k, v)| (k, *v)).collect::<Vec<_>>();
193    spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) {
194        Ordering::Equal => acol.cmp(bcol),
195        ord => ord,
196    });
197
198    for (pos, (span, height)) in spans_ordered {
199        adjust_row_range(cfg, height, len, pos.row, pos.row + span, heights);
200    }
201}
202
203fn adjust_row_range(
204    cfg: &SpannedConfig,
205    max_span_height: usize,
206    len: usize,
207    start: usize,
208    end: usize,
209    heights: &mut [usize],
210) {
211    let range_height = range_height(cfg, len, start, end, heights);
212    if range_height >= max_span_height {
213        return;
214    }
215
216    inc_range(heights, max_span_height - range_height, start, end);
217}
218
219fn range_height(
220    cfg: &SpannedConfig,
221    len: usize,
222    start: usize,
223    end: usize,
224    heights: &[usize],
225) -> usize {
226    let count_borders = count_horizontal_borders(cfg, len, start, end);
227    let range_height = heights[start..end].iter().sum::<usize>();
228    count_borders + range_height
229}
230
231fn count_horizontal_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize {
232    (start..end)
233        .skip(1)
234        .filter(|&i| cfg.has_horizontal(i, len))
235        .count()
236}
237
238fn get_cell_height(cell: &str, cfg: &SpannedConfig, pos: Position) -> usize {
239    let count_lines = max(1, count_lines(cell));
240    let padding = cfg.get_padding(pos);
241    count_lines + padding.top.size + padding.bottom.size
242}
243
244fn inc_range(list: &mut [usize], size: usize, start: usize, end: usize) {
245    if list.is_empty() {
246        return;
247    }
248
249    let span = end - start;
250    let one = size / span;
251    let rest = size - span * one;
252
253    let mut i = start;
254    while i < end {
255        if i == start {
256            list[i] += one + rest;
257        } else {
258            list[i] += one;
259        }
260
261        i += 1;
262    }
263}
264
265fn adjust_vspans(
266    cfg: &SpannedConfig,
267    len: usize,
268    spans: &HashMap<Position, (usize, usize)>,
269    widths: &mut [usize],
270) {
271    if spans.is_empty() {
272        return;
273    }
274
275    // The overall width distribution will be different depend on the order.
276    //
277    // We sort spans in order to prioritize the smaller spans first.
278    let mut spans_ordered = spans.iter().map(|(k, v)| (k, *v)).collect::<Vec<_>>();
279    spans_ordered.sort_unstable_by(|a, b| match a.1 .0.cmp(&b.1 .0) {
280        Ordering::Equal => a.0.cmp(b.0),
281        o => o,
282    });
283
284    for (pos, (span, width)) in spans_ordered {
285        adjust_column_range(cfg, width, len, pos.col, pos.col + span, widths);
286    }
287}
288
289fn adjust_column_range(
290    cfg: &SpannedConfig,
291    max_span_width: usize,
292    len: usize,
293    start: usize,
294    end: usize,
295    widths: &mut [usize],
296) {
297    let range_width = range_width(cfg, len, start, end, widths);
298    if range_width >= max_span_width {
299        return;
300    }
301
302    inc_range(widths, max_span_width - range_width, start, end);
303}
304
305fn get_cell_width(text: &str, cfg: &SpannedConfig, pos: Position) -> usize {
306    let padding = get_cell_padding(cfg, pos);
307    let width = get_text_width(text);
308    width + padding
309}
310
311fn get_cell_padding(cfg: &SpannedConfig, pos: Position) -> usize {
312    let padding = cfg.get_padding(pos);
313    padding.left.size + padding.right.size
314}
315
316fn range_width(
317    cfg: &SpannedConfig,
318    len: usize,
319    start: usize,
320    end: usize,
321    widths: &[usize],
322) -> usize {
323    let count_borders = count_vertical_borders(cfg, len, start, end);
324    let range_width = widths[start..end].iter().sum::<usize>();
325    count_borders + range_width
326}
327
328fn count_vertical_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize {
329    (start..end)
330        .skip(1)
331        .filter(|&i| cfg.has_vertical(i, len))
332        .count()
333}
334
335fn build_height<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
336where
337    R: Records,
338    <R::Iter as IntoRecords>::Cell: AsRef<str>,
339{
340    if cfg.has_row_spans() {
341        build_height_spanned(records, cfg)
342    } else {
343        build_height_basic(records, cfg)
344    }
345}
346
347fn build_height_basic<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
348where
349    R: Records,
350    <R::Iter as IntoRecords>::Cell: AsRef<str>,
351{
352    let mut heights = vec![];
353
354    for (row, columns) in records.iter_rows().into_iter().enumerate() {
355        let mut row_height = 0;
356        for (col, cell) in columns.into_iter().enumerate() {
357            let pos = (row, col).into();
358            let height = get_cell_height(cell.as_ref(), cfg, pos);
359            row_height = max(row_height, height);
360        }
361
362        heights.push(row_height);
363    }
364
365    heights
366}
367
368fn build_height_spanned<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
369where
370    R: Records,
371    <R::Iter as IntoRecords>::Cell: AsRef<str>,
372{
373    let mut heights = vec![];
374    let mut hspans = HashMap::new();
375
376    for (row, columns) in records.iter_rows().into_iter().enumerate() {
377        let mut row_height = 0;
378        for (col, cell) in columns.into_iter().enumerate() {
379            let pos = (row, col).into();
380            if !cfg.is_cell_visible(pos) {
381                continue;
382            }
383
384            let height = get_cell_height(cell.as_ref(), cfg, pos);
385            match cfg.get_row_span(pos) {
386                Some(n) if n > 1 => {
387                    hspans.insert(pos, (n, height));
388                }
389                _ => row_height = max(row_height, height),
390            }
391        }
392
393        heights.push(row_height);
394    }
395
396    adjust_hspans(cfg, heights.len(), &hspans, &mut heights);
397
398    heights
399}
400
401fn build_width<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
402where
403    R: Records,
404    <R::Iter as IntoRecords>::Cell: AsRef<str>,
405{
406    if cfg.has_column_spans() {
407        build_width_spanned(records, cfg)
408    } else {
409        build_width_basic(records, cfg)
410    }
411}
412
413fn build_width_basic<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
414where
415    R: Records,
416    <R::Iter as IntoRecords>::Cell: AsRef<str>,
417{
418    let count_columns = records.count_columns();
419    let mut widths = vec![0; count_columns];
420
421    for (row, columns) in records.iter_rows().into_iter().enumerate() {
422        for (col, cell) in columns.into_iter().enumerate() {
423            let pos = (row, col).into();
424            let width = get_cell_width(cell.as_ref(), cfg, pos);
425            widths[col] = max(widths[col], width);
426        }
427    }
428
429    widths
430}
431
432fn build_width_spanned<R>(records: R, cfg: &SpannedConfig) -> Vec<usize>
433where
434    R: Records,
435    <R::Iter as IntoRecords>::Cell: AsRef<str>,
436{
437    let count_columns = records.count_columns();
438
439    let mut widths = vec![0; count_columns];
440    let mut vspans = HashMap::new();
441
442    for (row, columns) in records.iter_rows().into_iter().enumerate() {
443        for (col, cell) in columns.into_iter().enumerate() {
444            let pos = (row, col).into();
445            if !cfg.is_cell_visible(pos) {
446                continue;
447            }
448
449            let width = get_cell_width(cell.as_ref(), cfg, pos);
450            match cfg.get_column_span(pos) {
451                Some(n) if n > 1 => {
452                    vspans.insert(pos, (n, width));
453                }
454                _ => widths[col] = max(widths[col], width),
455            }
456        }
457    }
458
459    adjust_vspans(cfg, count_columns, &vspans, &mut widths);
460
461    widths
462}
463
464fn get_width_total<R>(records: R, cfg: &SpannedConfig) -> usize
465where
466    R: Records,
467    <R::Iter as IntoRecords>::Cell: AsRef<str>,
468{
469    let widths = build_width(records, cfg);
470    let count_columns = widths.len();
471
472    let total = widths.into_iter().sum::<usize>();
473    let count_verticals = cfg.count_vertical(count_columns);
474
475    total + count_verticals
476}
477
478fn get_height_total<R>(records: R, cfg: &SpannedConfig) -> usize
479where
480    R: Records,
481    <R::Iter as IntoRecords>::Cell: AsRef<str>,
482{
483    let heights = build_height(records, cfg);
484    let count_rows = heights.len();
485
486    let total = heights.into_iter().sum::<usize>();
487    let count_horizontals = cfg.count_horizontal(count_rows);
488
489    total + count_horizontals
490}