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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
110pub struct PoolTable {
111 config: CompactMultilineConfig,
112 dims: PoolTableDimension,
113 value: TableValue,
114}
115
116impl PoolTable {
117 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 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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
202pub enum TableValue {
203 Row(Vec<TableValue>),
205 Column(Vec<TableValue>),
207 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; }
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; }
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 {
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}