papergrid/dimension/
peekable.rs

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