Coverage Report

Created: 2026-09-04 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}