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