comfy_table/utils/formatting/
borders.rs

1use crate::style::TableComponent;
2use crate::table::Table;
3use crate::utils::ColumnDisplayInfo;
4
5pub(crate) fn draw_borders(
6    table: &Table,
7    rows: &[Vec<Vec<String>>],
8    display_info: &[ColumnDisplayInfo],
9) -> Vec<String> {
10    // We know how many lines there should be. Initialize the vector with the rough correct amount.
11    // We might over allocate a bit, but that's better than under allocating.
12    let mut lines = if let Some(capacity) = rows.first().map(|lines| lines.len()) {
13        // Lines * 2 -> Lines + delimiters
14        // + 5 -> header delimiters + header + bottom/top borders
15        Vec::with_capacity(capacity * 2 + 5)
16    } else {
17        Vec::new()
18    };
19
20    if should_draw_top_border(table) {
21        lines.push(draw_top_border(table, display_info));
22    }
23
24    draw_rows(&mut lines, rows, table, display_info);
25
26    if should_draw_bottom_border(table) {
27        lines.push(draw_bottom_border(table, display_info));
28    }
29
30    lines
31}
32
33fn draw_top_border(table: &Table, display_info: &[ColumnDisplayInfo]) -> String {
34    let left_corner = table.style_or_default(TableComponent::TopLeftCorner);
35    let top_border = table.style_or_default(TableComponent::TopBorder);
36    let intersection = table.style_or_default(TableComponent::TopBorderIntersections);
37    let right_corner = table.style_or_default(TableComponent::TopRightCorner);
38
39    let mut line = String::new();
40    // We only need the top left corner, if we need to draw a left border
41    if should_draw_left_border(table) {
42        line += &left_corner;
43    }
44
45    // Build the top border line depending on the columns' width.
46    // Also add the border intersections.
47    let mut first = true;
48    for info in display_info.iter() {
49        // Only add something, if the column isn't hidden
50        if !info.is_hidden {
51            if !first {
52                line += &intersection;
53            }
54            line += &top_border.repeat(info.width().into());
55            first = false;
56        }
57    }
58
59    // We only need the top right corner, if we need to draw a right border
60    if should_draw_right_border(table) {
61        line += &right_corner;
62    }
63
64    line
65}
66
67fn draw_rows(
68    lines: &mut Vec<String>,
69    rows: &[Vec<Vec<String>>],
70    table: &Table,
71    display_info: &[ColumnDisplayInfo],
72) {
73    // Iterate over all rows
74    let mut row_iter = rows.iter().enumerate().peekable();
75    while let Some((row_index, row)) = row_iter.next() {
76        // Concatenate the line parts and insert the vertical borders if needed
77        for line_parts in row.iter() {
78            lines.push(embed_line(line_parts, table));
79        }
80
81        // Draw the horizontal header line if desired, otherwise continue to the next iteration
82        if row_index == 0 && table.header.is_some() {
83            if should_draw_header(table) {
84                lines.push(draw_horizontal_lines(table, display_info, true));
85            }
86            continue;
87        }
88
89        // Draw a horizontal line, if we desired and if we aren't in the last row of the table.
90        if row_iter.peek().is_some() && should_draw_horizontal_lines(table) {
91            lines.push(draw_horizontal_lines(table, display_info, false));
92        }
93    }
94}
95
96// Takes the parts of a single line, surrounds them with borders and adds vertical lines.
97fn embed_line(line_parts: &[String], table: &Table) -> String {
98    let vertical_lines = table.style_or_default(TableComponent::VerticalLines);
99    let left_border = table.style_or_default(TableComponent::LeftBorder);
100    let right_border = table.style_or_default(TableComponent::RightBorder);
101
102    let mut line = String::new();
103    if should_draw_left_border(table) {
104        line += &left_border;
105    }
106
107    let mut part_iter = line_parts.iter().peekable();
108    while let Some(part) = part_iter.next() {
109        line += part;
110        if should_draw_vertical_lines(table) && part_iter.peek().is_some() {
111            line += &vertical_lines;
112        } else if should_draw_right_border(table) && part_iter.peek().is_none() {
113            line += &right_border;
114        }
115    }
116
117    line
118}
119
120// The horizontal line that separates between rows.
121fn draw_horizontal_lines(
122    table: &Table,
123    display_info: &[ColumnDisplayInfo],
124    header: bool,
125) -> String {
126    // Styling depends on whether we're currently on the header line or not.
127    let (left_intersection, horizontal_lines, middle_intersection, right_intersection) = if header {
128        (
129            table.style_or_default(TableComponent::LeftHeaderIntersection),
130            table.style_or_default(TableComponent::HeaderLines),
131            table.style_or_default(TableComponent::MiddleHeaderIntersections),
132            table.style_or_default(TableComponent::RightHeaderIntersection),
133        )
134    } else {
135        (
136            table.style_or_default(TableComponent::LeftBorderIntersections),
137            table.style_or_default(TableComponent::HorizontalLines),
138            table.style_or_default(TableComponent::MiddleIntersections),
139            table.style_or_default(TableComponent::RightBorderIntersections),
140        )
141    };
142
143    let mut line = String::new();
144    // We only need the bottom left corner, if we need to draw a left border
145    if should_draw_left_border(table) {
146        line += &left_intersection;
147    }
148
149    // Append the middle lines depending on the columns' widths.
150    // Also add the middle intersections.
151    let mut first = true;
152    for info in display_info.iter() {
153        // Only add something, if the column isn't hidden
154        if !info.is_hidden {
155            if !first {
156                line += &middle_intersection;
157            }
158            line += &horizontal_lines.repeat(info.width().into());
159            first = false;
160        }
161    }
162
163    // We only need the bottom right corner, if we need to draw a right border
164    if should_draw_right_border(table) {
165        line += &right_intersection;
166    }
167
168    line
169}
170
171fn draw_bottom_border(table: &Table, display_info: &[ColumnDisplayInfo]) -> String {
172    let left_corner = table.style_or_default(TableComponent::BottomLeftCorner);
173    let bottom_border = table.style_or_default(TableComponent::BottomBorder);
174    let middle_intersection = table.style_or_default(TableComponent::BottomBorderIntersections);
175    let right_corner = table.style_or_default(TableComponent::BottomRightCorner);
176
177    let mut line = String::new();
178    // We only need the bottom left corner, if we need to draw a left border
179    if should_draw_left_border(table) {
180        line += &left_corner;
181    }
182
183    // Add the bottom border lines depending on column width
184    // Also add the border intersections.
185    let mut first = true;
186    for info in display_info.iter() {
187        // Only add something, if the column isn't hidden
188        if !info.is_hidden {
189            if !first {
190                line += &middle_intersection;
191            }
192            line += &bottom_border.repeat(info.width().into());
193            first = false;
194        }
195    }
196
197    // We only need the bottom right corner, if we need to draw a right border
198    if should_draw_right_border(table) {
199        line += &right_corner;
200    }
201
202    line
203}
204
205fn should_draw_top_border(table: &Table) -> bool {
206    if table.style_exists(TableComponent::TopLeftCorner)
207        || table.style_exists(TableComponent::TopBorder)
208        || table.style_exists(TableComponent::TopBorderIntersections)
209        || table.style_exists(TableComponent::TopRightCorner)
210    {
211        return true;
212    }
213
214    false
215}
216
217fn should_draw_bottom_border(table: &Table) -> bool {
218    if table.style_exists(TableComponent::BottomLeftCorner)
219        || table.style_exists(TableComponent::BottomBorder)
220        || table.style_exists(TableComponent::BottomBorderIntersections)
221        || table.style_exists(TableComponent::BottomRightCorner)
222    {
223        return true;
224    }
225
226    false
227}
228
229pub fn should_draw_left_border(table: &Table) -> bool {
230    if table.style_exists(TableComponent::TopLeftCorner)
231        || table.style_exists(TableComponent::LeftBorder)
232        || table.style_exists(TableComponent::LeftBorderIntersections)
233        || table.style_exists(TableComponent::LeftHeaderIntersection)
234        || table.style_exists(TableComponent::BottomLeftCorner)
235    {
236        return true;
237    }
238
239    false
240}
241
242pub fn should_draw_right_border(table: &Table) -> bool {
243    if table.style_exists(TableComponent::TopRightCorner)
244        || table.style_exists(TableComponent::RightBorder)
245        || table.style_exists(TableComponent::RightBorderIntersections)
246        || table.style_exists(TableComponent::RightHeaderIntersection)
247        || table.style_exists(TableComponent::BottomRightCorner)
248    {
249        return true;
250    }
251
252    false
253}
254
255fn should_draw_horizontal_lines(table: &Table) -> bool {
256    if table.style_exists(TableComponent::LeftBorderIntersections)
257        || table.style_exists(TableComponent::HorizontalLines)
258        || table.style_exists(TableComponent::MiddleIntersections)
259        || table.style_exists(TableComponent::RightBorderIntersections)
260    {
261        return true;
262    }
263
264    false
265}
266
267pub fn should_draw_vertical_lines(table: &Table) -> bool {
268    if table.style_exists(TableComponent::TopBorderIntersections)
269        || table.style_exists(TableComponent::MiddleHeaderIntersections)
270        || table.style_exists(TableComponent::VerticalLines)
271        || table.style_exists(TableComponent::MiddleIntersections)
272        || table.style_exists(TableComponent::BottomBorderIntersections)
273    {
274        return true;
275    }
276
277    false
278}
279
280fn should_draw_header(table: &Table) -> bool {
281    if table.style_exists(TableComponent::LeftHeaderIntersection)
282        || table.style_exists(TableComponent::HeaderLines)
283        || table.style_exists(TableComponent::MiddleHeaderIntersections)
284        || table.style_exists(TableComponent::RightHeaderIntersection)
285    {
286        return true;
287    }
288
289    false
290}