papergrid/estimation/
height.rs

1//! The module contains a [`HeightEstimator`] for [`Grid`] height estimation.
2//!
3//! [`Grid`]: crate::Grid
4
5use std::cmp::{max, Ordering};
6
7use crate::{records::Records, Entity, GridConfig, Position};
8
9use super::Estimate;
10
11/// A [`Estimate`]or of a height for a [`Grid`].
12///
13/// [`Grid`]: crate::Grid
14#[derive(Debug, Default, Clone, PartialEq, Eq)]
15pub struct HeightEstimator {
16    heights: Vec<usize>,
17}
18
19impl<R> Estimate<R> for HeightEstimator
20where
21    R: Records,
22{
23    fn estimate(&mut self, records: R, cfg: &GridConfig) {
24        self.heights = build_heights(&records, cfg);
25    }
26
27    fn get(&self, column: usize) -> Option<usize> {
28        self.heights.get(column).copied()
29    }
30
31    fn total(&self) -> usize {
32        self.heights.iter().sum()
33    }
34}
35
36impl From<Vec<usize>> for HeightEstimator {
37    fn from(heights: Vec<usize>) -> Self {
38        Self { heights }
39    }
40}
41
42impl From<HeightEstimator> for Vec<usize> {
43    fn from(val: HeightEstimator) -> Self {
44        val.heights
45    }
46}
47
48fn build_heights<R>(records: &R, cfg: &GridConfig) -> Vec<usize>
49where
50    R: Records,
51{
52    let shape = (records.count_rows(), records.count_columns());
53    let mut heights = vec![0; records.count_rows()];
54    for (row, height) in heights.iter_mut().enumerate() {
55        let max = (0..records.count_columns())
56            .filter(|&col| is_simple_cell(cfg, (row, col), shape))
57            .map(|col| cell_height(records, cfg, (row, col)))
58            .max()
59            .unwrap_or(0);
60
61        *height = max;
62    }
63
64    adjust_spans(records, cfg, &mut heights);
65
66    heights
67}
68
69fn adjust_spans<R>(records: &R, cfg: &GridConfig, heights: &mut [usize])
70where
71    R: Records,
72{
73    if !cfg.has_row_spans() {
74        return;
75    }
76
77    // The overall height disctribution will be different depend on the order.
78    //
79    // We sort spans in order to prioritize the smaller spans first.
80    let mut spans = cfg
81        .iter_row_spans((records.count_rows(), records.count_columns()))
82        .collect::<Vec<_>>();
83    spans.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) {
84        Ordering::Equal => acol.cmp(bcol),
85        ord => ord,
86    });
87
88    // todo: the order is matter here; we need to figure out what is correct.
89    for ((row, col), span) in spans {
90        adjust_range(records, cfg, col, row, row + span, heights);
91    }
92}
93
94fn adjust_range<R>(
95    records: &R,
96    cfg: &GridConfig,
97    col: usize,
98    start: usize,
99    end: usize,
100    heights: &mut [usize],
101) where
102    R: Records,
103{
104    let max_span_height = cell_height(records, cfg, (start, col));
105    let range_height = range_height(cfg, start, end, heights);
106
107    if range_height >= max_span_height {
108        return;
109    }
110
111    inc_range_height(heights, max_span_height - range_height, start, end);
112}
113
114fn is_simple_cell(cfg: &GridConfig, pos: Position, shape: (usize, usize)) -> bool {
115    cfg.is_cell_visible(pos, shape) && matches!(cfg.get_row_span(pos, shape), None | Some(1))
116}
117
118fn range_height(grid: &GridConfig, start: usize, end: usize, heights: &[usize]) -> usize {
119    let count_borders = count_borders_in_range(grid, start, end, heights.len());
120    let range_height = heights[start..end].iter().sum::<usize>();
121    count_borders + range_height
122}
123
124fn count_borders_in_range(cfg: &GridConfig, start: usize, end: usize, count_rows: usize) -> usize {
125    (start..end)
126        .skip(1)
127        .filter(|&i| cfg.has_horizontal(i, count_rows))
128        .count()
129}
130
131fn cell_height<R>(records: &R, cfg: &GridConfig, pos: Position) -> usize
132where
133    R: Records,
134{
135    let count_lines = max(1, records.count_lines(pos));
136    let padding = cfg.get_padding(Entity::Cell(pos.0, pos.1));
137    count_lines + padding.top.size + padding.bottom.size
138}
139
140fn inc_range_height(heights: &mut [usize], size: usize, start: usize, end: usize) {
141    if heights.is_empty() {
142        return;
143    }
144
145    let span = end - start;
146    let one = size / span;
147    let rest = size - span * one;
148
149    let mut i = start;
150    while i < end {
151        if i == start {
152            heights[i] += one + rest;
153        } else {
154            heights[i] += one;
155        }
156
157        i += 1;
158    }
159}