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