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