1use papergrid::records::vec_records::{CellInfo, VecRecords};
2
3use crate::grid::{
4 config::SpannedConfig,
5 dimension::{Dimension, Estimate},
6 records::Records,
7};
8
9/// PeekableDimension is a [`Dimension`] implementation for a [`Table`]
10///
11/// [`Table`]: crate::Table
12#[derive(Debug, Default, Clone)]
13pub struct PeekableDimension {
14 width: Vec<usize>,
15 height: Vec<usize>,
16}
17
18impl PeekableDimension {
19 /// Calculates height of rows.
20 pub fn height<T: AsRef<str>>(
21 records: &VecRecords<CellInfo<T>>,
22 cfg: &SpannedConfig,
23 ) -> Vec<usize> {
24 estimation::build_height(records, cfg)
25 }
26
27 /// Calculates width of columns.
28 pub fn width<T: AsRef<str>>(
29 records: &VecRecords<CellInfo<T>>,
30 cfg: &SpannedConfig,
31 ) -> Vec<usize> {
32 estimation::build_width(records, cfg)
33 }
34
35 /// Return width and height lists.
36 pub fn get_values(self) -> (Vec<usize>, Vec<usize>) {
37 (self.width, self.height)
38 }
39}
40
41impl Dimension for PeekableDimension {
42 fn get_width(&self, column: usize) -> usize {
43 self.width[column]
44 }
45
46 fn get_height(&self, row: usize) -> usize {
47 self.height[row]
48 }
49}
50
51impl<T> Estimate<&VecRecords<CellInfo<T>>, SpannedConfig> for PeekableDimension
52where
53 T: AsRef<str>,
54{
55 fn estimate(&mut self, records: &VecRecords<CellInfo<T>>, cfg: &SpannedConfig) {
56 let (width: Vec, height: Vec) = estimation::build_dimensions(records, cfg);
57 self.width = width;
58 self.height = height;
59 }
60}
61
62mod estimation {
63 use core::cmp::{max, Ordering};
64 use std::collections::HashMap;
65
66 use papergrid::{
67 config::Position,
68 records::vec_records::{Cell, CellInfo, VecRecords},
69 };
70
71 use super::*;
72
73 pub(super) fn build_dimensions<T: AsRef<str>>(
74 records: &VecRecords<CellInfo<T>>,
75 cfg: &SpannedConfig,
76 ) -> (Vec<usize>, Vec<usize>) {
77 let count_columns = records.count_columns();
78
79 let mut widths = vec![0; count_columns];
80 let mut heights = vec![];
81
82 let mut vspans = HashMap::new();
83 let mut hspans = HashMap::new();
84
85 for (row, columns) in records.iter_rows().enumerate() {
86 let mut row_height = 0;
87 for (col, cell) in columns.iter().enumerate() {
88 let pos = (row, col);
89 if !cfg.is_cell_visible(pos) {
90 continue;
91 }
92
93 let height = cell.count_lines();
94 let width = cell.width();
95
96 let pad = cfg.get_padding(pos.into());
97 let width = width + pad.left.size + pad.right.size;
98 let height = height + pad.top.size + pad.bottom.size;
99
100 match cfg.get_column_span(pos) {
101 Some(n) if n > 1 => {
102 let _ = vspans.insert(pos, (n, width));
103 }
104 _ => widths[col] = max(widths[col], width),
105 }
106
107 match cfg.get_row_span(pos) {
108 Some(n) if n > 1 => {
109 let _ = hspans.insert(pos, (n, height));
110 }
111 _ => row_height = max(row_height, height),
112 }
113 }
114
115 heights.push(row_height);
116 }
117
118 let count_rows = heights.len();
119
120 adjust_vspans(cfg, count_columns, &vspans, &mut widths);
121 adjust_hspans(cfg, count_rows, &hspans, &mut heights);
122
123 (widths, heights)
124 }
125
126 fn adjust_hspans(
127 cfg: &SpannedConfig,
128 len: usize,
129 spans: &HashMap<Position, (usize, usize)>,
130 heights: &mut [usize],
131 ) {
132 if spans.is_empty() {
133 return;
134 }
135
136 let mut spans_ordered = spans
137 .iter()
138 .map(|(k, v)| ((k.0, k.1), *v))
139 .collect::<Vec<_>>();
140 spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) {
141 Ordering::Equal => acol.cmp(bcol),
142 ord => ord,
143 });
144
145 for ((row, _), (span, height)) in spans_ordered {
146 adjust_row_range(cfg, height, len, row, row + span, heights);
147 }
148 }
149
150 fn adjust_row_range(
151 cfg: &SpannedConfig,
152 max_span_height: usize,
153 len: usize,
154 start: usize,
155 end: usize,
156 heights: &mut [usize],
157 ) {
158 let range_height = range_height(cfg, len, start, end, heights);
159 if range_height >= max_span_height {
160 return;
161 }
162
163 inc_range(heights, max_span_height - range_height, start, end);
164 }
165
166 fn range_height(
167 cfg: &SpannedConfig,
168 len: usize,
169 start: usize,
170 end: usize,
171 heights: &[usize],
172 ) -> usize {
173 let count_borders = count_horizontal_borders(cfg, len, start, end);
174 let range_height = heights[start..end].iter().sum::<usize>();
175 count_borders + range_height
176 }
177
178 fn count_horizontal_borders(
179 cfg: &SpannedConfig,
180 len: usize,
181 start: usize,
182 end: usize,
183 ) -> usize {
184 (start..end)
185 .skip(1)
186 .filter(|&i| cfg.has_horizontal(i, len))
187 .count()
188 }
189
190 fn inc_range(list: &mut [usize], size: usize, start: usize, end: usize) {
191 if list.is_empty() {
192 return;
193 }
194
195 let span = end - start;
196 let one = size / span;
197 let rest = size - span * one;
198
199 let mut i = start;
200 while i < end {
201 if i == start {
202 list[i] += one + rest;
203 } else {
204 list[i] += one;
205 }
206
207 i += 1;
208 }
209 }
210
211 fn adjust_vspans(
212 cfg: &SpannedConfig,
213 len: usize,
214 spans: &HashMap<Position, (usize, usize)>,
215 widths: &mut [usize],
216 ) {
217 if spans.is_empty() {
218 return;
219 }
220
221 // The overall width distribution will be different depend on the order.
222 //
223 // We sort spans in order to prioritize the smaller spans first.
224 let mut spans_ordered = spans
225 .iter()
226 .map(|(k, v)| ((k.0, k.1), *v))
227 .collect::<Vec<_>>();
228 spans_ordered.sort_unstable_by(|a, b| match a.1 .0.cmp(&b.1 .0) {
229 Ordering::Equal => a.0.cmp(&b.0),
230 o => o,
231 });
232
233 for ((_, col), (span, width)) in spans_ordered {
234 adjust_column_range(cfg, width, len, col, col + span, widths);
235 }
236 }
237
238 fn adjust_column_range(
239 cfg: &SpannedConfig,
240 max_span_width: usize,
241 len: usize,
242 start: usize,
243 end: usize,
244 widths: &mut [usize],
245 ) {
246 let range_width = range_width(cfg, len, start, end, widths);
247 if range_width >= max_span_width {
248 return;
249 }
250
251 inc_range(widths, max_span_width - range_width, start, end);
252 }
253
254 fn range_width(
255 cfg: &SpannedConfig,
256 len: usize,
257 start: usize,
258 end: usize,
259 widths: &[usize],
260 ) -> usize {
261 let count_borders = count_vertical_borders(cfg, len, start, end);
262 let range_width = widths[start..end].iter().sum::<usize>();
263 count_borders + range_width
264 }
265
266 fn count_vertical_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize {
267 (start..end)
268 .skip(1)
269 .filter(|&i| cfg.has_vertical(i, len))
270 .count()
271 }
272
273 pub(super) fn build_height<T: AsRef<str>>(
274 records: &VecRecords<CellInfo<T>>,
275 cfg: &SpannedConfig,
276 ) -> Vec<usize> {
277 let mut heights = vec![];
278 let mut hspans = HashMap::new();
279
280 for (row, columns) in records.iter_rows().enumerate() {
281 let mut row_height = 0;
282 for (col, cell) in columns.iter().enumerate() {
283 let pos = (row, col);
284 if !cfg.is_cell_visible(pos) {
285 continue;
286 }
287
288 let height = cell.count_lines();
289 match cfg.get_row_span(pos) {
290 Some(n) if n > 1 => {
291 let _ = hspans.insert(pos, (n, height));
292 }
293 _ => row_height = max(row_height, height),
294 }
295 }
296
297 heights.push(row_height);
298 }
299
300 adjust_hspans(cfg, heights.len(), &hspans, &mut heights);
301
302 heights
303 }
304
305 pub(super) fn build_width<T: AsRef<str>>(
306 records: &VecRecords<CellInfo<T>>,
307 cfg: &SpannedConfig,
308 ) -> Vec<usize> {
309 let count_columns = records.count_columns();
310
311 let mut widths = vec![0; count_columns];
312 let mut vspans = HashMap::new();
313
314 for (row, columns) in records.iter_rows().enumerate() {
315 for (col, cell) in columns.iter().enumerate() {
316 let pos = (row, col);
317 if !cfg.is_cell_visible(pos) {
318 continue;
319 }
320
321 let width = cell.width();
322 match cfg.get_column_span(pos) {
323 Some(n) if n > 1 => {
324 let _ = vspans.insert(pos, (n, width));
325 }
326 _ => widths[col] = max(widths[col], width),
327 }
328 }
329 }
330
331 adjust_vspans(cfg, count_columns, &vspans, &mut widths);
332
333 widths
334 }
335}
336