papergrid/estimation/
height.rs
1use std::cmp::{max, Ordering};
6
7use crate::{records::Records, Entity, GridConfig, Position};
8
9use super::Estimate;
10
11#[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 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 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}