box_drawing_table/
table.rs

1use super::*;
2
3#[derive(Debug, Clone, PartialEq)]
4pub struct Table {
5    cols: Vec<Column>,
6    rows: Vec<Row>,
7}
8
9impl Table {
10    pub fn new(columns: Vec<Column>) -> Self {
11        Self {
12            cols: columns,
13            rows: Vec::new(),
14        }
15    }
16    pub fn append_row(&mut self, row: Row) {
17        self.rows.push(row);
18    }
19
20    fn get_border(
21        &self,
22        row_idx: usize,
23        col_idx: usize,
24        horizontal: Option<Border>,
25        vertical: Option<Border>,
26    ) -> &str {
27        match (horizontal, vertical) {
28            (None, None) => panic!(""),
29            (None, Some(vertical)) => match vertical {
30                Border::Single => "│",
31                Border::Double => "║",
32            },
33            (Some(horizontal), None) => match horizontal {
34                Border::Single => "─",
35                Border::Double => "═",
36            },
37            (Some(horizontal), Some(vertical)) => {
38                // this represents the existence of adjacent borders
39                let mut adjacent_border = 0;
40                if row_idx > 0 {
41                    adjacent_border |= 0b0001; // up
42                }
43                if row_idx + 1 < self.rows.len() {
44                    adjacent_border |= 0b0100; // right
45                }
46                if col_idx > 0 {
47                    adjacent_border |= 0b1000; // down
48                }
49                if col_idx + 1 < self.cols.len() {
50                    adjacent_border |= 0b0010; // left
51                }
52
53                use Border::*;
54                match adjacent_border {
55                    0b0110 => match (horizontal, vertical) {
56                        (Single, Single) => "┌",
57                        (Double, Double) => "╔",
58                        (Single, Double) => "╓",
59                        (Double, Single) => "╒",
60                    },
61                    0b1110 => match (horizontal, vertical) {
62                        (Single, Single) => "┬",
63                        (Double, Double) => "╦",
64                        (Single, Double) => "╥",
65                        (Double, Single) => "╤",
66                    },
67                    0b1100 => match (horizontal, vertical) {
68                        (Single, Single) => "┐",
69                        (Double, Double) => "╗",
70                        (Single, Double) => "╖",
71                        (Double, Single) => "╕",
72                    },
73                    0b0111 => match (horizontal, vertical) {
74                        (Single, Single) => "├",
75                        (Double, Double) => "╠",
76                        (Single, Double) => "╟",
77                        (Double, Single) => "╞",
78                    },
79                    0b1111 => match (horizontal, vertical) {
80                        (Single, Single) => "┼",
81                        (Double, Double) => "╬",
82                        (Single, Double) => "╫",
83                        (Double, Single) => "╪",
84                    },
85                    0b1101 => match (horizontal, vertical) {
86                        (Single, Single) => "┤",
87                        (Double, Double) => "╣",
88                        (Single, Double) => "╢",
89                        (Double, Single) => "╡",
90                    },
91                    0b0011 => match (horizontal, vertical) {
92                        (Single, Single) => "└",
93                        (Double, Double) => "╚",
94                        (Single, Double) => "╙",
95                        (Double, Single) => "╘",
96                    },
97                    0b1011 => match (horizontal, vertical) {
98                        (Single, Single) => "┴",
99                        (Double, Double) => "╩",
100                        (Single, Double) => "╨",
101                        (Double, Single) => "╧",
102                    },
103                    0b1001 => match (horizontal, vertical) {
104                        (Single, Single) => "┘",
105                        (Double, Double) => "╝",
106                        (Single, Double) => "╜",
107                        (Double, Single) => "╛",
108                    },
109                    0b0010 | 0b1000 => match (horizontal, vertical) {
110                        (Single, _) => "─",
111                        (Double, _) => "═",
112                    },
113                    0b0001 | 0b0100 => match (horizontal, vertical) {
114                        (_, Single) => "│",
115                        (_, Double) => "║",
116                    },
117                    _ => unimplemented!(),
118                }
119            }
120        }
121    }
122}
123
124fn fill(s: &str, width: usize) -> String {
125    use unicode_width::UnicodeWidthStr;
126    let s_width = s.width();
127    assert!(width % s_width == 0);
128    let times = width / s_width;
129    s.repeat(times)
130}
131
132impl std::fmt::Display for Table {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        use unicode_width::UnicodeWidthStr;
135
136        // 1. calculate each column width.
137        let widths: Vec<usize> = {
138            let mut ws = Vec::new();
139            let mut cell_idx = 0;
140            for col in self.cols.iter() {
141                match col {
142                    Column::VerticalBorder(_) => ws.push(1),
143                    Column::Cells { width } => {
144                        match width {
145                            CellSize::Flexible => {
146                                // maximum width of all rows
147                                let max_width = self
148                                    .rows
149                                    .iter()
150                                    .filter_map(|row| {
151                                        row.cells().and_then(|cells| cells.get(cell_idx))
152                                    })
153                                    .map(|cell| cell.value.width() + cell.align.padding_size())
154                                    .fold(1 /* defualt width*/, std::cmp::max);
155                                ws.push(max_width);
156                            }
157                            CellSize::Fixed(w) => ws.push(*w),
158                        }
159                        cell_idx += 1;
160                    }
161                }
162            }
163            ws
164        };
165
166        // 2. render each row
167        for (ri, row) in self.rows.iter().enumerate() {
168            match row {
169                Row::HorizontalBorder(_) => {
170                    for (ci, col) in self.cols.iter().enumerate() {
171                        let c = self.get_border(ri, ci, row.border(), col.border());
172                        write!(f, "{}", fill(c, widths[ci]))?;
173                    }
174                    writeln!(f)?;
175                }
176                Row::Cells { height, cells } => {
177                    use std::borrow::Cow;
178                    enum ActualContent<'a> {
179                        Border(String),
180                        Text(Vec<Cow<'a, str>>),
181                    }
182                    struct ActualCell<'a> {
183                        width: usize,
184                        align: Align,
185                        style: ansi_term::Style,
186                        content: ActualContent<'a>,
187                    }
188
189                    let cells: Vec<ActualCell> = {
190                        let mut buf = Vec::new();
191                        let mut cells_iter = cells.iter();
192                        for (ci, col) in self.cols.iter().enumerate() {
193                            match col {
194                                Column::Cells { .. } => match cells_iter.next() {
195                                    Some(cell) => {
196                                        let width = widths[ci];
197                                        let wrap_opts = textwrap::Options::with_splitter(
198                                            width - cell.align.padding_size(),
199                                            textwrap::NoHyphenation,
200                                        );
201                                        buf.push(ActualCell {
202                                            width: width + cell.align.padding_size(),
203                                            align: cell.align,
204                                            style: cell.style,
205                                            content: ActualContent::Text(textwrap::wrap(
206                                                cell.value.as_str(),
207                                                wrap_opts,
208                                            )),
209                                        });
210                                    }
211                                    None => {
212                                        // empty cell
213                                        buf.push(ActualCell {
214                                            width: widths[ci],
215                                            align: Align::default(),
216                                            style: ansi_term::Style::default(),
217                                            content: ActualContent::Text(Vec::new()),
218                                        });
219                                    }
220                                },
221                                Column::VerticalBorder(b) => {
222                                    let border_str = self.get_border(ri, ci, None, Some(*b));
223                                    let repeated = fill(border_str, widths[ci]);
224                                    let align = Align::default();
225                                    buf.push(ActualCell {
226                                        width: widths[ci] + align.padding_size(),
227                                        align: Align::default(),
228                                        style: ansi_term::Style::default(),
229                                        content: ActualContent::Border(repeated),
230                                    });
231                                }
232                            }
233                        }
234                        buf
235                    };
236
237                    let height = match height {
238                        CellSize::Flexible => {
239                            // maximum width of all columns
240                            cells
241                                .iter()
242                                .filter_map(|actual| match &actual.content {
243                                    ActualContent::Text(text) => Some(text.len()),
244                                    _ => None,
245                                })
246                                .fold(1, std::cmp::max)
247                        }
248                        CellSize::Fixed(h) => *h,
249                    };
250
251                    let mut lines = vec![String::new(); height];
252                    for actual in cells {
253                        match actual.content {
254                            ActualContent::Text(text) => {
255                                let text =
256                                    text.into_iter().chain(std::iter::repeat(Cow::Borrowed("")));
257                                for (buf, w) in lines.iter_mut().zip(text) {
258                                    let sz = w.as_ref().width();
259                                    let pad = actual.width - actual.align.padding_size() - sz;
260                                    let (padl, padr) = match actual.align {
261                                        Align::Center => (pad / 2, pad - pad / 2),
262                                        Align::Left => (0, pad),
263                                        Align::Right => (pad, 0),
264                                        Align::CenterPadded { padl, padr } => {
265                                            (padl + (pad - padl - padr), padr)
266                                        }
267                                        Align::LeftPadded { padl } => (padl, pad - padl),
268                                        Align::RightPadded { padr } => (pad - padr, padr),
269                                    };
270                                    assert_eq!(pad, padl + padr);
271                                    buf.push_str(&fill(" ", padl));
272                                    buf.push_str(&actual.style.paint(w.as_ref()).to_string());
273                                    buf.push_str(&fill(" ", padr));
274                                }
275                            }
276                            ActualContent::Border(border) => {
277                                for buf in lines.iter_mut() {
278                                    buf.push_str(
279                                        &actual
280                                            .style
281                                            .paint(fill(&border, actual.width))
282                                            .to_string(),
283                                    );
284                                }
285                            }
286                        }
287                    }
288                    for line in lines {
289                        writeln!(f, "{}", line)?;
290                    }
291                }
292            }
293        }
294        Ok(())
295    }
296}