tabled/tables/
table_pool.rs

1use core::fmt::{self, Display, Formatter};
2
3use crate::{
4    grid::{
5        config::{AlignmentHorizontal, CompactMultilineConfig, Indent, Sides},
6        dimension::{DimensionPriority, PoolTableDimension},
7        records::EmptyRecords,
8        records::IntoRecords,
9    },
10    settings::{Style, TableOption},
11};
12
13/// [`PoolTable`] is a table which allows a greater set of possibilities for cell alignment.
14/// It's data is not aligned in any way by default.
15///
16/// It works similar to the main [`Table`] by default.
17///
18///
19/// ```
20/// use tabled::tables::PoolTable;
21///
22/// let data = vec![
23///     vec!["Hello", "World", "!"],
24///     vec!["Salve", "mondo", "!"],
25///     vec!["Hola", "mundo", "!"],
26/// ];
27///
28/// let table = PoolTable::new(data).to_string();
29///
30/// assert_eq!(
31///     table,
32///     "+-------+-------+---+\n\
33///      | Hello | World | ! |\n\
34///      +-------+-------+---+\n\
35///      | Salve | mondo | ! |\n\
36///      +-------+-------+---+\n\
37///      | Hola  | mundo | ! |\n\
38///      +-------+-------+---+"
39/// )
40/// ```
41///
42/// But it allows you to have a different number of columns inside the rows.
43///
44/// ```
45/// use tabled::tables::PoolTable;
46///
47/// let data = vec![
48///     vec!["Hello", "World", "!"],
49///     vec!["Salve, mondo!"],
50///     vec!["Hola", "mundo", "", "", "!"],
51/// ];
52///
53/// let table = PoolTable::new(data).to_string();
54///
55/// assert_eq!(
56///     table,
57///     "+---------+---------+----+\n\
58///      | Hello   | World   | !  |\n\
59///      +---------+---------+----+\n\
60///      | Salve, mondo!          |\n\
61///      +------+-------+--+--+---+\n\
62///      | Hola | mundo |  |  | ! |\n\
63///      +------+-------+--+--+---+"
64/// )
65/// ```
66///
67/// Notice that you also can build a custom table layout by using [`TableValue`].
68///
69/// ```
70/// use tabled::tables::{PoolTable, TableValue};
71///
72/// let message = "Hello\nWorld";
73///
74/// let data = TableValue::Column(vec![
75///     TableValue::Row(vec![
76///         TableValue::Column(vec![
77///             TableValue::Cell(String::from(message)),
78///         ]),
79///         TableValue::Column(vec![
80///             TableValue::Cell(String::from(message)),
81///             TableValue::Row(vec![
82///                 TableValue::Cell(String::from(message)),
83///                 TableValue::Cell(String::from(message)),
84///                 TableValue::Cell(String::from(message)),
85///             ])
86///         ]),
87///     ]),
88///     TableValue::Cell(String::from(message)),
89/// ]);
90///
91/// let table = PoolTable::from(data).to_string();
92///
93/// assert_eq!(
94///     table,
95///     "+-------+-----------------------+\n\
96///      | Hello | Hello                 |\n\
97///      | World | World                 |\n\
98///      |       +-------+-------+-------+\n\
99///      |       | Hello | Hello | Hello |\n\
100///      |       | World | World | World |\n\
101///      +-------+-------+-------+-------+\n\
102///      | Hello                         |\n\
103///      | World                         |\n\
104///      +-------------------------------+"
105/// )
106/// ```
107///
108/// [`Table`]: crate::Table
109#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
110pub struct PoolTable {
111    config: CompactMultilineConfig,
112    dims: PoolTableDimension,
113    value: TableValue,
114}
115
116impl PoolTable {
117    /// Creates a [`PoolTable`] out from a record iterator.
118    pub fn new<I>(iter: I) -> Self
119    where
120        I: IntoRecords,
121        I::Cell: AsRef<str>,
122    {
123        let value = TableValue::Column(
124            iter.iter_rows()
125                .into_iter()
126                .map(|row| {
127                    row.into_iter()
128                        .map(|cell| cell.as_ref().to_string())
129                        .map(TableValue::Cell)
130                        .collect()
131                })
132                .map(TableValue::Row)
133                .collect(),
134        );
135
136        Self {
137            config: configure_grid(),
138            dims: PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List),
139            value,
140        }
141    }
142
143    /// A is a generic function which applies options to the [`PoolTable`] configuration.
144    ///
145    /// Notice that it has a limited support of options.
146    ///
147    /// ```
148    /// use tabled::tables::PoolTable;
149    /// use tabled::settings::{Style, Padding};
150    ///
151    /// let data = vec![
152    ///     vec!["Hello", "World", "!"],
153    ///     vec!["Salve", "mondo", "!"],
154    ///     vec!["Hola", "mundo", "!"],
155    /// ];
156    ///
157    /// let table = PoolTable::new(data)
158    ///     .with(Style::extended())
159    ///     .with(Padding::zero())
160    ///     .to_string();
161    ///
162    /// assert_eq!(
163    ///     table,
164    ///     "╔═════╦═════╦═╗\n\
165    ///      ║Hello║World║!║\n\
166    ///      ╠═════╬═════╬═╣\n\
167    ///      ║Salve║mondo║!║\n\
168    ///      ╠═════╬═════╬═╣\n\
169    ///      ║Hola ║mundo║!║\n\
170    ///      ╚═════╩═════╩═╝"
171    /// )
172    /// ```
173    pub fn with<O>(&mut self, option: O) -> &mut Self
174    where
175        O: TableOption<EmptyRecords, CompactMultilineConfig, PoolTableDimension>,
176    {
177        let mut records = EmptyRecords::default();
178        option.change(&mut records, &mut self.config, &mut self.dims);
179
180        self
181    }
182}
183
184impl From<TableValue> for PoolTable {
185    fn from(value: TableValue) -> Self {
186        Self {
187            config: configure_grid(),
188            dims: PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List),
189            value,
190        }
191    }
192}
193
194impl Display for PoolTable {
195    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
196        print::build_table(&self.value, &self.config, self.dims).fmt(f)
197    }
198}
199
200/// [`TableValue`] a structure which is responsible for a [`PoolTable`] layout.
201#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
202pub enum TableValue {
203    /// A horizontal row.
204    Row(Vec<TableValue>),
205    /// A vertical column.
206    Column(Vec<TableValue>),
207    /// A single cell.
208    Cell(String),
209}
210
211fn configure_grid() -> CompactMultilineConfig {
212    let pad = Sides::new(
213        Indent::spaced(1),
214        Indent::spaced(1),
215        Indent::zero(),
216        Indent::zero(),
217    );
218
219    let mut cfg = CompactMultilineConfig::new();
220    cfg.set_padding(pad);
221    cfg.set_alignment_horizontal(AlignmentHorizontal::Left);
222    cfg.set_borders(Style::ascii().get_borders());
223
224    cfg
225}
226
227impl<R, C> TableOption<R, C, PoolTableDimension> for PoolTableDimension {
228    fn change(self, _: &mut R, _: &mut C, dimension: &mut PoolTableDimension) {
229        *dimension = self;
230    }
231}
232
233impl<R, D> TableOption<R, CompactMultilineConfig, D> for CompactMultilineConfig {
234    fn change(self, _: &mut R, config: &mut CompactMultilineConfig, _: &mut D) {
235        *config = self;
236    }
237}
238
239mod print {
240    use std::{cmp::max, collections::HashMap, iter::repeat_n};
241
242    use crate::{
243        builder::Builder,
244        grid::{
245            ansi::ANSIStr,
246            config::{
247                AlignmentHorizontal, AlignmentVertical, Border, Borders, CompactMultilineConfig,
248                Indent, Sides,
249            },
250            dimension::{DimensionPriority, PoolTableDimension},
251            util::string::{
252                count_lines, get_line_width, get_lines, get_text_dimension, get_text_width,
253            },
254        },
255        settings::{Padding, Style},
256    };
257
258    use super::TableValue;
259
260    #[derive(Debug, Default)]
261    struct PrintContext {
262        pos: usize,
263        is_last_col: bool,
264        is_last_row: bool,
265        is_first_col: bool,
266        is_first_row: bool,
267        kv: bool,
268        kv_is_first: bool,
269        list: bool,
270        list_is_first: bool,
271        no_left: bool,
272        no_right: bool,
273        no_bottom: bool,
274        lean_top: bool,
275        top_intersection: bool,
276        top_left: bool,
277        intersections_horizontal: Vec<usize>,
278        intersections_vertical: Vec<usize>,
279        size: Dim,
280    }
281
282    struct CellData {
283        content: String,
284        intersections_horizontal: Vec<usize>,
285        intersections_vertical: Vec<usize>,
286    }
287
288    impl CellData {
289        fn new(content: String, i_horizontal: Vec<usize>, i_vertical: Vec<usize>) -> Self {
290            Self {
291                content,
292                intersections_horizontal: i_horizontal,
293                intersections_vertical: i_vertical,
294            }
295        }
296    }
297
298    pub(super) fn build_table(
299        val: &TableValue,
300        cfg: &CompactMultilineConfig,
301        dims_priority: PoolTableDimension,
302    ) -> String {
303        let dims = collect_table_dimensions(val, cfg);
304        let ctx = PrintContext {
305            is_last_col: true,
306            is_last_row: true,
307            is_first_col: true,
308            is_first_row: true,
309            size: *dims.all.get(&0).unwrap(),
310            ..Default::default()
311        };
312
313        let data = _build_table(val, cfg, &dims, dims_priority, ctx);
314        let mut table = data.content;
315
316        let margin = cfg.get_margin();
317        let has_margin = margin.top.size > 0
318            || margin.bottom.size > 0
319            || margin.left.size > 0
320            || margin.right.size > 0;
321        if has_margin {
322            let color = convert_border_colors(*cfg.get_margin_color());
323            table = set_margin(&table, *margin, color);
324        }
325
326        table
327    }
328
329    fn _build_table(
330        val: &TableValue,
331        cfg: &CompactMultilineConfig,
332        dims: &Dimensions,
333        priority: PoolTableDimension,
334        ctx: PrintContext,
335    ) -> CellData {
336        match val {
337            TableValue::Cell(text) => generate_value_cell(text, cfg, ctx),
338            TableValue::Row(list) => {
339                if list.is_empty() {
340                    return generate_value_cell("", cfg, ctx);
341                }
342
343                generate_table_row(list, cfg, dims, priority, ctx)
344            }
345            TableValue::Column(list) => {
346                if list.is_empty() {
347                    return generate_value_cell("", cfg, ctx);
348                }
349
350                generate_table_column(list, cfg, dims, priority, ctx)
351            }
352        }
353    }
354
355    fn generate_table_column(
356        list: &[TableValue],
357        cfg: &CompactMultilineConfig,
358        dims: &Dimensions,
359        priority: PoolTableDimension,
360        ctx: PrintContext,
361    ) -> CellData {
362        let array_dims = dims.arrays.get(&ctx.pos).unwrap();
363
364        let height = dims.all.get(&ctx.pos).unwrap().height;
365        let additional_height = ctx.size.height - height;
366        let (chunk_height, mut rest_height) = split_value(additional_height, list.len());
367
368        let mut intersections_horizontal = ctx.intersections_horizontal;
369        let mut intersections_vertical = ctx.intersections_vertical;
370        let mut next_vsplit = false;
371        let mut next_intersections_vertical = vec![];
372
373        let mut builder = Builder::new();
374        for (i, val) in list.iter().enumerate() {
375            let val_pos = *array_dims.index.get(&i).unwrap();
376
377            let mut height = dims.all.get(&val_pos).unwrap().height;
378            match priority.height() {
379                DimensionPriority::First => {
380                    if i == 0 {
381                        height += additional_height;
382                    }
383                }
384                DimensionPriority::Last => {
385                    if i + 1 == list.len() {
386                        height += additional_height;
387                    }
388                }
389                DimensionPriority::List => {
390                    height += chunk_height;
391
392                    if rest_height > 0 {
393                        height += 1;
394                        rest_height -= 1; // must be safe
395                    }
396                }
397            }
398
399            let size = Dim::new(ctx.size.width, height);
400
401            let (split, intersections_vertical) =
402                short_splits3(&mut intersections_vertical, size.height);
403            let old_split = next_vsplit;
404            next_vsplit = split;
405
406            let is_prev_list_not_first = ctx.list && !ctx.list_is_first;
407            let valctx = PrintContext {
408                pos: val_pos,
409                is_last_col: ctx.is_last_col,
410                is_last_row: ctx.is_last_row && i + 1 == list.len(),
411                is_first_col: ctx.is_first_col,
412                is_first_row: ctx.is_first_row && i == 0,
413                kv: ctx.kv,
414                kv_is_first: ctx.kv_is_first,
415                list: true,
416                list_is_first: i == 0 && !is_prev_list_not_first,
417                no_left: ctx.no_left,
418                no_right: ctx.no_right,
419                no_bottom: ctx.no_bottom && i + 1 == list.len(),
420                lean_top: ctx.lean_top && i == 0,
421                top_intersection: (ctx.top_intersection && i == 0) || old_split,
422                top_left: ctx.top_left || i > 0,
423                intersections_horizontal,
424                intersections_vertical,
425                size,
426            };
427
428            let data = _build_table(val, cfg, dims, priority, valctx);
429            intersections_horizontal = data.intersections_horizontal;
430            next_intersections_vertical.extend(data.intersections_vertical);
431
432            builder.push_record([data.content]);
433        }
434
435        let table = builder
436            .build()
437            .with(Style::empty())
438            .with(Padding::zero())
439            .to_string();
440
441        CellData::new(table, intersections_horizontal, next_intersections_vertical)
442    }
443
444    fn generate_table_row(
445        list: &[TableValue],
446        cfg: &CompactMultilineConfig,
447        dims: &Dimensions,
448        priority: PoolTableDimension,
449        ctx: PrintContext,
450    ) -> CellData {
451        let array_dims = dims.arrays.get(&ctx.pos).unwrap();
452
453        let list_width = dims.all.get(&ctx.pos).unwrap().width;
454        let additional_width = ctx.size.width - list_width;
455        let (chunk_width, mut rest_width) = split_value(additional_width, list.len());
456
457        let mut intersections_horizontal = ctx.intersections_horizontal;
458        let mut intersections_vertical = ctx.intersections_vertical;
459        let mut new_intersections_horizontal = vec![];
460        let mut split_next = false;
461
462        let mut buf = Vec::with_capacity(list.len());
463        for (i, val) in list.iter().enumerate() {
464            let val_pos = *array_dims.index.get(&i).unwrap();
465
466            let mut width = dims.all.get(&val_pos).unwrap().width;
467            match priority.width() {
468                DimensionPriority::First => {
469                    if i == 0 {
470                        width += additional_width;
471                    }
472                }
473                DimensionPriority::Last => {
474                    if i + 1 == list.len() {
475                        width += additional_width;
476                    }
477                }
478                DimensionPriority::List => {
479                    width += chunk_width;
480
481                    if rest_width > 0 {
482                        width += 1;
483                        rest_width -= 1; // must be safe
484                    }
485                }
486            }
487
488            let size = Dim::new(width, ctx.size.height);
489
490            let (split, intersections_horizontal) =
491                short_splits3(&mut intersections_horizontal, width);
492            let old_split = split_next;
493            split_next = split;
494
495            let is_prev_list_not_first = ctx.list && !ctx.list_is_first;
496            let valctx = PrintContext {
497                pos: val_pos,
498                is_first_col: ctx.is_first_col && i == 0,
499                is_last_col: ctx.is_last_col && i + 1 == list.len(),
500                is_last_row: ctx.is_last_row,
501                is_first_row: ctx.is_first_row,
502                kv: false,
503                kv_is_first: false,
504                list: false,
505                list_is_first: !is_prev_list_not_first,
506                no_left: false,
507                no_right: !(ctx.is_last_col && i + 1 == list.len()),
508                no_bottom: false,
509                lean_top: !(ctx.is_first_col && i == 0),
510                top_intersection: (ctx.top_intersection && i == 0) || old_split,
511                top_left: ctx.top_left && i == 0,
512                intersections_horizontal,
513                intersections_vertical,
514                size,
515            };
516
517            let val = _build_table(val, cfg, dims, priority, valctx);
518            intersections_vertical = val.intersections_vertical;
519            new_intersections_horizontal.extend(val.intersections_horizontal.iter());
520            let value = val.content;
521
522            buf.push(value);
523        }
524
525        let mut builder = Builder::with_capacity(1, buf.len());
526        builder.push_record(buf);
527
528        let mut table = builder.build();
529        let _ = table.with(Style::empty());
530        let _ = table.with(Padding::zero());
531
532        let table = table.to_string();
533
534        CellData::new(table, new_intersections_horizontal, intersections_vertical)
535    }
536
537    fn generate_value_cell(
538        text: &str,
539        cfg: &CompactMultilineConfig,
540        ctx: PrintContext,
541    ) -> CellData {
542        let width = ctx.size.width;
543        let height = ctx.size.height;
544        let table = generate_value_table(text, cfg, ctx);
545        CellData::new(table, vec![width], vec![height])
546    }
547
548    fn generate_value_table(
549        text: &str,
550        cfg: &CompactMultilineConfig,
551        mut ctx: PrintContext,
552    ) -> String {
553        if ctx.size.width == 0 || ctx.size.height == 0 {
554            return String::new();
555        }
556
557        let halignment = cfg.get_alignment_horizontal();
558        let valignment = cfg.get_alignment_vertical();
559        let pad = cfg.get_padding();
560        let pad_color = convert_border_colors(*cfg.get_padding_color());
561        let lines_alignment = cfg.get_formatting().allow_lines_alignment;
562
563        let mut borders = *cfg.get_borders();
564
565        let bottom_intersection = cfg.get_borders().bottom_intersection.unwrap_or(' ');
566        let mut horizontal_splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width);
567        squash_splits(&mut horizontal_splits);
568
569        let right_intersection = borders.right_intersection.unwrap_or(' ');
570        let mut vertical_splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height);
571        squash_splits(&mut vertical_splits);
572
573        config_borders(&mut borders, &ctx);
574        let border = create_border(borders);
575
576        let borders_colors = *cfg.get_borders_color();
577        let border_color = create_border(borders_colors);
578
579        let mut height = ctx.size.height;
580        height -= pad.top.size + pad.bottom.size;
581
582        let mut width = ctx.size.width;
583        width -= pad.left.size + pad.right.size;
584
585        let count_lines = count_lines(text);
586        let (top, bottom) = indent_vertical(valignment, height, count_lines);
587
588        let mut buf = String::new();
589        print_top_line(
590            &mut buf,
591            border,
592            border_color,
593            &horizontal_splits,
594            bottom_intersection,
595            ctx.size.width,
596        );
597
598        let mut line_index = 0;
599        let mut vertical_splits = &vertical_splits[..];
600
601        for _ in 0..top {
602            let mut border = border;
603            if vertical_splits.first() == Some(&line_index) {
604                border.left = Some(right_intersection);
605                vertical_splits = &vertical_splits[1..];
606            }
607
608            print_line(&mut buf, border, border_color, None, ' ', ctx.size.width);
609            line_index += 1;
610        }
611
612        for _ in 0..pad.top.size {
613            let mut border = border;
614            if vertical_splits.first() == Some(&line_index) {
615                border.left = Some(right_intersection);
616                vertical_splits = &vertical_splits[1..];
617            }
618
619            print_line(
620                &mut buf,
621                border,
622                border_color,
623                pad_color.top,
624                pad.top.fill,
625                ctx.size.width,
626            );
627            line_index += 1;
628        }
629
630        if lines_alignment {
631            for line in get_lines(text) {
632                let line_width = get_line_width(&line);
633                let (left, right) = indent_horizontal(halignment, width, line_width);
634
635                if border.has_left() {
636                    let mut c = border.left.unwrap_or(' ');
637                    if vertical_splits.first() == Some(&line_index) {
638                        c = right_intersection;
639                        vertical_splits = &vertical_splits[1..];
640                    }
641
642                    print_char(&mut buf, c, border_color.left);
643                }
644
645                print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size);
646                buf.extend(repeat_n(' ', left));
647                buf.push_str(&line);
648                buf.extend(repeat_n(' ', right));
649                print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size);
650
651                if border.has_right() {
652                    print_char(&mut buf, border.right.unwrap_or(' '), border_color.right);
653                }
654
655                buf.push('\n');
656
657                line_index += 1;
658            }
659        } else {
660            let text_width = get_text_width(text);
661            let (left, _) = indent_horizontal(halignment, width, text_width);
662
663            for line in get_lines(text) {
664                let line_width = get_line_width(&line);
665                let right = width - line_width - left;
666
667                if border.has_left() {
668                    let mut c = border.left.unwrap_or(' ');
669                    if vertical_splits.first() == Some(&line_index) {
670                        c = right_intersection;
671                        vertical_splits = &vertical_splits[1..];
672                    }
673
674                    print_char(&mut buf, c, border_color.left);
675                }
676
677                print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size);
678                buf.extend(repeat_n(' ', left));
679                buf.push_str(&line);
680                buf.extend(repeat_n(' ', right));
681                print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size);
682
683                if border.has_right() {
684                    print_char(&mut buf, border.right.unwrap_or(' '), border_color.right);
685                }
686
687                buf.push('\n');
688
689                line_index += 1;
690            }
691        }
692
693        for _ in 0..pad.bottom.size {
694            let mut border = border;
695            if vertical_splits.first() == Some(&line_index) {
696                border.left = Some(right_intersection);
697                vertical_splits = &vertical_splits[1..];
698            }
699
700            print_line(
701                &mut buf,
702                border,
703                border_color,
704                pad_color.bottom,
705                pad.bottom.fill,
706                ctx.size.width,
707            );
708
709            line_index += 1;
710        }
711
712        for _ in 0..bottom {
713            let mut border = border;
714            if vertical_splits.first() == Some(&line_index) {
715                border.left = Some(right_intersection);
716                vertical_splits = &vertical_splits[1..];
717            }
718
719            print_line(&mut buf, border, border_color, None, ' ', ctx.size.width);
720            line_index += 1;
721        }
722
723        print_bottom_line(&mut buf, border, border_color, ctx.size.width);
724
725        let _ = buf.remove(buf.len() - 1);
726
727        buf
728    }
729
730    fn print_chars(buf: &mut String, c: char, color: Option<ANSIStr<'static>>, width: usize) {
731        match color {
732            Some(color) => {
733                buf.push_str(color.get_prefix());
734                buf.extend(repeat_n(c, width));
735                buf.push_str(color.get_suffix());
736            }
737            None => buf.extend(repeat_n(c, width)),
738        }
739    }
740
741    fn print_char(buf: &mut String, c: char, color: Option<ANSIStr<'static>>) {
742        match color {
743            Some(color) => {
744                buf.push_str(color.get_prefix());
745                buf.push(c);
746                buf.push_str(color.get_suffix());
747            }
748            None => buf.push(c),
749        }
750    }
751
752    fn print_line(
753        buf: &mut String,
754        border: Border<char>,
755        border_color: Border<ANSIStr<'static>>,
756        color: Option<ANSIStr<'static>>,
757        c: char,
758        width: usize,
759    ) {
760        if border.has_left() {
761            let c = border.left.unwrap_or(' ');
762            print_char(buf, c, border_color.left);
763        }
764
765        print_chars(buf, c, color, width);
766
767        if border.has_right() {
768            let c = border.right.unwrap_or(' ');
769            print_char(buf, c, border_color.right);
770        }
771
772        buf.push('\n');
773    }
774
775    fn print_top_line(
776        buf: &mut String,
777        border: Border<char>,
778        color: Border<ANSIStr<'static>>,
779        splits: &[usize],
780        split_char: char,
781        width: usize,
782    ) {
783        if !border.has_top() {
784            return;
785        }
786
787        let mut used_color: Option<ANSIStr<'static>> = None;
788
789        if border.has_left() {
790            if let Some(color) = color.left_top_corner {
791                used_color = Some(color);
792                buf.push_str(color.get_prefix());
793            }
794
795            let c = border.left_top_corner.unwrap_or(' ');
796            buf.push(c);
797        }
798
799        if let Some(color) = color.top {
800            match used_color {
801                Some(used) => {
802                    if used != color {
803                        buf.push_str(used.get_suffix());
804                        buf.push_str(color.get_prefix());
805                    }
806                }
807                None => {
808                    buf.push_str(color.get_prefix());
809                    used_color = Some(color);
810                }
811            }
812        }
813
814        let c = border.top.unwrap_or(' ');
815        if splits.is_empty() {
816            buf.extend(repeat_n(c, width));
817        } else {
818            let mut splits = splits;
819            for i in 0..width {
820                if splits.first() == Some(&i) {
821                    buf.push(split_char);
822                    splits = &splits[1..];
823                } else {
824                    buf.push(c);
825                }
826            }
827        }
828
829        if border.has_right() {
830            if let Some(color) = color.right_top_corner {
831                match used_color {
832                    Some(used) => {
833                        if used != color {
834                            buf.push_str(used.get_suffix());
835                            buf.push_str(color.get_prefix());
836                        }
837                    }
838                    None => {
839                        buf.push_str(color.get_prefix());
840                        used_color = Some(color);
841                    }
842                }
843            }
844
845            let c = border.right_top_corner.unwrap_or(' ');
846            buf.push(c);
847        }
848
849        if let Some(used) = used_color {
850            buf.push_str(used.get_suffix());
851        }
852
853        buf.push('\n');
854    }
855
856    fn print_bottom_line(
857        buf: &mut String,
858        border: Border<char>,
859        color: Border<ANSIStr<'static>>,
860        width: usize,
861    ) {
862        if !border.has_bottom() {
863            return;
864        }
865
866        let mut used_color: Option<ANSIStr<'static>> = None;
867
868        if border.has_left() {
869            if let Some(color) = color.left_bottom_corner {
870                used_color = Some(color);
871                buf.push_str(color.get_prefix());
872            }
873
874            let c = border.left_bottom_corner.unwrap_or(' ');
875            buf.push(c);
876        }
877
878        if let Some(color) = color.bottom {
879            match used_color {
880                Some(used) => {
881                    if used != color {
882                        buf.push_str(used.get_suffix());
883                        buf.push_str(color.get_prefix());
884                    }
885                }
886                None => {
887                    buf.push_str(color.get_prefix());
888                    used_color = Some(color);
889                }
890            }
891        }
892
893        let c = border.bottom.unwrap_or(' ');
894        buf.extend(repeat_n(c, width));
895
896        if border.has_right() {
897            if let Some(color) = color.right_bottom_corner {
898                match used_color {
899                    Some(used) => {
900                        if used != color {
901                            buf.push_str(used.get_suffix());
902                            buf.push_str(color.get_prefix());
903                        }
904                    }
905                    None => {
906                        buf.push_str(color.get_prefix());
907                        used_color = Some(color);
908                    }
909                }
910            }
911
912            let c = border.right_bottom_corner.unwrap_or(' ');
913            buf.push(c);
914        }
915
916        if let Some(used) = used_color {
917            buf.push_str(used.get_suffix());
918        }
919
920        buf.push('\n');
921    }
922
923    fn create_border<T>(borders: Borders<T>) -> Border<T> {
924        Border {
925            top: borders.top,
926            bottom: borders.bottom,
927            left: borders.left,
928            right: borders.right,
929            left_top_corner: borders.top_left,
930            left_bottom_corner: borders.bottom_left,
931            right_top_corner: borders.top_right,
932            right_bottom_corner: borders.bottom_right,
933        }
934    }
935
936    fn config_borders(borders: &mut Borders<char>, ctx: &PrintContext) {
937        // set top_left
938        {
939            if ctx.kv && ctx.kv_is_first {
940                borders.top_left = borders.top_intersection;
941            }
942
943            if ctx.kv && !ctx.kv_is_first {
944                borders.top_left = borders.intersection;
945            }
946
947            if ctx.kv && ctx.list && !ctx.list_is_first {
948                borders.top_left = borders.left_intersection;
949            }
950
951            if ctx.is_first_col && !ctx.is_first_row {
952                borders.top_left = borders.left_intersection;
953            }
954
955            if ctx.lean_top {
956                borders.top_left = borders.top_intersection;
957            }
958
959            if ctx.top_left {
960                borders.top_left = borders.left_intersection;
961            }
962
963            if ctx.top_intersection {
964                borders.top_left = borders.intersection;
965            }
966        }
967
968        if ctx.is_last_col && !ctx.is_first_row {
969            borders.top_right = borders.right_intersection;
970        }
971
972        if !ctx.is_first_col && ctx.is_last_row {
973            borders.bottom_left = borders.bottom_intersection;
974        }
975
976        if !ctx.is_last_row || ctx.no_bottom {
977            cfg_no_bottom_borders(borders);
978        }
979
980        if ctx.no_right {
981            cfg_no_right_borders(borders);
982        }
983    }
984
985    fn cfg_no_bottom_borders(borders: &mut Borders<char>) {
986        borders.bottom = None;
987        borders.bottom_intersection = None;
988        borders.bottom_left = None;
989        borders.bottom_right = None;
990        borders.horizontal = None;
991    }
992
993    fn cfg_no_right_borders(borders: &mut Borders<char>) {
994        borders.right = None;
995        borders.right_intersection = None;
996        borders.top_right = None;
997        borders.bottom_right = None;
998        borders.vertical = None;
999    }
1000
1001    #[derive(Debug, Default)]
1002    struct Dimensions {
1003        all: HashMap<usize, Dim>,
1004        arrays: HashMap<usize, ArrayDimensions>,
1005    }
1006
1007    #[derive(Debug, Default, Clone, Copy)]
1008    struct Dim {
1009        width: usize,
1010        height: usize,
1011    }
1012
1013    impl Dim {
1014        fn new(width: usize, height: usize) -> Self {
1015            Self { width, height }
1016        }
1017    }
1018
1019    #[derive(Debug, Default)]
1020    struct ArrayDimensions {
1021        max: Dim,
1022        index: HashMap<usize, usize>,
1023    }
1024
1025    fn collect_table_dimensions(val: &TableValue, cfg: &CompactMultilineConfig) -> Dimensions {
1026        let mut buf = Dimensions::default();
1027        let (dim, _) = __collect_table_dims(&mut buf, val, cfg, 0);
1028        let _ = buf.all.insert(0, dim);
1029        buf
1030    }
1031
1032    fn __collect_table_dims(
1033        buf: &mut Dimensions,
1034        val: &TableValue,
1035        cfg: &CompactMultilineConfig,
1036        pos: usize,
1037    ) -> (Dim, usize) {
1038        match val {
1039            TableValue::Cell(text) => (str_dimension(text, cfg), 0),
1040            TableValue::Row(list) => {
1041                if list.is_empty() {
1042                    return (empty_dimension(cfg), 0);
1043                }
1044
1045                let mut index = ArrayDimensions {
1046                    max: Dim::default(),
1047                    index: HashMap::with_capacity(list.len()),
1048                };
1049
1050                let mut total_width = 0;
1051
1052                let mut count_elements = list.len();
1053                let mut val_pos = pos + 1;
1054                for (i, value) in list.iter().enumerate() {
1055                    let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos);
1056                    count_elements += elements;
1057
1058                    total_width += dim.width;
1059
1060                    index.max.width = max(index.max.width, dim.width);
1061                    index.max.height = max(index.max.height, dim.height);
1062
1063                    let _ = buf.all.insert(val_pos, dim);
1064
1065                    let _ = index.index.insert(i, val_pos);
1066
1067                    val_pos += 1 + elements;
1068                }
1069
1070                let max_height = index.max.height;
1071
1072                let _ = buf.arrays.insert(pos, index);
1073
1074                let has_vertical = cfg.get_borders().has_left();
1075                total_width += has_vertical as usize * (list.len() - 1);
1076
1077                (Dim::new(total_width, max_height), count_elements)
1078            }
1079            TableValue::Column(list) => {
1080                if list.is_empty() {
1081                    return (empty_dimension(cfg), 0);
1082                }
1083
1084                let mut index = ArrayDimensions {
1085                    max: Dim::default(),
1086                    index: HashMap::with_capacity(list.len()),
1087                };
1088
1089                let mut total_height = 0;
1090
1091                let mut count_elements = list.len();
1092                let mut val_pos = pos + 1;
1093                for (i, value) in list.iter().enumerate() {
1094                    let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos);
1095                    count_elements += elements;
1096
1097                    total_height += dim.height;
1098
1099                    index.max.width = max(index.max.width, dim.width);
1100                    index.max.height = max(index.max.height, dim.height);
1101
1102                    let _ = buf.all.insert(val_pos, dim);
1103
1104                    let _ = index.index.insert(i, val_pos);
1105
1106                    val_pos += 1 + elements;
1107                }
1108
1109                let max_width = index.max.width;
1110
1111                let _ = buf.arrays.insert(pos, index);
1112
1113                let has_horizontal = cfg.get_borders().has_top();
1114                total_height += has_horizontal as usize * (list.len() - 1);
1115
1116                (Dim::new(max_width, total_height), count_elements)
1117            }
1118        }
1119    }
1120
1121    fn empty_dimension(cfg: &CompactMultilineConfig) -> Dim {
1122        Dim::new(get_padding_horizontal(cfg), 1 + get_padding_vertical(cfg))
1123    }
1124
1125    fn str_dimension(text: &str, cfg: &CompactMultilineConfig) -> Dim {
1126        let (count_lines, width) = get_text_dimension(text);
1127        let w = width + get_padding_horizontal(cfg);
1128        let h = count_lines + get_padding_vertical(cfg);
1129        Dim::new(w, h)
1130    }
1131
1132    fn get_padding_horizontal(cfg: &CompactMultilineConfig) -> usize {
1133        let pad = cfg.get_padding();
1134        pad.left.size + pad.right.size
1135    }
1136
1137    fn get_padding_vertical(cfg: &CompactMultilineConfig) -> usize {
1138        let pad = cfg.get_padding();
1139        pad.top.size + pad.bottom.size
1140    }
1141
1142    fn split_value(value: usize, by: usize) -> (usize, usize) {
1143        let val = value / by;
1144        let rest = value - val * by;
1145        (val, rest)
1146    }
1147
1148    fn indent_vertical(al: AlignmentVertical, available: usize, real: usize) -> (usize, usize) {
1149        let top = indent_top(al, available, real);
1150        let bottom = available - real - top;
1151        (top, bottom)
1152    }
1153
1154    fn indent_horizontal(al: AlignmentHorizontal, available: usize, real: usize) -> (usize, usize) {
1155        let top = indent_left(al, available, real);
1156        let right = available - real - top;
1157        (top, right)
1158    }
1159
1160    fn indent_top(al: AlignmentVertical, available: usize, real: usize) -> usize {
1161        match al {
1162            AlignmentVertical::Top => 0,
1163            AlignmentVertical::Bottom => available - real,
1164            AlignmentVertical::Center => (available - real) / 2,
1165        }
1166    }
1167
1168    fn indent_left(al: AlignmentHorizontal, available: usize, real: usize) -> usize {
1169        match al {
1170            AlignmentHorizontal::Left => 0,
1171            AlignmentHorizontal::Right => available - real,
1172            AlignmentHorizontal::Center => (available - real) / 2,
1173        }
1174    }
1175
1176    fn short_splits(splits: &mut Vec<usize>, width: usize) -> Vec<usize> {
1177        if splits.is_empty() {
1178            return Vec::new();
1179        }
1180
1181        let mut out = Vec::new();
1182        let mut pos = 0;
1183        for &split in splits.iter() {
1184            if pos + split >= width {
1185                break;
1186            }
1187
1188            pos += split;
1189            out.push(pos);
1190        }
1191
1192        let _ = splits.drain(..out.len());
1193
1194        if !splits.is_empty() && pos <= width {
1195            let rest = width - pos;
1196            splits[0] -= rest;
1197        }
1198
1199        out
1200    }
1201
1202    fn short_splits3(splits: &mut Vec<usize>, width: usize) -> (bool, Vec<usize>) {
1203        if splits.is_empty() {
1204            return (false, Vec::new());
1205        }
1206
1207        let mut out = Vec::new();
1208        let mut pos = 0;
1209        for &split in splits.iter() {
1210            if pos + split >= width {
1211                break;
1212            }
1213
1214            pos += split + 1;
1215            out.push(split);
1216        }
1217
1218        let _ = splits.drain(..out.len());
1219
1220        if splits.is_empty() {
1221            return (false, out);
1222        }
1223
1224        if pos <= width {
1225            splits[0] -= width - pos;
1226            if splits[0] > 0 {
1227                splits[0] -= 1;
1228            } else {
1229                let _ = splits.remove(0);
1230                return (true, out);
1231            }
1232        }
1233
1234        (false, out)
1235    }
1236
1237    fn squash_splits(splits: &mut [usize]) {
1238        splits.iter_mut().enumerate().for_each(|(i, s)| *s += i);
1239    }
1240
1241    fn set_margin(
1242        table: &str,
1243        margin: Sides<Indent>,
1244        color: Sides<Option<ANSIStr<'static>>>,
1245    ) -> String {
1246        if table.is_empty() {
1247            return String::new();
1248        }
1249
1250        let mut buf = String::new();
1251        let width = get_text_width(table);
1252        let top_color = color.top;
1253        let bottom_color = color.bottom;
1254        let left_color = color.left;
1255        let right_color = color.right;
1256        for _ in 0..margin.top.size {
1257            print_chars(&mut buf, margin.left.fill, left_color, margin.left.size);
1258            print_chars(&mut buf, margin.top.fill, top_color, width);
1259            print_chars(&mut buf, margin.right.fill, right_color, margin.right.size);
1260            buf.push('\n');
1261        }
1262
1263        for line in get_lines(table) {
1264            print_chars(&mut buf, margin.left.fill, left_color, margin.left.size);
1265            buf.push_str(&line);
1266            print_chars(&mut buf, margin.right.fill, right_color, margin.right.size);
1267            buf.push('\n');
1268        }
1269
1270        for _ in 0..margin.bottom.size {
1271            print_chars(&mut buf, margin.left.fill, left_color, margin.left.size);
1272            print_chars(&mut buf, margin.bottom.fill, bottom_color, width);
1273            print_chars(&mut buf, margin.right.fill, right_color, margin.right.size);
1274            buf.push('\n');
1275        }
1276
1277        let _ = buf.remove(buf.len() - 1);
1278
1279        buf
1280    }
1281
1282    fn convert_border_colors(
1283        pad_color: Sides<ANSIStr<'static>>,
1284    ) -> Sides<Option<ANSIStr<'static>>> {
1285        Sides::new(
1286            (!pad_color.left.is_empty()).then_some(pad_color.left),
1287            (!pad_color.right.is_empty()).then_some(pad_color.right),
1288            (!pad_color.top.is_empty()).then_some(pad_color.top),
1289            (!pad_color.bottom.is_empty()).then_some(pad_color.bottom),
1290        )
1291    }
1292}