papergrid/grid/
iterable.rs

1//! The module contains a [`IterGrid`] structure.
2
3use std::{
4    borrow::{Borrow, Cow},
5    cmp,
6    collections::BTreeMap,
7    fmt::{self, Write},
8};
9
10use crate::{
11    ansi::{ANSIBuf, ANSIFmt},
12    colors::Colors,
13    config::{
14        spanned::SpannedConfig, AlignmentHorizontal, AlignmentVertical, Formatting, Indent, Offset,
15        Position, Sides,
16    },
17    dimension::Dimension,
18    records::{IntoRecords, Records},
19    util::string::{count_lines, get_line_width, get_lines, get_text_width, Lines},
20};
21
22/// Grid provides a set of methods for building a text-based table.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
24pub struct IterGrid<R, D, G, C> {
25    records: R,
26    config: G,
27    dimension: D,
28    colors: C,
29}
30
31impl<R, D, G, C> IterGrid<R, D, G, C> {
32    /// The new method creates a grid instance with default styles.
33    pub fn new(records: R, config: G, dimension: D, colors: C) -> Self {
34        IterGrid {
35            records,
36            config,
37            dimension,
38            colors,
39        }
40    }
41
42    /// Builds a table.
43    pub fn build<F>(self, mut f: F) -> fmt::Result
44    where
45        R: Records,
46        <R::Iter as IntoRecords>::Cell: AsRef<str>,
47        D: Dimension,
48        C: Colors,
49        G: Borrow<SpannedConfig>,
50        F: Write,
51    {
52        if self.records.count_columns() == 0 || self.records.hint_count_rows() == Some(0) {
53            return Ok(());
54        }
55
56        let config = self.config.borrow();
57        let ctx = GridCtx {
58            cfg: config,
59            colors: &self.colors,
60            dims: &self.dimension,
61        };
62
63        print_grid(&mut f, self.records, &ctx)
64    }
65
66    /// Builds a table into string.
67    ///
68    /// Notice that it consumes self.
69    #[allow(clippy::inherent_to_string)]
70    pub fn to_string(self) -> String
71    where
72        R: Records,
73        <R::Iter as IntoRecords>::Cell: AsRef<str>,
74        D: Dimension,
75        G: Borrow<SpannedConfig>,
76        C: Colors,
77    {
78        let mut buf = String::new();
79        self.build(&mut buf).expect("It's guaranteed to never happen otherwise it's considered an stdlib error or impl error");
80        buf
81    }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85struct GridCtx<'a, D, C> {
86    cfg: &'a SpannedConfig,
87    colors: &'a C,
88    dims: &'a D,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92struct RowLine {
93    row: usize,
94    line: usize,
95}
96
97impl RowLine {
98    fn new(row: usize, line: usize) -> Self {
99        Self { row, line }
100    }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104struct Height {
105    value: usize,
106    total: Option<usize>,
107}
108
109impl Height {
110    fn new(value: usize, total: Option<usize>) -> Self {
111        Self { value, total }
112    }
113}
114
115fn print_grid<F, R, D, C>(f: &mut F, records: R, ctx: &GridCtx<'_, D, C>) -> fmt::Result
116where
117    F: Write,
118    R: Records,
119    <R::Iter as IntoRecords>::Cell: AsRef<str>,
120    D: Dimension,
121    C: Colors,
122{
123    // spanned version is a bit more complex and 'supposedly' slower,
124    // because spans are considered to be not a general case we are having 2 versions
125    let grid_has_spans = ctx.cfg.has_column_spans() || ctx.cfg.has_row_spans();
126    if grid_has_spans {
127        print_grid_spanned(f, records, ctx)
128    } else {
129        print_grid_general(f, records, ctx)
130    }
131}
132
133fn print_grid_general<F, R, D, C>(f: &mut F, records: R, ctx: &GridCtx<'_, D, C>) -> fmt::Result
134where
135    F: Write,
136    R: Records,
137    <R::Iter as IntoRecords>::Cell: AsRef<str>,
138    D: Dimension,
139    C: Colors,
140{
141    let count_columns = records.count_columns();
142
143    let mut totalw = None;
144    let totalh = records
145        .hint_count_rows()
146        .map(|count_rows| total_height(ctx.cfg, ctx.dims, count_rows));
147
148    let mut records_iter = records.iter_rows().into_iter();
149    let mut next_columns = records_iter.next();
150
151    if next_columns.is_none() {
152        return Ok(());
153    }
154
155    if ctx.cfg.get_margin().top.size > 0 {
156        totalw = Some(output_width(ctx.cfg, ctx.dims, count_columns));
157
158        print_margin_top(f, ctx.cfg, totalw.unwrap())?;
159        f.write_char('\n')?;
160    }
161
162    let mut row = 0;
163    let mut line = 0;
164    let mut is_prev_row_skipped = false;
165    let mut buf = Vec::new();
166    while let Some(columns) = next_columns {
167        let columns = columns.into_iter();
168        next_columns = records_iter.next();
169        let is_last_row = next_columns.is_none();
170
171        let height = ctx.dims.get_height(row);
172        let count_rows = convert_count_rows(row, is_last_row);
173        let has_horizontal = ctx.cfg.has_horizontal(row, count_rows);
174        let shape = Position::new(count_rows, count_columns);
175        let rline = RowLine::new(row, line);
176
177        if row > 0 && !is_prev_row_skipped && (has_horizontal || height > 0) {
178            f.write_char('\n')?;
179        }
180
181        if has_horizontal {
182            print_horizontal_line(f, ctx, rline, shape, totalh)?;
183
184            line += 1;
185
186            if height > 0 {
187                f.write_char('\n')?;
188            }
189        }
190
191        if height == 1 {
192            print_single_line_columns(f, columns, ctx, rline, totalh, shape)?
193        } else if height > 0 {
194            buf.reserve(count_columns);
195
196            collect_columns(&mut buf, columns, ctx, row, height);
197            let height = Height::new(height, totalh);
198            print_columns_lines(f, &mut buf, ctx.cfg, rline, height)?;
199
200            buf.clear();
201        }
202
203        is_prev_row_skipped = height == 0 && !has_horizontal;
204        line += height;
205        row += 1;
206    }
207
208    if ctx.cfg.has_horizontal(row, row) {
209        f.write_char('\n')?;
210        let shape = Position::new(row, count_columns);
211        let rline = RowLine::new(row, line);
212        print_horizontal_line(f, ctx, rline, shape, totalh)?;
213    }
214
215    if ctx.cfg.get_margin().bottom.size > 0 {
216        let totalw = totalw.unwrap_or_else(|| output_width(ctx.cfg, ctx.dims, count_columns));
217
218        f.write_char('\n')?;
219        print_margin_bottom(f, ctx.cfg, totalw)?;
220    }
221
222    Ok(())
223}
224
225fn output_width<D>(cfg: &SpannedConfig, d: D, count_columns: usize) -> usize
226where
227    D: Dimension,
228{
229    let margin = cfg.get_margin();
230    total_width(cfg, &d, count_columns) + margin.left.size + margin.right.size
231}
232
233fn print_horizontal_line<F, D, C>(
234    f: &mut F,
235    ctx: &GridCtx<'_, D, C>,
236    rline: RowLine,
237    shape: Position,
238    totalh: Option<usize>,
239) -> fmt::Result
240where
241    F: Write,
242    D: Dimension,
243{
244    print_margin_left(f, ctx.cfg, rline.line, totalh)?;
245    print_split_line(f, ctx.cfg, ctx.dims, rline.row, shape)?;
246    print_margin_right(f, ctx.cfg, rline.line, totalh)?;
247    Ok(())
248}
249
250fn print_single_line_columns<F, I, D, C>(
251    f: &mut F,
252    iter: I,
253    ctx: &GridCtx<'_, D, C>,
254    rline: RowLine,
255    totalheight: Option<usize>,
256    shape: Position,
257) -> fmt::Result
258where
259    F: Write,
260    I: Iterator,
261    I::Item: AsRef<str>,
262    D: Dimension,
263    C: Colors,
264{
265    print_margin_left(f, ctx.cfg, rline.line, totalheight)?;
266
267    for (col, cell) in iter.enumerate() {
268        let pos = Position::new(rline.row, col);
269        let width = ctx.dims.get_width(col);
270        let color = ctx.colors.get_color(pos);
271        let text = cell.as_ref();
272        print_vertical_char(f, ctx.cfg, pos, 0, 1, shape.col)?;
273        print_single_line_column(f, text, ctx.cfg, width, color, pos)?;
274    }
275
276    let pos = Position::new(rline.row, shape.col);
277    print_vertical_char(f, ctx.cfg, pos, 0, 1, shape.col)?;
278    print_margin_right(f, ctx.cfg, rline.line, totalheight)?;
279
280    Ok(())
281}
282
283fn print_single_line_column<F, C>(
284    f: &mut F,
285    text: &str,
286    cfg: &SpannedConfig,
287    width: usize,
288    color: Option<&C>,
289    pos: Position,
290) -> fmt::Result
291where
292    F: Write,
293    C: ANSIFmt,
294{
295    let pad = cfg.get_padding(pos);
296    let pad_color = cfg.get_padding_color(pos);
297    let fmt = cfg.get_formatting(pos);
298    let space = cfg.get_justification(pos);
299    let space_color = cfg.get_justification_color(pos);
300
301    let (text, text_width) = if fmt.horizontal_trim && !text.is_empty() {
302        let text = string_trim(text);
303        let width = get_line_width(&text);
304
305        (text, width)
306    } else {
307        let text = Cow::Borrowed(text);
308        let width = get_text_width(&text);
309
310        (text, width)
311    };
312
313    let alignment = *cfg.get_alignment_horizontal(pos);
314    let available_width = width - pad.left.size - pad.right.size;
315    let (left, right) = calculate_indent(alignment, text_width, available_width);
316
317    print_padding(f, &pad.left, pad_color.left.as_ref())?;
318
319    print_indent(f, space, left, space_color)?;
320    print_text(f, &text, color)?;
321    print_indent(f, space, right, space_color)?;
322
323    print_padding(f, &pad.right, pad_color.right.as_ref())?;
324
325    Ok(())
326}
327
328fn print_columns_lines<T, F, C>(
329    f: &mut F,
330    buf: &mut [Cell<'_, T, C>],
331    cfg: &SpannedConfig,
332    rline: RowLine,
333    height: Height,
334) -> fmt::Result
335where
336    F: Write,
337    C: ANSIFmt,
338{
339    let count_columns = buf.len();
340
341    for i in 0..height.value {
342        let exact_line = rline.line + i;
343
344        print_margin_left(f, cfg, exact_line, height.total)?;
345
346        for (col, cell) in buf.iter_mut().enumerate() {
347            let pos = Position::new(rline.row, col);
348            print_vertical_char(f, cfg, pos, i, height.value, count_columns)?;
349            cell.display(f)?;
350        }
351
352        let pos = Position::new(rline.row, count_columns);
353        print_vertical_char(f, cfg, pos, i, height.value, count_columns)?;
354
355        print_margin_right(f, cfg, exact_line, height.total)?;
356
357        if i + 1 != height.value {
358            f.write_char('\n')?;
359        }
360    }
361
362    Ok(())
363}
364
365fn collect_columns<'a, I, D, C>(
366    buf: &mut Vec<Cell<'a, I::Item, &'a C::Color>>,
367    iter: I,
368    ctx: &GridCtx<'a, D, C>,
369    row: usize,
370    height: usize,
371) where
372    I: Iterator,
373    I::Item: AsRef<str>,
374    C: Colors,
375    D: Dimension,
376{
377    for (col, cell) in iter.enumerate() {
378        let pos = Position::new(row, col);
379        let width = ctx.dims.get_width(col);
380        let color = ctx.colors.get_color(pos);
381        let cell = Cell::new(cell, width, height, ctx.cfg, color, pos);
382        buf.push(cell);
383    }
384}
385
386fn print_split_line<F, D>(
387    f: &mut F,
388    cfg: &SpannedConfig,
389    dimension: &D,
390    row: usize,
391    shape: Position,
392) -> fmt::Result
393where
394    F: Write,
395    D: Dimension,
396{
397    let mut used_color = None;
398    print_vertical_intersection(f, cfg, (row, 0).into(), shape, &mut used_color)?;
399
400    for col in 0..shape.col {
401        let width = dimension.get_width(col);
402
403        // general case
404        if width > 0 {
405            let pos = (row, col).into();
406            let main = cfg.get_horizontal(pos, shape.row);
407            match main {
408                Some(c) => {
409                    let clr = cfg.get_horizontal_color(pos, shape.row);
410                    prepare_coloring(f, clr, &mut used_color)?;
411                    print_horizontal_border(f, cfg, pos, width, c, &used_color)?;
412                }
413                None => repeat_char(f, ' ', width)?,
414            }
415        }
416
417        print_vertical_intersection(f, cfg, (row, col + 1).into(), shape, &mut used_color)?;
418    }
419
420    if let Some(clr) = used_color.take() {
421        clr.fmt_ansi_suffix(f)?;
422    }
423
424    Ok(())
425}
426
427fn print_grid_spanned<F, R, D, C>(f: &mut F, records: R, ctx: &GridCtx<'_, D, C>) -> fmt::Result
428where
429    F: Write,
430    R: Records,
431    <R::Iter as IntoRecords>::Cell: AsRef<str>,
432    D: Dimension,
433    C: Colors,
434{
435    let count_columns = records.count_columns();
436
437    let total_width = total_width(ctx.cfg, ctx.dims, count_columns);
438    let margin = ctx.cfg.get_margin();
439    let total_width_with_margin = total_width + margin.left.size + margin.right.size;
440
441    let totalh = records
442        .hint_count_rows()
443        .map(|rows| total_height(ctx.cfg, ctx.dims, rows));
444
445    if margin.top.size > 0 {
446        print_margin_top(f, ctx.cfg, total_width_with_margin)?;
447        f.write_char('\n')?;
448    }
449
450    let mut buf = BTreeMap::new();
451
452    let mut records_iter = records.iter_rows().into_iter();
453    let mut next_columns = records_iter.next();
454
455    let mut need_new_line = false;
456    let mut line = 0;
457    let mut row = 0;
458    while let Some(columns) = next_columns {
459        let columns = columns.into_iter();
460        next_columns = records_iter.next();
461        let is_last_row = next_columns.is_none();
462
463        let height = ctx.dims.get_height(row);
464        let count_rows = convert_count_rows(row, is_last_row);
465        let shape = Position::new(count_rows, count_columns);
466
467        let has_horizontal = ctx.cfg.has_horizontal(row, count_rows);
468        if need_new_line && (has_horizontal || height > 0) {
469            f.write_char('\n')?;
470            need_new_line = false;
471        }
472
473        if has_horizontal {
474            print_margin_left(f, ctx.cfg, line, totalh)?;
475            print_split_line_spanned(f, &mut buf, ctx.cfg, ctx.dims, row, shape)?;
476            print_margin_right(f, ctx.cfg, line, totalh)?;
477
478            line += 1;
479
480            if height > 0 {
481                f.write_char('\n')?;
482            }
483        }
484
485        let rline = RowLine::new(row, line);
486        let height = Height::new(height, totalh);
487        print_spanned_columns(f, columns, &mut buf, ctx, rline, height, shape)?;
488
489        if has_horizontal || height.value > 0 {
490            need_new_line = true;
491        }
492
493        line += height.value;
494        row += 1;
495    }
496
497    if row > 0 {
498        if ctx.cfg.has_horizontal(row, row) {
499            f.write_char('\n')?;
500            let shape = Position::new(row, count_columns);
501            let rline = RowLine::new(row, line);
502            print_horizontal_line(f, ctx, rline, shape, totalh)?;
503        }
504
505        if margin.bottom.size > 0 {
506            f.write_char('\n')?;
507            print_margin_bottom(f, ctx.cfg, total_width_with_margin)?;
508        }
509    }
510
511    Ok(())
512}
513
514fn print_split_line_spanned<S, F, D, C>(
515    f: &mut F,
516    buf: &mut BTreeMap<usize, Cell<'_, S, C>>,
517    cfg: &SpannedConfig,
518    dimension: &D,
519    row: usize,
520    shape: Position,
521) -> fmt::Result
522where
523    F: Write,
524    D: Dimension,
525    C: ANSIFmt,
526{
527    let mut used_color = None;
528    print_vertical_intersection(f, cfg, (row, 0).into(), shape, &mut used_color)?;
529
530    for col in 0..shape.col {
531        let pos = (row, col).into();
532        if cfg.is_cell_covered_by_both_spans(pos) {
533            continue;
534        }
535
536        let width = dimension.get_width(col);
537        let mut col = col;
538        if cfg.is_cell_covered_by_row_span(pos) {
539            // means it's part of other a spanned cell
540            // so. we just need to use line from other cell.
541
542            prepare_coloring(f, None, &mut used_color)?;
543            let cell = buf.get_mut(&col).unwrap();
544            cell.display(f)?;
545
546            // We need to use a correct right split char.
547            let original_row = closest_visible_row(cfg, pos).unwrap();
548            if let Some(span) = cfg.get_column_span((original_row, col).into()) {
549                col += span - 1;
550            }
551        } else if width > 0 {
552            // general case
553            let main = cfg.get_horizontal(pos, shape.row);
554            match main {
555                Some(c) => {
556                    let clr = cfg.get_horizontal_color(pos, shape.row);
557                    prepare_coloring(f, clr, &mut used_color)?;
558                    print_horizontal_border(f, cfg, pos, width, c, &used_color)?;
559                }
560                None => repeat_char(f, ' ', width)?,
561            }
562        }
563
564        print_vertical_intersection(f, cfg, (row, col + 1).into(), shape, &mut used_color)?;
565    }
566
567    if let Some(clr) = used_color.take() {
568        clr.fmt_ansi_suffix(f)?;
569    }
570
571    Ok(())
572}
573
574fn print_vertical_intersection<'a, F>(
575    f: &mut F,
576    cfg: &'a SpannedConfig,
577    pos: Position,
578    shape: Position,
579    used_color: &mut Option<&'a ANSIBuf>,
580) -> fmt::Result
581where
582    F: fmt::Write,
583{
584    if !cfg.has_vertical(pos.col, shape.col) {
585        return Ok(());
586    }
587
588    match cfg.get_intersection(pos, shape.into()) {
589        Some(c) => {
590            let clr = cfg.get_intersection_color(pos, shape.into());
591            prepare_coloring(f, clr, used_color)?;
592            f.write_char(c)
593        }
594        None => Ok(()),
595    }
596}
597
598fn print_spanned_columns<'a, F, I, D, C>(
599    f: &mut F,
600    iter: I,
601    buf: &mut BTreeMap<usize, Cell<'a, I::Item, &'a C::Color>>,
602    ctx: &GridCtx<'a, D, C>,
603    rline: RowLine,
604    height: Height,
605    shape: Position,
606) -> fmt::Result
607where
608    F: Write,
609    I: Iterator,
610    I::Item: AsRef<str>,
611    D: Dimension,
612    C: Colors,
613{
614    if height.value == 0 {
615        // it's possible that we dont show row but it contains an actual cell which will be
616        // rendered after all cause it's a rowspanned
617
618        let mut skip = 0;
619        for (col, cell) in iter.enumerate() {
620            if skip > 0 {
621                skip -= 1;
622                continue;
623            }
624
625            if let Some(cell) = buf.get(&col) {
626                skip = cell.colspan - 1;
627                continue;
628            }
629
630            let pos = Position::new(rline.row, col);
631            let rowspan = ctx.cfg.get_row_span(pos).unwrap_or(1);
632            if rowspan < 2 {
633                continue;
634            }
635
636            // FIXME: Do we need to recalcalate it?
637            // Is height not enough?
638            let height = if rowspan > 1 {
639                range_height(ctx.cfg, ctx.dims, rline.row, rline.row + rowspan, shape.row)
640            } else {
641                height.value
642            };
643
644            let colspan = ctx.cfg.get_column_span(pos).unwrap_or(1);
645            skip = colspan - 1;
646            let width = if colspan > 1 {
647                range_width(ctx.cfg, ctx.dims, col, col + colspan, shape.col)
648            } else {
649                ctx.dims.get_width(col)
650            };
651
652            let color = ctx.colors.get_color(pos);
653            let mut cell = Cell::new(cell, width, height, ctx.cfg, color, pos);
654            cell.rowspan = rowspan;
655            cell.colspan = colspan;
656
657            buf.insert(col, cell);
658        }
659
660        buf.retain(|_, cell| {
661            cell.rowspan -= 1;
662            cell.rowspan != 0
663        });
664
665        return Ok(());
666    }
667
668    let mut skip = 0;
669    for (col, cell) in iter.enumerate() {
670        if skip > 0 {
671            skip -= 1;
672            continue;
673        }
674
675        if let Some(cell) = buf.get(&col) {
676            skip = cell.colspan - 1;
677            continue;
678        }
679
680        let pos = Position::new(rline.row, col);
681        let colspan = ctx.cfg.get_column_span(pos).unwrap_or(1);
682        skip = colspan - 1;
683
684        let width = if colspan > 1 {
685            range_width(ctx.cfg, ctx.dims, col, col + colspan, shape.col)
686        } else {
687            ctx.dims.get_width(col)
688        };
689
690        let rowspan = ctx.cfg.get_row_span(pos).unwrap_or(1);
691        let height = if rowspan > 1 {
692            range_height(ctx.cfg, ctx.dims, rline.row, rline.row + rowspan, shape.row)
693        } else {
694            height.value
695        };
696
697        let color = ctx.colors.get_color(pos);
698        let mut cell = Cell::new(cell, width, height, ctx.cfg, color, pos);
699        cell.rowspan = rowspan;
700        cell.colspan = colspan;
701
702        buf.insert(col, cell);
703    }
704
705    for i in 0..height.value {
706        let exact_line = rline.line + i;
707        let cell_line = i;
708
709        print_margin_left(f, ctx.cfg, exact_line, height.total)?;
710
711        for (&col, cell) in buf.iter_mut() {
712            let pos = Position::new(rline.row, col);
713            print_vertical_char(f, ctx.cfg, pos, cell_line, height.value, shape.col)?;
714            cell.display(f)?;
715        }
716
717        let pos = Position::new(rline.row, shape.col);
718        print_vertical_char(f, ctx.cfg, pos, cell_line, height.value, shape.col)?;
719
720        print_margin_right(f, ctx.cfg, exact_line, height.total)?;
721
722        if i + 1 != height.value {
723            f.write_char('\n')?;
724        }
725    }
726
727    buf.retain(|_, cell| {
728        cell.rowspan -= 1;
729        cell.rowspan != 0
730    });
731
732    Ok(())
733}
734
735fn print_horizontal_border<F>(
736    f: &mut F,
737    cfg: &SpannedConfig,
738    pos: Position,
739    width: usize,
740    c: char,
741    used_color: &Option<&ANSIBuf>,
742) -> fmt::Result
743where
744    F: Write,
745{
746    if !cfg.is_overridden_horizontal(pos) {
747        return repeat_char(f, c, width);
748    }
749
750    for i in 0..width {
751        let c = cfg.lookup_horizontal_char(pos, i, width).unwrap_or(c);
752        match cfg.lookup_horizontal_color(pos, i, width) {
753            Some(color) => match used_color {
754                Some(clr) => {
755                    clr.fmt_ansi_suffix(f)?;
756                    color.fmt_ansi_prefix(f)?;
757                    f.write_char(c)?;
758                    color.fmt_ansi_suffix(f)?;
759                    clr.fmt_ansi_prefix(f)?;
760                }
761                None => {
762                    color.fmt_ansi_prefix(f)?;
763                    f.write_char(c)?;
764                    color.fmt_ansi_suffix(f)?;
765                }
766            },
767            _ => f.write_char(c)?,
768        }
769    }
770
771    Ok(())
772}
773
774struct Cell<'a, T, C> {
775    lines: LinesIter<T>,
776    width: usize,
777    indent_top: usize,
778    indent_left: Option<usize>,
779    alignh: AlignmentHorizontal,
780    fmt: Formatting,
781    pad: &'a Sides<Indent>,
782    pad_color: &'a Sides<Option<ANSIBuf>>,
783    color: Option<C>,
784    justification: char,
785    justification_color: Option<&'a ANSIBuf>,
786    rowspan: usize,
787    colspan: usize,
788}
789
790impl<'a, T, C> Cell<'a, T, C> {
791    fn new(
792        text: T,
793        width: usize,
794        height: usize,
795        cfg: &'a SpannedConfig,
796        color: Option<C>,
797        pos: Position,
798    ) -> Cell<'a, T, C>
799    where
800        T: AsRef<str>,
801    {
802        let fmt = cfg.get_formatting(pos);
803        let pad = cfg.get_padding(pos);
804        let pad_color = cfg.get_padding_color(pos);
805        let alignh = *cfg.get_alignment_horizontal(pos);
806        let alignv = *cfg.get_alignment_vertical(pos);
807        let justification = cfg.get_justification(pos);
808        let justification_color = cfg.get_justification_color(pos);
809
810        let (count_lines, skip) = if fmt.vertical_trim {
811            let (len, top, _) = count_empty_lines(text.as_ref());
812            (len, top)
813        } else {
814            (count_lines(text.as_ref()), 0)
815        };
816
817        let indent_top = top_indent(pad, alignv, count_lines, height);
818
819        let mut indent_left = None;
820        if !fmt.allow_lines_alignment {
821            let text_width = text_width(text.as_ref(), fmt.horizontal_trim);
822            let available = width - pad.left.size - pad.right.size;
823            indent_left = Some(calculate_indent(alignh, text_width, available).0);
824        }
825
826        let mut lines = LinesIter::new(text);
827        for _ in 0..skip {
828            let _ = lines.lines.next();
829        }
830
831        Self {
832            lines,
833            indent_left,
834            indent_top,
835            width,
836            alignh,
837            fmt,
838            pad,
839            pad_color,
840            color,
841            justification,
842            justification_color,
843            colspan: 0,
844            rowspan: 0,
845        }
846    }
847
848    fn display<F>(&mut self, f: &mut F) -> fmt::Result
849    where
850        F: Write,
851        C: ANSIFmt,
852    {
853        if self.indent_top > 0 {
854            self.indent_top -= 1;
855            print_padding_n(f, &self.pad.top, self.pad_color.top.as_ref(), self.width)?;
856            return Ok(());
857        }
858
859        let line = match self.lines.lines.next() {
860            Some(line) => line,
861            None => {
862                let color = self.pad_color.bottom.as_ref();
863                print_padding_n(f, &self.pad.bottom, color, self.width)?;
864                return Ok(());
865            }
866        };
867
868        let line = if self.fmt.horizontal_trim && !line.is_empty() {
869            string_trim(&line)
870        } else {
871            line
872        };
873
874        let line_width = get_line_width(&line);
875        let available_width = self.width - self.pad.left.size - self.pad.right.size;
876
877        let (left, right) = if self.fmt.allow_lines_alignment {
878            calculate_indent(self.alignh, line_width, available_width)
879        } else {
880            let left = self.indent_left.expect("must be here");
881            (left, available_width - line_width - left)
882        };
883
884        print_padding(f, &self.pad.left, self.pad_color.left.as_ref())?;
885
886        print_indent(f, self.justification, left, self.justification_color)?;
887        print_text(f, &line, self.color.as_ref())?;
888        print_indent(f, self.justification, right, self.justification_color)?;
889
890        print_padding(f, &self.pad.right, self.pad_color.right.as_ref())?;
891
892        Ok(())
893    }
894}
895
896struct LinesIter<C> {
897    _cell: C,
898    /// SAFETY: IT'S NOT SAFE TO KEEP THE 'static REFERENCES AROUND AS THEY ARE NOT 'static in reality AND WILL BE DROPPED
899    _text: &'static str,
900    /// SAFETY: IT'S NOT SAFE TO KEEP THE 'static REFERENCES AROUND AS THEY ARE NOT 'static in reality AND WILL BE DROPPED
901    lines: Lines<'static>,
902}
903
904impl<C> LinesIter<C> {
905    fn new(cell: C) -> Self
906    where
907        C: AsRef<str>,
908    {
909        // We want to not allocate a String/Vec.
910        // It's currently not possible due to a lifetime issues. (It's known as self-referential struct)
911        //
912        // Here we change the lifetime of text.
913        //
914        // # Safety
915        //
916        // It must be safe because the referenced string and the references are dropped at the same time.
917        // And the referenced String is guaranteed to not be changed.
918        let text = cell.as_ref();
919        let text = unsafe {
920            std::str::from_utf8_unchecked(std::slice::from_raw_parts(text.as_ptr(), text.len()))
921        };
922
923        let lines = get_lines(text);
924
925        Self {
926            _cell: cell,
927            _text: text,
928            lines,
929        }
930    }
931}
932
933fn print_text<F>(f: &mut F, text: &str, clr: Option<impl ANSIFmt>) -> fmt::Result
934where
935    F: Write,
936{
937    match clr {
938        Some(color) => {
939            color.fmt_ansi_prefix(f)?;
940            f.write_str(text)?;
941            color.fmt_ansi_suffix(f)
942        }
943        None => f.write_str(text),
944    }
945}
946
947fn prepare_coloring<'a, F>(
948    f: &mut F,
949    clr: Option<&'a ANSIBuf>,
950    used_color: &mut Option<&'a ANSIBuf>,
951) -> fmt::Result
952where
953    F: Write,
954{
955    match clr {
956        Some(clr) => match used_color.as_mut() {
957            Some(used_clr) => {
958                if **used_clr != *clr {
959                    used_clr.fmt_ansi_suffix(f)?;
960                    clr.fmt_ansi_prefix(f)?;
961                    *used_clr = clr;
962                }
963            }
964            None => {
965                clr.fmt_ansi_prefix(f)?;
966                *used_color = Some(clr);
967            }
968        },
969        None => {
970            if let Some(clr) = used_color.take() {
971                clr.fmt_ansi_suffix(f)?
972            }
973        }
974    }
975
976    Ok(())
977}
978
979fn top_indent(
980    padding: &Sides<Indent>,
981    alignment: AlignmentVertical,
982    cell_height: usize,
983    available: usize,
984) -> usize {
985    let height = available - padding.top.size;
986    let indent = indent_from_top(alignment, height, cell_height);
987
988    indent + padding.top.size
989}
990
991fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize {
992    match alignment {
993        AlignmentVertical::Top => 0,
994        AlignmentVertical::Bottom => available - real,
995        AlignmentVertical::Center => (available - real) / 2,
996    }
997}
998
999fn calculate_indent(alignment: AlignmentHorizontal, got: usize, max: usize) -> (usize, usize) {
1000    let diff = max - got;
1001    match alignment {
1002        AlignmentHorizontal::Left => (0, diff),
1003        AlignmentHorizontal::Right => (diff, 0),
1004        AlignmentHorizontal::Center => {
1005            let left = diff / 2;
1006            let rest = diff - left;
1007            (left, rest)
1008        }
1009    }
1010}
1011
1012fn repeat_char<F>(f: &mut F, c: char, n: usize) -> fmt::Result
1013where
1014    F: Write,
1015{
1016    for _ in 0..n {
1017        f.write_char(c)?;
1018    }
1019
1020    Ok(())
1021}
1022
1023fn print_vertical_char<F>(
1024    f: &mut F,
1025    cfg: &SpannedConfig,
1026    pos: Position,
1027    line: usize,
1028    count_lines: usize,
1029    count_columns: usize,
1030) -> fmt::Result
1031where
1032    F: Write,
1033{
1034    let symbol = match cfg.get_vertical(pos, count_columns) {
1035        Some(c) => c,
1036        None => return Ok(()),
1037    };
1038
1039    let symbol = cfg
1040        .lookup_vertical_char(pos, line, count_lines)
1041        .unwrap_or(symbol);
1042
1043    let color = cfg
1044        .get_vertical_color(pos, count_columns)
1045        .or_else(|| cfg.lookup_vertical_color(pos, line, count_lines));
1046
1047    match color {
1048        Some(clr) => {
1049            clr.fmt_ansi_prefix(f)?;
1050            f.write_char(symbol)?;
1051            clr.fmt_ansi_suffix(f)?;
1052        }
1053        None => f.write_char(symbol)?,
1054    }
1055
1056    Ok(())
1057}
1058
1059fn print_margin_top<F>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result
1060where
1061    F: Write,
1062{
1063    let indent = cfg.get_margin().top;
1064    let offset = cfg.get_margin_offset().top;
1065    let color = cfg.get_margin_color();
1066    let color = color.top;
1067    print_indent_lines(f, &indent, &offset, color, width)
1068}
1069
1070fn print_margin_bottom<F>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result
1071where
1072    F: Write,
1073{
1074    let indent = cfg.get_margin().bottom;
1075    let offset = cfg.get_margin_offset().bottom;
1076    let color = cfg.get_margin_color();
1077    let color = color.bottom;
1078    print_indent_lines(f, &indent, &offset, color, width)
1079}
1080
1081fn print_margin_left<F>(
1082    f: &mut F,
1083    cfg: &SpannedConfig,
1084    line: usize,
1085    height: Option<usize>,
1086) -> fmt::Result
1087where
1088    F: Write,
1089{
1090    let indent = cfg.get_margin().left;
1091    let offset = cfg.get_margin_offset().left;
1092    let color = cfg.get_margin_color();
1093    let color = color.left;
1094    print_margin_vertical(f, indent, offset, color, line, height)
1095}
1096
1097fn print_margin_right<F>(
1098    f: &mut F,
1099    cfg: &SpannedConfig,
1100    line: usize,
1101    height: Option<usize>,
1102) -> fmt::Result
1103where
1104    F: Write,
1105{
1106    let indent = cfg.get_margin().right;
1107    let offset = cfg.get_margin_offset().right;
1108    let color = cfg.get_margin_color();
1109    let color = color.right;
1110    print_margin_vertical(f, indent, offset, color, line, height)
1111}
1112
1113fn print_margin_vertical<F>(
1114    f: &mut F,
1115    indent: Indent,
1116    offset: Offset,
1117    color: Option<&ANSIBuf>,
1118    line: usize,
1119    height: Option<usize>,
1120) -> fmt::Result
1121where
1122    F: Write,
1123{
1124    if indent.size == 0 {
1125        return Ok(());
1126    }
1127
1128    match offset {
1129        Offset::Start(mut offset) => {
1130            if let Some(max) = height {
1131                offset = cmp::min(offset, max);
1132            }
1133
1134            if line >= offset {
1135                print_indent(f, indent.fill, indent.size, color)?;
1136            } else {
1137                repeat_char(f, ' ', indent.size)?;
1138            }
1139        }
1140        Offset::End(mut offset) => {
1141            if let Some(max) = height {
1142                offset = cmp::min(offset, max);
1143                let pos = max - offset;
1144
1145                if line >= pos {
1146                    repeat_char(f, ' ', indent.size)?;
1147                } else {
1148                    print_indent(f, indent.fill, indent.size, color)?;
1149                }
1150            } else {
1151                print_indent(f, indent.fill, indent.size, color)?;
1152            }
1153        }
1154    }
1155
1156    Ok(())
1157}
1158
1159fn print_indent_lines<F>(
1160    f: &mut F,
1161    indent: &Indent,
1162    offset: &Offset,
1163    color: Option<&ANSIBuf>,
1164    width: usize,
1165) -> fmt::Result
1166where
1167    F: Write,
1168{
1169    if indent.size == 0 {
1170        return Ok(());
1171    }
1172
1173    let (start_offset, end_offset) = match offset {
1174        Offset::Start(start) => (*start, 0),
1175        Offset::End(end) => (0, *end),
1176    };
1177
1178    let start_offset = std::cmp::min(start_offset, width);
1179    let end_offset = std::cmp::min(end_offset, width);
1180    let indent_size = width - start_offset - end_offset;
1181
1182    for i in 0..indent.size {
1183        if start_offset > 0 {
1184            repeat_char(f, ' ', start_offset)?;
1185        }
1186
1187        if indent_size > 0 {
1188            print_indent(f, indent.fill, indent_size, color)?;
1189        }
1190
1191        if end_offset > 0 {
1192            repeat_char(f, ' ', end_offset)?;
1193        }
1194
1195        if i + 1 != indent.size {
1196            f.write_char('\n')?;
1197        }
1198    }
1199
1200    Ok(())
1201}
1202
1203fn print_padding<F>(f: &mut F, pad: &Indent, color: Option<&ANSIBuf>) -> fmt::Result
1204where
1205    F: Write,
1206{
1207    print_indent(f, pad.fill, pad.size, color)
1208}
1209
1210fn print_padding_n<F>(f: &mut F, pad: &Indent, color: Option<&ANSIBuf>, n: usize) -> fmt::Result
1211where
1212    F: Write,
1213{
1214    print_indent(f, pad.fill, n, color)
1215}
1216
1217fn print_indent<F>(f: &mut F, c: char, n: usize, color: Option<&ANSIBuf>) -> fmt::Result
1218where
1219    F: Write,
1220{
1221    if n == 0 {
1222        return Ok(());
1223    }
1224
1225    match color {
1226        Some(color) => {
1227            color.fmt_ansi_prefix(f)?;
1228            repeat_char(f, c, n)?;
1229            color.fmt_ansi_suffix(f)
1230        }
1231        None => repeat_char(f, c, n),
1232    }
1233}
1234
1235fn range_width<D>(cfg: &SpannedConfig, dims: D, start: usize, end: usize, max: usize) -> usize
1236where
1237    D: Dimension,
1238{
1239    let count_borders = count_verticals_in_range(cfg, start, end, max);
1240    let range_width = (start..end).map(|col| dims.get_width(col)).sum::<usize>();
1241
1242    count_borders + range_width
1243}
1244
1245fn range_height<D>(cfg: &SpannedConfig, dims: D, from: usize, end: usize, max: usize) -> usize
1246where
1247    D: Dimension,
1248{
1249    let count_borders = count_horizontals_in_range(cfg, from, end, max);
1250    let range_width = (from..end).map(|col| dims.get_height(col)).sum::<usize>();
1251
1252    count_borders + range_width
1253}
1254
1255fn count_horizontals_in_range(cfg: &SpannedConfig, from: usize, end: usize, max: usize) -> usize {
1256    (from + 1..end)
1257        .map(|i| cfg.has_horizontal(i, max) as usize)
1258        .sum()
1259}
1260
1261fn count_verticals_in_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize {
1262    (start..end)
1263        .skip(1)
1264        .map(|i| cfg.has_vertical(i, max) as usize)
1265        .sum()
1266}
1267
1268fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> {
1269    loop {
1270        if cfg.is_cell_visible(pos) {
1271            return Some(pos.row);
1272        }
1273
1274        if pos.row == 0 {
1275            return None;
1276        }
1277
1278        pos -= (1, 0);
1279    }
1280}
1281
1282fn convert_count_rows(row: usize, is_last: bool) -> usize {
1283    if is_last {
1284        row + 1
1285    } else {
1286        row + 2
1287    }
1288}
1289
1290/// Trims a string.
1291fn string_trim(text: &str) -> Cow<'_, str> {
1292    #[cfg(feature = "ansi")]
1293    {
1294        ansi_str::AnsiStr::ansi_trim(text)
1295    }
1296
1297    #[cfg(not(feature = "ansi"))]
1298    {
1299        text.trim().into()
1300    }
1301}
1302
1303fn total_width<D>(cfg: &SpannedConfig, dimension: &D, count_columns: usize) -> usize
1304where
1305    D: Dimension,
1306{
1307    (0..count_columns)
1308        .map(|i| dimension.get_width(i))
1309        .sum::<usize>()
1310        + cfg.count_vertical(count_columns)
1311}
1312
1313fn total_height<D>(cfg: &SpannedConfig, dimension: &D, count_rows: usize) -> usize
1314where
1315    D: Dimension,
1316{
1317    (0..count_rows)
1318        .map(|i| dimension.get_height(i))
1319        .sum::<usize>()
1320        + cfg.count_horizontal(count_rows)
1321}
1322
1323fn count_empty_lines(cell: &str) -> (usize, usize, usize) {
1324    let mut len = 0;
1325    let mut top = 0;
1326    let mut bottom = 0;
1327    let mut top_check = true;
1328
1329    for line in get_lines(cell) {
1330        let is_empty = line.trim().is_empty();
1331        if top_check {
1332            if is_empty {
1333                top += 1;
1334            } else {
1335                len = 1;
1336                top_check = false;
1337            }
1338
1339            continue;
1340        }
1341
1342        if is_empty {
1343            bottom += 1;
1344        } else {
1345            len += bottom + 1;
1346            bottom = 0;
1347        }
1348    }
1349
1350    (len, top, bottom)
1351}
1352
1353fn text_width(text: &str, trim: bool) -> usize {
1354    if trim {
1355        get_lines(text)
1356            .map(|line| get_line_width(line.trim()))
1357            .max()
1358            .unwrap_or(0)
1359    } else {
1360        get_text_width(text)
1361    }
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366    use super::*;
1367
1368    #[test]
1369    fn vertical_alignment_test() {
1370        use AlignmentVertical::*;
1371
1372        assert_eq!(indent_from_top(Bottom, 1, 1), 0);
1373        assert_eq!(indent_from_top(Top, 1, 1), 0);
1374        assert_eq!(indent_from_top(Center, 1, 1), 0);
1375        assert_eq!(indent_from_top(Bottom, 3, 1), 2);
1376        assert_eq!(indent_from_top(Top, 3, 1), 0);
1377        assert_eq!(indent_from_top(Center, 3, 1), 1);
1378        assert_eq!(indent_from_top(Center, 4, 1), 1);
1379    }
1380
1381    #[test]
1382    fn count_empty_lines_test() {
1383        assert_eq!(count_empty_lines("\n\nsome text\n\n\n"), (1, 2, 3));
1384        assert_eq!(count_empty_lines("\n\nsome\ntext\n\n\n"), (2, 2, 3));
1385        assert_eq!(count_empty_lines("\n\nsome\nsome\ntext\n\n\n"), (3, 2, 3));
1386        assert_eq!(count_empty_lines("\n\n\n\n"), (0, 5, 0));
1387    }
1388}