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