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 TableValue::Row(
128 row.into_iter()
129 .map(|cell| cell.as_ref().to_string())
130 .map(TableValue::Cell)
131 .collect(),
132 )
133 })
134 .collect(),
135 );
136
137 Self {
138 config: configure_grid(),
139 dims: PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List),
140 value,
141 }
142 }
143
144 /// A is a generic function which applies options to the [`PoolTable`] configuration.
145 ///
146 /// Notice that it has a limited support of options.
147 ///
148 /// ```
149 /// use tabled::tables::PoolTable;
150 /// use tabled::settings::{Style, Padding};
151 ///
152 /// let data = vec![
153 /// vec!["Hello", "World", "!"],
154 /// vec!["Salve", "mondo", "!"],
155 /// vec!["Hola", "mundo", "!"],
156 /// ];
157 ///
158 /// let table = PoolTable::new(data)
159 /// .with(Style::extended())
160 /// .with(Padding::zero())
161 /// .to_string();
162 ///
163 /// assert_eq!(
164 /// table,
165 /// "╔═════╦═════╦═╗\n\
166 /// ║Hello║World║!║\n\
167 /// ╠═════╬═════╬═╣\n\
168 /// ║Salve║mondo║!║\n\
169 /// ╠═════╬═════╬═╣\n\
170 /// ║Hola ║mundo║!║\n\
171 /// ╚═════╩═════╩═╝"
172 /// )
173 /// ```
174 pub fn with<O>(&mut self, option: O) -> &mut Self
175 where
176 O: TableOption<EmptyRecords, CompactMultilineConfig, PoolTableDimension>,
177 {
178 let mut records = EmptyRecords::default();
179 option.change(&mut records, &mut self.config, &mut self.dims);
180
181 self
182 }
183}
184
185impl From<TableValue> for PoolTable {
186 fn from(value: TableValue) -> Self {
187 Self {
188 config: configure_grid(),
189 dims: PoolTableDimension::new(width:DimensionPriority::List, height:DimensionPriority::List),
190 value,
191 }
192 }
193}
194
195impl Display for PoolTable {
196 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
197 print::build_table(&self.value, &self.config, self.dims).fmt(f)
198 }
199}
200
201/// [`TableValue`] a structure which is responsible for a [`PoolTable`] layout.
202#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
203pub enum TableValue {
204 /// A horizontal row.
205 Row(Vec<TableValue>),
206 /// A vertical column.
207 Column(Vec<TableValue>),
208 /// A single cell.
209 Cell(String),
210}
211
212fn configure_grid() -> CompactMultilineConfig {
213 let pad: Sides = Sides::new(
214 left:Indent::spaced(1),
215 right:Indent::spaced(1),
216 top:Indent::zero(),
217 bottom:Indent::zero(),
218 );
219
220 let mut cfg: CompactMultilineConfig = CompactMultilineConfig::new();
221 cfg.set_padding(pad);
222 cfg.set_alignment_horizontal(alignment:AlignmentHorizontal::Left);
223 cfg.set_borders(Style::ascii().get_borders());
224
225 cfg
226}
227
228impl<R, C> TableOption<R, C, PoolTableDimension> for PoolTableDimension {
229 fn change(self, _: &mut R, _: &mut C, dimension: &mut PoolTableDimension) {
230 *dimension = self;
231 }
232}
233
234impl<R, D> TableOption<R, CompactMultilineConfig, D> for CompactMultilineConfig {
235 fn change(self, _: &mut R, config: &mut CompactMultilineConfig, _: &mut D) {
236 *config = self;
237 }
238}
239
240mod print {
241 use std::{cmp::max, collections::HashMap, iter::repeat};
242
243 use crate::{
244 builder::Builder,
245 grid::{
246 ansi::ANSIStr,
247 config::{
248 AlignmentHorizontal, AlignmentVertical, Border, Borders, ColoredConfig,
249 CompactMultilineConfig, Indent, Offset, Sides,
250 },
251 dimension::{Dimension, DimensionPriority, Estimate, PoolTableDimension},
252 records::Records,
253 util::string::{
254 count_lines, get_lines, string_dimension, string_width, string_width_multiline,
255 },
256 },
257 settings::{Padding, Style, TableOption},
258 };
259
260 use super::TableValue;
261
262 #[derive(Debug, Default)]
263 struct PrintContext {
264 pos: usize,
265 is_last_col: bool,
266 is_last_row: bool,
267 is_first_col: bool,
268 is_first_row: bool,
269 kv: bool,
270 kv_is_first: bool,
271 list: bool,
272 list_is_first: bool,
273 no_left: bool,
274 no_right: bool,
275 no_bottom: bool,
276 lean_top: bool,
277 top_intersection: bool,
278 top_left: bool,
279 intersections_horizontal: Vec<usize>,
280 intersections_vertical: Vec<usize>,
281 size: Dim,
282 }
283
284 struct CellData {
285 content: String,
286 intersections_horizontal: Vec<usize>,
287 intersections_vertical: Vec<usize>,
288 }
289
290 impl CellData {
291 fn new(content: String, i_horizontal: Vec<usize>, i_vertical: Vec<usize>) -> Self {
292 Self {
293 content,
294 intersections_horizontal: i_horizontal,
295 intersections_vertical: i_vertical,
296 }
297 }
298 }
299
300 pub(super) fn build_table(
301 val: &TableValue,
302 cfg: &CompactMultilineConfig,
303 dims_priority: PoolTableDimension,
304 ) -> String {
305 let dims = collect_table_dimensions(val, cfg);
306 let ctx = PrintContext {
307 is_last_col: true,
308 is_last_row: true,
309 is_first_col: true,
310 is_first_row: true,
311 size: *dims.all.get(&0).unwrap(),
312 ..Default::default()
313 };
314
315 let data = _build_table(val, cfg, &dims, dims_priority, ctx);
316 let mut table = data.content;
317
318 let margin = cfg.get_margin();
319 let has_margin = margin.top.size > 0
320 || margin.bottom.size > 0
321 || margin.left.size > 0
322 || margin.right.size > 0;
323 if has_margin {
324 let color = convert_border_colors(*cfg.get_margin_color());
325 table = set_margin(&table, *margin, color);
326 }
327
328 table
329 }
330
331 fn _build_table(
332 val: &TableValue,
333 cfg: &CompactMultilineConfig,
334 dims: &Dimensions,
335 priority: PoolTableDimension,
336 ctx: PrintContext,
337 ) -> CellData {
338 match val {
339 TableValue::Cell(text) => generate_value_cell(text, cfg, ctx),
340 TableValue::Row(list) => {
341 if list.is_empty() {
342 return generate_value_cell("", cfg, ctx);
343 }
344
345 generate_table_row(list, cfg, dims, priority, ctx)
346 }
347 TableValue::Column(list) => {
348 if list.is_empty() {
349 return generate_value_cell("", cfg, ctx);
350 }
351
352 generate_table_column(list, cfg, dims, priority, ctx)
353 }
354 }
355 }
356
357 fn generate_table_column(
358 list: &Vec<TableValue>,
359 cfg: &CompactMultilineConfig,
360 dims: &Dimensions,
361 priority: PoolTableDimension,
362 ctx: PrintContext,
363 ) -> CellData {
364 let array_dims = dims.arrays.get(&ctx.pos).unwrap();
365
366 let height = dims.all.get(&ctx.pos).unwrap().height;
367 let additional_height = ctx.size.height - height;
368 let (chunk_height, mut rest_height) = split_value(additional_height, list.len());
369
370 let mut intersections_horizontal = ctx.intersections_horizontal;
371 let mut intersections_vertical = ctx.intersections_vertical;
372 let mut next_vsplit = false;
373 let mut next_intersections_vertical = vec![];
374
375 let mut builder = Builder::new();
376 for (i, val) in list.iter().enumerate() {
377 let val_pos = *array_dims.index.get(&i).unwrap();
378
379 let mut height = dims.all.get(&val_pos).unwrap().height;
380 match priority.height() {
381 DimensionPriority::First => {
382 if i == 0 {
383 height += additional_height;
384 }
385 }
386 DimensionPriority::Last => {
387 if i + 1 == list.len() {
388 height += additional_height;
389 }
390 }
391 DimensionPriority::List => {
392 height += chunk_height;
393
394 if rest_height > 0 {
395 height += 1;
396 rest_height -= 1; // must be safe
397 }
398 }
399 }
400
401 let size = Dim::new(ctx.size.width, height);
402
403 let (split, intersections_vertical) =
404 short_splits3(&mut intersections_vertical, size.height);
405 let old_split = next_vsplit;
406 next_vsplit = split;
407
408 let is_prev_list_not_first = ctx.list && !ctx.list_is_first;
409 let valctx = PrintContext {
410 pos: val_pos,
411 is_last_col: ctx.is_last_col,
412 is_last_row: ctx.is_last_row && i + 1 == list.len(),
413 is_first_col: ctx.is_first_col,
414 is_first_row: ctx.is_first_row && i == 0,
415 kv: ctx.kv,
416 kv_is_first: ctx.kv_is_first,
417 list: true,
418 list_is_first: i == 0 && !is_prev_list_not_first,
419 no_left: ctx.no_left,
420 no_right: ctx.no_right,
421 no_bottom: ctx.no_bottom && i + 1 == list.len(),
422 lean_top: ctx.lean_top && i == 0,
423 top_intersection: (ctx.top_intersection && i == 0) || old_split,
424 top_left: ctx.top_left || i > 0,
425 intersections_horizontal,
426 intersections_vertical,
427 size,
428 };
429
430 let data = _build_table(val, cfg, dims, priority, valctx);
431 intersections_horizontal = data.intersections_horizontal;
432 next_intersections_vertical.extend(data.intersections_vertical);
433
434 builder.push_record([data.content]);
435 }
436
437 let table = builder
438 .build()
439 .with(Style::empty())
440 .with(Padding::zero())
441 .to_string();
442
443 CellData::new(table, intersections_horizontal, next_intersections_vertical)
444 }
445
446 fn generate_table_row(
447 list: &Vec<TableValue>,
448 cfg: &CompactMultilineConfig,
449 dims: &Dimensions,
450 priority: PoolTableDimension,
451 ctx: PrintContext,
452 ) -> CellData {
453 let array_dims = dims.arrays.get(&ctx.pos).unwrap();
454
455 let list_width = dims.all.get(&ctx.pos).unwrap().width;
456 let additional_width = ctx.size.width - list_width;
457 let (chunk_width, mut rest_width) = split_value(additional_width, list.len());
458
459 let mut intersections_horizontal = ctx.intersections_horizontal;
460 let mut intersections_vertical = ctx.intersections_vertical;
461 let mut new_intersections_horizontal = vec![];
462 let mut split_next = false;
463
464 let mut buf = Vec::with_capacity(list.len());
465 for (i, val) in list.iter().enumerate() {
466 let val_pos = *array_dims.index.get(&i).unwrap();
467
468 let mut width = dims.all.get(&val_pos).unwrap().width;
469 match priority.width() {
470 DimensionPriority::First => {
471 if i == 0 {
472 width += additional_width;
473 }
474 }
475 DimensionPriority::Last => {
476 if i + 1 == list.len() {
477 width += additional_width;
478 }
479 }
480 DimensionPriority::List => {
481 width += chunk_width;
482
483 if rest_width > 0 {
484 width += 1;
485 rest_width -= 1; // must be safe
486 }
487 }
488 }
489
490 let size = Dim::new(width, ctx.size.height);
491
492 let (split, intersections_horizontal) =
493 short_splits3(&mut intersections_horizontal, width);
494 let old_split = split_next;
495 split_next = split;
496
497 let is_prev_list_not_first = ctx.list && !ctx.list_is_first;
498 let valctx = PrintContext {
499 pos: val_pos,
500 is_first_col: ctx.is_first_col && i == 0,
501 is_last_col: ctx.is_last_col && i + 1 == list.len(),
502 is_last_row: ctx.is_last_row,
503 is_first_row: ctx.is_first_row,
504 kv: false,
505 kv_is_first: false,
506 list: false,
507 list_is_first: !is_prev_list_not_first,
508 no_left: false,
509 no_right: !(ctx.is_last_col && i + 1 == list.len()),
510 no_bottom: false,
511 lean_top: !(ctx.is_first_col && i == 0),
512 top_intersection: (ctx.top_intersection && i == 0) || old_split,
513 top_left: ctx.top_left && i == 0,
514 intersections_horizontal,
515 intersections_vertical,
516 size,
517 };
518
519 let val = _build_table(val, cfg, dims, priority, valctx);
520 intersections_vertical = val.intersections_vertical;
521 new_intersections_horizontal.extend(val.intersections_horizontal.iter());
522 let value = val.content;
523
524 buf.push(value);
525 }
526
527 let mut builder = Builder::with_capacity(1, buf.len());
528 builder.push_record(buf);
529
530 let mut table = builder.build();
531 let _ = table.with(Style::empty());
532 let _ = table.with(Padding::zero());
533
534 let table = table.to_string();
535
536 CellData::new(table, new_intersections_horizontal, intersections_vertical)
537 }
538
539 fn generate_value_cell(
540 text: &str,
541 cfg: &CompactMultilineConfig,
542 ctx: PrintContext,
543 ) -> CellData {
544 let width = ctx.size.width;
545 let height = ctx.size.height;
546 let table = generate_value_table(text, cfg, ctx);
547 CellData::new(table, vec![width], vec![height])
548 }
549
550 fn generate_value_table(
551 text: &str,
552 cfg: &CompactMultilineConfig,
553 mut ctx: PrintContext,
554 ) -> String {
555 if ctx.size.width == 0 || ctx.size.height == 0 {
556 return String::new();
557 }
558
559 let halignment = cfg.get_alignment_horizontal();
560 let valignment = cfg.get_alignment_vertical();
561 let pad = cfg.get_padding();
562 let pad_color = convert_border_colors(*cfg.get_padding_color());
563 let lines_alignemnt = cfg.get_formatting().allow_lines_alignment;
564
565 let mut borders = *cfg.get_borders();
566
567 let bottom_intesection = cfg.get_borders().bottom_intersection.unwrap_or(' ');
568 let mut horizontal_splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width);
569 squash_splits(&mut horizontal_splits);
570
571 let right_intersection = borders.right_intersection.unwrap_or(' ');
572 let mut vertical_splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height);
573 squash_splits(&mut vertical_splits);
574
575 config_borders(&mut borders, &ctx);
576 let border = create_border(borders);
577
578 let borders_colors = *cfg.get_borders_color();
579 let border_color = create_border(borders_colors);
580
581 let mut height = ctx.size.height;
582 height -= pad.top.size + pad.bottom.size;
583
584 let mut width = ctx.size.width;
585 width -= pad.left.size + pad.right.size;
586
587 let count_lines = count_lines(text);
588 let (top, bottom) = indent_vertical(valignment, height, count_lines);
589
590 let mut buf = String::new();
591 print_top_line(
592 &mut buf,
593 border,
594 border_color,
595 &horizontal_splits,
596 bottom_intesection,
597 ctx.size.width,
598 );
599
600 let mut line_index = 0;
601 let mut vertical_splits = &vertical_splits[..];
602
603 for _ in 0..top {
604 let mut border = border;
605 if vertical_splits.first() == Some(&line_index) {
606 border.left = Some(right_intersection);
607 vertical_splits = &vertical_splits[1..];
608 }
609
610 print_line(&mut buf, border, border_color, None, ' ', ctx.size.width);
611 line_index += 1;
612 }
613
614 for _ in 0..pad.top.size {
615 let mut border = border;
616 if vertical_splits.first() == Some(&line_index) {
617 border.left = Some(right_intersection);
618 vertical_splits = &vertical_splits[1..];
619 }
620
621 print_line(
622 &mut buf,
623 border,
624 border_color,
625 pad_color.top,
626 pad.top.fill,
627 ctx.size.width,
628 );
629 line_index += 1;
630 }
631
632 if lines_alignemnt {
633 for line in get_lines(text) {
634 let line_width = string_width(&line);
635 let (left, right) = indent_horizontal(halignment, width, line_width);
636
637 if border.has_left() {
638 let mut c = border.left.unwrap_or(' ');
639 if vertical_splits.first() == Some(&line_index) {
640 c = right_intersection;
641 vertical_splits = &vertical_splits[1..];
642 }
643
644 print_char(&mut buf, c, border_color.left);
645 }
646
647 print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size);
648 buf.extend(repeat(' ').take(left));
649 buf.push_str(&line);
650 buf.extend(repeat(' ').take(right));
651 print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size);
652
653 if border.has_right() {
654 print_char(&mut buf, border.right.unwrap_or(' '), border_color.right);
655 }
656
657 buf.push('\n');
658
659 line_index += 1;
660 }
661 } else {
662 let text_width = string_width_multiline(text);
663 let (left, _) = indent_horizontal(halignment, width, text_width);
664
665 for line in get_lines(text) {
666 let line_width = string_width(&line);
667 let right = width - line_width - left;
668
669 if border.has_left() {
670 let mut c = border.left.unwrap_or(' ');
671 if vertical_splits.first() == Some(&line_index) {
672 c = right_intersection;
673 vertical_splits = &vertical_splits[1..];
674 }
675
676 print_char(&mut buf, c, border_color.left);
677 }
678
679 print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size);
680 buf.extend(repeat(' ').take(left));
681 buf.push_str(&line);
682 buf.extend(repeat(' ').take(right));
683 print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size);
684
685 if border.has_right() {
686 print_char(&mut buf, border.right.unwrap_or(' '), border_color.right);
687 }
688
689 buf.push('\n');
690
691 line_index += 1;
692 }
693 }
694
695 for _ in 0..pad.bottom.size {
696 let mut border = border;
697 if vertical_splits.first() == Some(&line_index) {
698 border.left = Some(right_intersection);
699 vertical_splits = &vertical_splits[1..];
700 }
701
702 print_line(
703 &mut buf,
704 border,
705 border_color,
706 pad_color.bottom,
707 pad.bottom.fill,
708 ctx.size.width,
709 );
710
711 line_index += 1;
712 }
713
714 for _ in 0..bottom {
715 let mut border = border;
716 if vertical_splits.first() == Some(&line_index) {
717 border.left = Some(right_intersection);
718 vertical_splits = &vertical_splits[1..];
719 }
720
721 print_line(&mut buf, border, border_color, None, ' ', ctx.size.width);
722 line_index += 1;
723 }
724
725 print_bottom_line(&mut buf, border, border_color, ctx.size.width);
726
727 let _ = buf.remove(buf.len() - 1);
728
729 buf
730 }
731
732 fn print_chars(buf: &mut String, c: char, color: Option<ANSIStr<'static>>, width: usize) {
733 match color {
734 Some(color) => {
735 buf.push_str(color.get_prefix());
736 buf.extend(repeat(c).take(width));
737 buf.push_str(color.get_suffix());
738 }
739 None => buf.extend(repeat(c).take(width)),
740 }
741 }
742
743 fn print_char(buf: &mut String, c: char, color: Option<ANSIStr<'static>>) {
744 match color {
745 Some(color) => {
746 buf.push_str(color.get_prefix());
747 buf.push(c);
748 buf.push_str(color.get_suffix());
749 }
750 None => buf.push(c),
751 }
752 }
753
754 fn print_line(
755 buf: &mut String,
756 border: Border<char>,
757 border_color: Border<ANSIStr<'static>>,
758 color: Option<ANSIStr<'static>>,
759 c: char,
760 width: usize,
761 ) {
762 if border.has_left() {
763 let c = border.left.unwrap_or(' ');
764 print_char(buf, c, border_color.left);
765 }
766
767 print_chars(buf, c, color, width);
768
769 if border.has_right() {
770 let c = border.right.unwrap_or(' ');
771 print_char(buf, c, border_color.right);
772 }
773
774 buf.push('\n');
775 }
776
777 fn print_top_line(
778 buf: &mut String,
779 border: Border<char>,
780 color: Border<ANSIStr<'static>>,
781 splits: &[usize],
782 split_char: char,
783 width: usize,
784 ) {
785 if !border.has_top() {
786 return;
787 }
788
789 let mut used_color: Option<ANSIStr<'static>> = None;
790
791 if border.has_left() {
792 if let Some(color) = color.left_top_corner {
793 used_color = Some(color);
794 buf.push_str(color.get_prefix());
795 }
796
797 let c = border.left_top_corner.unwrap_or(' ');
798 buf.push(c);
799 }
800
801 if let Some(color) = color.top {
802 match used_color {
803 Some(used) => {
804 if used != color {
805 buf.push_str(used.get_suffix());
806 buf.push_str(color.get_prefix());
807 }
808 }
809 None => {
810 buf.push_str(color.get_prefix());
811 used_color = Some(color);
812 }
813 }
814 }
815
816 let c = border.top.unwrap_or(' ');
817 if splits.is_empty() {
818 buf.extend(repeat(c).take(width));
819 } else {
820 let mut splits = splits;
821 for i in 0..width {
822 if splits.first() == Some(&i) {
823 buf.push(split_char);
824 splits = &splits[1..];
825 } else {
826 buf.push(c);
827 }
828 }
829 }
830
831 if border.has_right() {
832 if let Some(color) = color.right_top_corner {
833 match used_color {
834 Some(used) => {
835 if used != color {
836 buf.push_str(used.get_suffix());
837 buf.push_str(color.get_prefix());
838 }
839 }
840 None => {
841 buf.push_str(color.get_prefix());
842 used_color = Some(color);
843 }
844 }
845 }
846
847 let c = border.right_top_corner.unwrap_or(' ');
848 buf.push(c);
849 }
850
851 if let Some(used) = used_color {
852 buf.push_str(used.get_suffix());
853 }
854
855 buf.push('\n');
856 }
857
858 fn print_bottom_line(
859 buf: &mut String,
860 border: Border<char>,
861 color: Border<ANSIStr<'static>>,
862 width: usize,
863 ) {
864 if !border.has_bottom() {
865 return;
866 }
867
868 let mut used_color: Option<ANSIStr<'static>> = None;
869
870 if border.has_left() {
871 if let Some(color) = color.left_bottom_corner {
872 used_color = Some(color);
873 buf.push_str(color.get_prefix());
874 }
875
876 let c = border.left_bottom_corner.unwrap_or(' ');
877 buf.push(c);
878 }
879
880 if let Some(color) = color.bottom {
881 match used_color {
882 Some(used) => {
883 if used != color {
884 buf.push_str(used.get_suffix());
885 buf.push_str(color.get_prefix());
886 }
887 }
888 None => {
889 buf.push_str(color.get_prefix());
890 used_color = Some(color);
891 }
892 }
893 }
894
895 let c = border.bottom.unwrap_or(' ');
896 buf.extend(repeat(c).take(width));
897
898 if border.has_right() {
899 if let Some(color) = color.right_bottom_corner {
900 match used_color {
901 Some(used) => {
902 if used != color {
903 buf.push_str(used.get_suffix());
904 buf.push_str(color.get_prefix());
905 }
906 }
907 None => {
908 buf.push_str(color.get_prefix());
909 used_color = Some(color);
910 }
911 }
912 }
913
914 let c = border.right_bottom_corner.unwrap_or(' ');
915 buf.push(c);
916 }
917
918 if let Some(used) = used_color {
919 buf.push_str(used.get_suffix());
920 }
921
922 buf.push('\n');
923 }
924
925 fn create_border<T>(borders: Borders<T>) -> Border<T> {
926 Border {
927 top: borders.top,
928 bottom: borders.bottom,
929 left: borders.left,
930 right: borders.right,
931 left_top_corner: borders.top_left,
932 left_bottom_corner: borders.bottom_left,
933 right_top_corner: borders.top_right,
934 right_bottom_corner: borders.bottom_right,
935 }
936 }
937
938 fn config_borders(borders: &mut Borders<char>, ctx: &PrintContext) {
939 // set top_left
940 {
941 if ctx.kv && ctx.kv_is_first {
942 borders.top_left = borders.top_intersection;
943 }
944
945 if ctx.kv && !ctx.kv_is_first {
946 borders.top_left = borders.intersection;
947 }
948
949 if ctx.kv && ctx.list && !ctx.list_is_first {
950 borders.top_left = borders.left_intersection;
951 }
952
953 if ctx.is_first_col && !ctx.is_first_row {
954 borders.top_left = borders.left_intersection;
955 }
956
957 if ctx.lean_top {
958 borders.top_left = borders.top_intersection;
959 }
960
961 if ctx.top_left {
962 borders.top_left = borders.left_intersection;
963 }
964
965 if ctx.top_intersection {
966 borders.top_left = borders.intersection;
967 }
968 }
969
970 if ctx.is_last_col && !ctx.is_first_row {
971 borders.top_right = borders.right_intersection;
972 }
973
974 if !ctx.is_first_col && ctx.is_last_row {
975 borders.bottom_left = borders.bottom_intersection;
976 }
977
978 if !ctx.is_last_row || ctx.no_bottom {
979 cfg_no_bottom_borders(borders);
980 }
981
982 if ctx.no_right {
983 cfg_no_right_borders(borders);
984 }
985 }
986
987 struct ConfigCell(PrintContext);
988
989 impl<R, D> TableOption<R, ColoredConfig, D> for ConfigCell {
990 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
991 {
992 // we set a horizontal lines to borders to not complicate logic with cleaning it
993
994 let mut borders = *cfg.get_borders();
995 if let Some(line) = cfg.get_horizontal_line(0) {
996 borders.top = line.main;
997 borders.top_left = line.left;
998 borders.top_right = line.right;
999 }
1000
1001 if let Some(line) = cfg.get_horizontal_line(1) {
1002 borders.bottom = line.main;
1003 borders.bottom_left = line.left;
1004 borders.bottom_right = line.right;
1005 }
1006
1007 cfg_clear_borders(cfg);
1008 cfg.set_borders(borders);
1009 }
1010
1011 let mut ctx = self.0;
1012
1013 let has_vertical = cfg.get_borders().has_left();
1014 if !ctx.intersections_horizontal.is_empty() && has_vertical {
1015 let mut splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width);
1016 squash_splits(&mut splits);
1017
1018 let c = cfg.get_borders().bottom_intersection.unwrap_or(' ');
1019 cfg_set_top_chars(cfg, &splits, c)
1020 }
1021
1022 let has_horizontal = cfg.get_borders().has_top();
1023 if !ctx.intersections_vertical.is_empty() && has_horizontal {
1024 let mut splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height);
1025 squash_splits(&mut splits);
1026
1027 let c = cfg.get_borders().right_intersection.unwrap_or(' ');
1028 cfg_set_left_chars(cfg, &splits, c)
1029 }
1030
1031 let mut borders = *cfg.get_borders();
1032
1033 // set top_left
1034 {
1035 if ctx.kv && ctx.kv_is_first {
1036 borders.top_left = borders.top_intersection;
1037 }
1038
1039 if ctx.kv && !ctx.kv_is_first {
1040 borders.top_left = borders.intersection;
1041 }
1042
1043 if ctx.kv && ctx.list && !ctx.list_is_first {
1044 borders.top_left = borders.left_intersection;
1045 }
1046
1047 if ctx.is_first_col && !ctx.is_first_row {
1048 borders.top_left = borders.left_intersection;
1049 }
1050
1051 if ctx.lean_top {
1052 borders.top_left = borders.top_intersection;
1053 }
1054
1055 if ctx.top_left {
1056 borders.top_left = borders.left_intersection;
1057 }
1058
1059 if ctx.top_intersection {
1060 borders.top_left = borders.intersection;
1061 }
1062 }
1063
1064 if ctx.is_last_col && !ctx.is_first_row {
1065 borders.top_right = borders.right_intersection;
1066 }
1067
1068 if !ctx.is_first_col && ctx.is_last_row {
1069 borders.bottom_left = borders.bottom_intersection;
1070 }
1071
1072 if !ctx.is_last_row || ctx.no_bottom {
1073 cfg_no_bottom_borders(&mut borders);
1074 }
1075
1076 if ctx.no_right {
1077 cfg_no_right_borders(&mut borders);
1078 }
1079
1080 cfg.set_borders(borders);
1081 }
1082 }
1083
1084 fn cfg_no_bottom_borders(borders: &mut Borders<char>) {
1085 borders.bottom = None;
1086 borders.bottom_intersection = None;
1087 borders.bottom_left = None;
1088 borders.bottom_right = None;
1089 borders.horizontal = None;
1090 }
1091
1092 fn cfg_no_right_borders(borders: &mut Borders<char>) {
1093 borders.right = None;
1094 borders.right_intersection = None;
1095 borders.top_right = None;
1096 borders.bottom_right = None;
1097 borders.vertical = None;
1098 }
1099
1100 fn cfg_set_top_chars(cfg: &mut ColoredConfig, list: &[usize], c: char) {
1101 for &split in list {
1102 let offset = split;
1103 cfg.set_horizontal_char((0, 0), c, Offset::Begin(offset));
1104 }
1105 }
1106
1107 fn cfg_set_left_chars(cfg: &mut ColoredConfig, list: &[usize], c: char) {
1108 for &offset in list {
1109 cfg.set_vertical_char((0, 0), c, Offset::Begin(offset));
1110 }
1111 }
1112
1113 struct NoTopBorders;
1114
1115 impl<R, D> TableOption<R, ColoredConfig, D> for NoTopBorders {
1116 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1117 let mut borders = *cfg.get_borders();
1118 borders.top = None;
1119 borders.top_intersection = None;
1120 borders.top_left = None;
1121 borders.top_right = None;
1122
1123 cfg.set_borders(borders);
1124 }
1125 }
1126
1127 struct NoBottomBorders;
1128
1129 impl<R, D> TableOption<R, ColoredConfig, D> for NoBottomBorders {
1130 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1131 let mut borders = *cfg.get_borders();
1132 borders.bottom = None;
1133 borders.bottom_intersection = None;
1134 borders.bottom_left = None;
1135 borders.bottom_right = None;
1136
1137 cfg.set_borders(borders);
1138 }
1139 }
1140
1141 struct NoRightBorders;
1142
1143 impl<R, D> TableOption<R, ColoredConfig, D> for NoRightBorders {
1144 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1145 let mut borders = *cfg.get_borders();
1146 borders.top_right = None;
1147 borders.bottom_right = None;
1148 borders.right = None;
1149 borders.right_intersection = None;
1150
1151 cfg.set_borders(borders);
1152 }
1153 }
1154
1155 struct NoLeftBorders;
1156
1157 impl<R, D> TableOption<R, ColoredConfig, D> for NoLeftBorders {
1158 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1159 let mut borders = *cfg.get_borders();
1160 borders.top_left = None;
1161 borders.bottom_left = None;
1162 borders.left = None;
1163 borders.left_intersection = None;
1164
1165 cfg.set_borders(borders);
1166 }
1167 }
1168
1169 struct TopLeftChangeTopIntersection;
1170
1171 impl<R, D> TableOption<R, ColoredConfig, D> for TopLeftChangeTopIntersection {
1172 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1173 let mut borders = *cfg.get_borders();
1174 borders.top_left = borders.top_intersection;
1175
1176 cfg.set_borders(borders);
1177 }
1178 }
1179
1180 struct TopLeftChangeIntersection;
1181
1182 impl<R, D> TableOption<R, ColoredConfig, D> for TopLeftChangeIntersection {
1183 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1184 let mut borders = *cfg.get_borders();
1185 borders.top_left = borders.intersection;
1186
1187 cfg.set_borders(borders);
1188 }
1189 }
1190
1191 struct TopLeftChangeToLeft;
1192
1193 impl<R, D> TableOption<R, ColoredConfig, D> for TopLeftChangeToLeft {
1194 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1195 let mut borders = *cfg.get_borders();
1196 borders.top_left = borders.left_intersection;
1197
1198 cfg.set_borders(borders);
1199 }
1200 }
1201
1202 struct TopRightChangeToRight;
1203
1204 impl<R, D> TableOption<R, ColoredConfig, D> for TopRightChangeToRight {
1205 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1206 let mut borders = *cfg.get_borders();
1207 borders.top_right = borders.right_intersection;
1208
1209 cfg.set_borders(borders);
1210 }
1211 }
1212
1213 struct BottomLeftChangeSplit;
1214
1215 impl<R, D> TableOption<R, ColoredConfig, D> for BottomLeftChangeSplit {
1216 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1217 let mut borders = *cfg.get_borders();
1218 borders.bottom_left = borders.left_intersection;
1219
1220 cfg.set_borders(borders);
1221 }
1222 }
1223
1224 struct BottomLeftChangeSplitToIntersection;
1225
1226 impl<R, D> TableOption<R, ColoredConfig, D> for BottomLeftChangeSplitToIntersection {
1227 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1228 let mut borders = *cfg.get_borders();
1229 borders.bottom_left = borders.intersection;
1230
1231 cfg.set_borders(borders);
1232 }
1233 }
1234
1235 struct BottomRightChangeToRight;
1236
1237 impl<R, D> TableOption<R, ColoredConfig, D> for BottomRightChangeToRight {
1238 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1239 let mut borders = *cfg.get_borders();
1240 borders.bottom_right = borders.right_intersection;
1241
1242 cfg.set_borders(borders);
1243 }
1244 }
1245
1246 struct BottomLeftChangeToBottomIntersection;
1247
1248 impl<R, D> TableOption<R, ColoredConfig, D> for BottomLeftChangeToBottomIntersection {
1249 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1250 let mut borders = *cfg.get_borders();
1251 borders.bottom_left = borders.bottom_intersection;
1252
1253 cfg.set_borders(borders);
1254 }
1255 }
1256
1257 struct SetBottomChars<'a>(&'a [usize], char);
1258
1259 impl<R, D> TableOption<R, ColoredConfig, D> for SetBottomChars<'_>
1260 where
1261 R: Records,
1262 for<'a> &'a R: Records,
1263 for<'a> D: Dimension + Estimate<&'a R, ColoredConfig>,
1264 {
1265 fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) {
1266 dims.estimate(&*records, cfg);
1267
1268 let table_width = (0..records.count_columns())
1269 .map(|col| dims.get_width(col))
1270 .sum::<usize>()
1271 + cfg.count_vertical(records.count_columns());
1272 let mut current_width = 0;
1273
1274 for pos in self.0 {
1275 current_width += pos;
1276 if current_width > table_width {
1277 break;
1278 }
1279
1280 let split_char = self.1;
1281 cfg.set_horizontal_char((1, 0), split_char, Offset::Begin(current_width));
1282
1283 current_width += 1;
1284 }
1285 }
1286 }
1287
1288 struct SetTopChars<'a>(&'a [usize], char);
1289
1290 impl<R, D> TableOption<R, ColoredConfig, D> for SetTopChars<'_> {
1291 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1292 for &split in self.0 {
1293 let offset = split;
1294 cfg.set_horizontal_char((0, 0), self.1, Offset::Begin(offset));
1295 }
1296 }
1297 }
1298
1299 struct SetLeftChars<'a>(&'a [usize], char);
1300
1301 impl<R, D> TableOption<R, ColoredConfig, D> for SetLeftChars<'_> {
1302 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1303 for &offset in self.0 {
1304 cfg.set_vertical_char((0, 0), self.1, Offset::Begin(offset));
1305 }
1306 }
1307 }
1308
1309 struct GetTopIntersection(char);
1310
1311 impl<R, D> TableOption<R, ColoredConfig, D> for &mut GetTopIntersection {
1312 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1313 self.0 = cfg.get_borders().top_intersection.unwrap_or(' ');
1314 }
1315 }
1316
1317 struct GetBottomIntersection(char);
1318
1319 impl<R, D> TableOption<R, ColoredConfig, D> for &mut GetBottomIntersection {
1320 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
1321 self.0 = cfg.get_borders().bottom_intersection.unwrap_or(' ');
1322 }
1323 }
1324
1325 #[derive(Debug, Default)]
1326 struct Dimensions {
1327 all: HashMap<usize, Dim>,
1328 arrays: HashMap<usize, ArrayDimensions>,
1329 }
1330
1331 #[derive(Debug, Default, Clone, Copy)]
1332 struct Dim {
1333 width: usize,
1334 height: usize,
1335 }
1336
1337 impl Dim {
1338 fn new(width: usize, height: usize) -> Self {
1339 Self { width, height }
1340 }
1341 }
1342
1343 #[derive(Debug, Default)]
1344 struct ArrayDimensions {
1345 max: Dim,
1346 index: HashMap<usize, usize>,
1347 }
1348
1349 fn collect_table_dimensions(val: &TableValue, cfg: &CompactMultilineConfig) -> Dimensions {
1350 let mut buf = Dimensions::default();
1351 let (dim, _) = __collect_table_dims(&mut buf, val, cfg, 0);
1352 let _ = buf.all.insert(0, dim);
1353 buf
1354 }
1355
1356 fn __collect_table_dims(
1357 buf: &mut Dimensions,
1358 val: &TableValue,
1359 cfg: &CompactMultilineConfig,
1360 pos: usize,
1361 ) -> (Dim, usize) {
1362 match val {
1363 TableValue::Cell(text) => (str_dimension(text, cfg), 0),
1364 TableValue::Row(list) => {
1365 if list.is_empty() {
1366 return (empty_dimension(cfg), 0);
1367 }
1368
1369 let mut index = ArrayDimensions {
1370 max: Dim::default(),
1371 index: HashMap::with_capacity(list.len()),
1372 };
1373
1374 let mut total_width = 0;
1375
1376 let mut count_elements = list.len();
1377 let mut val_pos = pos + 1;
1378 for (i, value) in list.iter().enumerate() {
1379 let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos);
1380 count_elements += elements;
1381
1382 total_width += dim.width;
1383
1384 index.max.width = max(index.max.width, dim.width);
1385 index.max.height = max(index.max.height, dim.height);
1386
1387 let _ = buf.all.insert(val_pos, dim);
1388
1389 let _ = index.index.insert(i, val_pos);
1390
1391 val_pos += 1 + elements;
1392 }
1393
1394 let max_height = index.max.height;
1395
1396 let _ = buf.arrays.insert(pos, index);
1397
1398 let has_vertical = cfg.get_borders().has_left();
1399 total_width += has_vertical as usize * (list.len() - 1);
1400
1401 (Dim::new(total_width, max_height), count_elements)
1402 }
1403 TableValue::Column(list) => {
1404 if list.is_empty() {
1405 return (empty_dimension(cfg), 0);
1406 }
1407
1408 let mut index = ArrayDimensions {
1409 max: Dim::default(),
1410 index: HashMap::with_capacity(list.len()),
1411 };
1412
1413 let mut total_height = 0;
1414
1415 let mut count_elements = list.len();
1416 let mut val_pos = pos + 1;
1417 for (i, value) in list.iter().enumerate() {
1418 let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos);
1419 count_elements += elements;
1420
1421 total_height += dim.height;
1422
1423 index.max.width = max(index.max.width, dim.width);
1424 index.max.height = max(index.max.height, dim.height);
1425
1426 let _ = buf.all.insert(val_pos, dim);
1427
1428 let _ = index.index.insert(i, val_pos);
1429
1430 val_pos += 1 + elements;
1431 }
1432
1433 let max_width = index.max.width;
1434
1435 let _ = buf.arrays.insert(pos, index);
1436
1437 let has_horizontal = cfg.get_borders().has_top();
1438 total_height += has_horizontal as usize * (list.len() - 1);
1439
1440 (Dim::new(max_width, total_height), count_elements)
1441 }
1442 }
1443 }
1444
1445 fn empty_dimension(cfg: &CompactMultilineConfig) -> Dim {
1446 Dim::new(get_padding_horizontal(cfg), 1 + get_padding_vertical(cfg))
1447 }
1448
1449 fn str_dimension(text: &str, cfg: &CompactMultilineConfig) -> Dim {
1450 let (count_lines, width) = string_dimension(text);
1451 let w = width + get_padding_horizontal(cfg);
1452 let h = count_lines + get_padding_vertical(cfg);
1453 Dim::new(w, h)
1454 }
1455
1456 fn get_padding_horizontal(cfg: &CompactMultilineConfig) -> usize {
1457 let pad = cfg.get_padding();
1458 pad.left.size + pad.right.size
1459 }
1460
1461 fn get_padding_vertical(cfg: &CompactMultilineConfig) -> usize {
1462 let pad = cfg.get_padding();
1463 pad.top.size + pad.bottom.size
1464 }
1465
1466 fn split_value(value: usize, by: usize) -> (usize, usize) {
1467 let val = value / by;
1468 let rest = value - val * by;
1469 (val, rest)
1470 }
1471
1472 fn indent_vertical(al: AlignmentVertical, available: usize, real: usize) -> (usize, usize) {
1473 let top = indent_top(al, available, real);
1474 let bottom = available - real - top;
1475 (top, bottom)
1476 }
1477
1478 fn indent_horizontal(al: AlignmentHorizontal, available: usize, real: usize) -> (usize, usize) {
1479 let top = indent_left(al, available, real);
1480 let right = available - real - top;
1481 (top, right)
1482 }
1483
1484 fn indent_top(al: AlignmentVertical, available: usize, real: usize) -> usize {
1485 match al {
1486 AlignmentVertical::Top => 0,
1487 AlignmentVertical::Bottom => available - real,
1488 AlignmentVertical::Center => (available - real) / 2,
1489 }
1490 }
1491
1492 fn indent_left(al: AlignmentHorizontal, available: usize, real: usize) -> usize {
1493 match al {
1494 AlignmentHorizontal::Left => 0,
1495 AlignmentHorizontal::Right => available - real,
1496 AlignmentHorizontal::Center => (available - real) / 2,
1497 }
1498 }
1499
1500 fn short_splits(splits: &mut Vec<usize>, width: usize) -> Vec<usize> {
1501 if splits.is_empty() {
1502 return Vec::new();
1503 }
1504
1505 let mut out = Vec::new();
1506 let mut pos = 0;
1507 for &split in splits.iter() {
1508 if pos + split >= width {
1509 break;
1510 }
1511
1512 pos += split;
1513 out.push(pos);
1514 }
1515
1516 let _ = splits.drain(..out.len());
1517
1518 if !splits.is_empty() && pos <= width {
1519 let rest = width - pos;
1520 splits[0] -= rest;
1521 }
1522
1523 out
1524 }
1525
1526 fn short_splits3(splits: &mut Vec<usize>, width: usize) -> (bool, Vec<usize>) {
1527 if splits.is_empty() {
1528 return (false, Vec::new());
1529 }
1530
1531 let mut out = Vec::new();
1532 let mut pos = 0;
1533 for &split in splits.iter() {
1534 if pos + split >= width {
1535 break;
1536 }
1537
1538 pos += split + 1;
1539 out.push(split);
1540 }
1541
1542 let _ = splits.drain(..out.len());
1543
1544 if splits.is_empty() {
1545 return (false, out);
1546 }
1547
1548 if pos <= width {
1549 splits[0] -= width - pos;
1550 if splits[0] > 0 {
1551 splits[0] -= 1;
1552 } else {
1553 let _ = splits.remove(0);
1554 return (true, out);
1555 }
1556 }
1557
1558 (false, out)
1559 }
1560
1561 fn squash_splits(splits: &mut [usize]) {
1562 splits.iter_mut().enumerate().for_each(|(i, s)| *s += i);
1563 }
1564
1565 fn set_margin(
1566 table: &str,
1567 margin: Sides<Indent>,
1568 color: Sides<Option<ANSIStr<'static>>>,
1569 ) -> String {
1570 if table.is_empty() {
1571 return String::new();
1572 }
1573
1574 let mut buf = String::new();
1575 let width = string_width_multiline(table);
1576 let top_color = color.top;
1577 let bottom_color = color.bottom;
1578 let left_color = color.left;
1579 let right_color = color.right;
1580 for _ in 0..margin.top.size {
1581 print_chars(&mut buf, margin.left.fill, left_color, margin.left.size);
1582 print_chars(&mut buf, margin.top.fill, top_color, width);
1583 print_chars(&mut buf, margin.right.fill, right_color, margin.right.size);
1584 buf.push('\n');
1585 }
1586
1587 for line in get_lines(table) {
1588 print_chars(&mut buf, margin.left.fill, left_color, margin.left.size);
1589 buf.push_str(&line);
1590 print_chars(&mut buf, margin.right.fill, right_color, margin.right.size);
1591 buf.push('\n');
1592 }
1593
1594 for _ in 0..margin.bottom.size {
1595 print_chars(&mut buf, margin.left.fill, left_color, margin.left.size);
1596 print_chars(&mut buf, margin.bottom.fill, bottom_color, width);
1597 print_chars(&mut buf, margin.right.fill, right_color, margin.right.size);
1598 buf.push('\n');
1599 }
1600
1601 let _ = buf.remove(buf.len() - 1);
1602
1603 buf
1604 }
1605
1606 fn convert_border_colors(
1607 pad_color: Sides<ANSIStr<'static>>,
1608 ) -> Sides<Option<ANSIStr<'static>>> {
1609 Sides::new(
1610 (!pad_color.left.is_empty()).then(|| pad_color.left),
1611 (!pad_color.right.is_empty()).then(|| pad_color.right),
1612 (!pad_color.top.is_empty()).then(|| pad_color.top),
1613 (!pad_color.bottom.is_empty()).then(|| pad_color.bottom),
1614 )
1615 }
1616
1617 fn cfg_clear_borders(cfg: &mut ColoredConfig) {
1618 cfg.remove_borders();
1619 cfg.remove_borders_colors();
1620 cfg.remove_vertical_chars();
1621 cfg.remove_horizontal_chars();
1622 cfg.remove_color_line_horizontal();
1623 cfg.remove_color_line_vertical();
1624 }
1625}
1626