1 | //! This module contains [`BorderSpanCorrection`] structure, which can be useful when [`Span`] is used, and |
2 | //! you want to fix the intersections symbols which are left intact by default. |
3 | //! |
4 | //! [`Span`]: crate::settings::span::Span |
5 | |
6 | use crate::{ |
7 | grid::{ |
8 | config::{ColoredConfig, Position, SpannedConfig}, |
9 | records::{ExactRecords, Records}, |
10 | }, |
11 | settings::TableOption, |
12 | }; |
13 | |
14 | /// A correctness function of style for [`Table`] which has [`Span`]s. |
15 | /// |
16 | /// Try to fix the style when table contains spans. |
17 | /// |
18 | /// By default [`Style`] doesn't implies any logic to better render split lines when |
19 | /// [`Span`] is used. |
20 | /// |
21 | /// So this function can be used to set the split lines in regard of spans used. |
22 | /// |
23 | /// # Example |
24 | /// |
25 | /// ``` |
26 | /// use tabled::{ |
27 | /// Table, |
28 | /// settings::{ |
29 | /// Modify, style::{Style, BorderSpanCorrection}, |
30 | /// Format, Span, object::Cell |
31 | /// } |
32 | /// }; |
33 | /// |
34 | /// let data = vec![ |
35 | /// ("09" , "June" , "2022" ), |
36 | /// ("10" , "July" , "2022" ), |
37 | /// ]; |
38 | /// |
39 | /// let mut table = Table::new(data); |
40 | /// table.with(Modify::new((0, 0)).with("date" ).with(Span::column(3))); |
41 | /// |
42 | /// assert_eq!( |
43 | /// table.to_string(), |
44 | /// concat!( |
45 | /// "+----+------+------+ \n" , |
46 | /// "| date | \n" , |
47 | /// "+----+------+------+ \n" , |
48 | /// "| 09 | June | 2022 | \n" , |
49 | /// "+----+------+------+ \n" , |
50 | /// "| 10 | July | 2022 | \n" , |
51 | /// "+----+------+------+" , |
52 | /// ) |
53 | /// ); |
54 | /// |
55 | /// table.with(BorderSpanCorrection); |
56 | /// |
57 | /// assert_eq!( |
58 | /// table.to_string(), |
59 | /// concat!( |
60 | /// "+------------------+ \n" , |
61 | /// "| date | \n" , |
62 | /// "+----+------+------+ \n" , |
63 | /// "| 09 | June | 2022 | \n" , |
64 | /// "+----+------+------+ \n" , |
65 | /// "| 10 | July | 2022 | \n" , |
66 | /// "+----+------+------+" , |
67 | /// ) |
68 | /// ); |
69 | /// ``` |
70 | /// See [`BorderSpanCorrection`]. |
71 | /// |
72 | /// [`Table`]: crate::Table |
73 | /// [`Span`]: crate::settings::span::Span |
74 | /// [`Style`]: crate::settings::Style |
75 | /// [`Style::correct_spans`]: crate::settings::style::BorderSpanCorrection |
76 | #[derive (Debug)] |
77 | pub struct BorderSpanCorrection; |
78 | |
79 | impl<R, D> TableOption<R, D, ColoredConfig> for BorderSpanCorrection |
80 | where |
81 | R: Records + ExactRecords, |
82 | { |
83 | fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
84 | let shape: (usize, usize) = (records.count_rows(), records.count_columns()); |
85 | correct_span_styles(cfg, shape); |
86 | } |
87 | } |
88 | |
89 | fn correct_span_styles(cfg: &mut SpannedConfig, shape: (usize, usize)) { |
90 | for ((row, c), span) in cfg.get_column_spans() { |
91 | for col in c..c + span { |
92 | if col == 0 { |
93 | continue; |
94 | } |
95 | |
96 | let is_first = col == c; |
97 | let has_up = row > 0 && has_left(cfg, (row - 1, col), shape); |
98 | let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape); |
99 | |
100 | let borders = cfg.get_borders(); |
101 | |
102 | let mut border = cfg.get_border((row, col), shape); |
103 | |
104 | let has_top_border = border.left_top_corner.is_some() && border.top.is_some(); |
105 | if has_top_border { |
106 | if has_up && is_first { |
107 | border.left_top_corner = borders.intersection; |
108 | } else if has_up { |
109 | border.left_top_corner = borders.bottom_intersection; |
110 | } else if is_first { |
111 | border.left_top_corner = borders.top_intersection; |
112 | } else { |
113 | border.left_top_corner = border.top; |
114 | } |
115 | } |
116 | |
117 | let has_bottom_border = border.left_bottom_corner.is_some() && border.bottom.is_some(); |
118 | if has_bottom_border { |
119 | if has_down && is_first { |
120 | border.left_bottom_corner = borders.intersection; |
121 | } else if has_down { |
122 | border.left_bottom_corner = borders.top_intersection; |
123 | } else if is_first { |
124 | border.left_bottom_corner = borders.bottom_intersection; |
125 | } else { |
126 | border.left_bottom_corner = border.bottom; |
127 | } |
128 | } |
129 | |
130 | cfg.set_border((row, col), border); |
131 | } |
132 | } |
133 | |
134 | for ((r, col), span) in cfg.get_row_spans() { |
135 | for row in r + 1..r + span { |
136 | let mut border = cfg.get_border((row, col), shape); |
137 | let borders = cfg.get_borders(); |
138 | |
139 | let has_left_border = border.left_top_corner.is_some(); |
140 | if has_left_border { |
141 | let has_left = col > 0 && has_top(cfg, (row, col - 1), shape); |
142 | if has_left { |
143 | border.left_top_corner = borders.right_intersection; |
144 | } else { |
145 | border.left_top_corner = borders.vertical; |
146 | } |
147 | } |
148 | |
149 | let has_right_border = border.right_top_corner.is_some(); |
150 | if has_right_border { |
151 | let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape); |
152 | if has_right { |
153 | border.right_top_corner = borders.left_intersection; |
154 | } else { |
155 | border.right_top_corner = borders.vertical; |
156 | } |
157 | } |
158 | |
159 | cfg.set_border((row, col), border); |
160 | } |
161 | } |
162 | |
163 | let cells = iter_totaly_spanned_cells(cfg, shape).collect::<Vec<_>>(); |
164 | for (row, col) in cells { |
165 | if row == 0 { |
166 | continue; |
167 | } |
168 | |
169 | let mut border = cfg.get_border((row, col), shape); |
170 | let borders = cfg.get_borders(); |
171 | |
172 | let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape); |
173 | let has_up = has_left(cfg, (row - 1, col), shape); |
174 | if has_up && !has_right { |
175 | border.right_top_corner = borders.right_intersection; |
176 | } |
177 | |
178 | let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape); |
179 | if has_down { |
180 | border.left_bottom_corner = borders.top_intersection; |
181 | } |
182 | |
183 | cfg.set_border((row, col), border); |
184 | } |
185 | } |
186 | |
187 | fn has_left(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool { |
188 | if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_column_span(pos) { |
189 | return false; |
190 | } |
191 | |
192 | let border: Border = cfg.get_border(pos, shape); |
193 | border.left.is_some() || border.left_top_corner.is_some() || border.left_bottom_corner.is_some() |
194 | } |
195 | |
196 | fn has_top(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool { |
197 | if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_row_span(pos) { |
198 | return false; |
199 | } |
200 | |
201 | let border: Border = cfg.get_border(pos, shape); |
202 | border.top.is_some() || border.left_top_corner.is_some() || border.right_top_corner.is_some() |
203 | } |
204 | |
205 | fn iter_totaly_spanned_cells( |
206 | cfg: &SpannedConfig, |
207 | shape: (usize, usize), |
208 | ) -> impl Iterator<Item = Position> + '_ { |
209 | // todo: can be optimized |
210 | let (count_rows: usize, count_cols: usize) = shape; |
211 | (0..count_rows).flat_map(move |row: usize| { |
212 | (0..count_cols) |
213 | .map(move |col: usize| (row, col)) |
214 | .filter(move |&p: (usize, usize)| cfg.is_cell_covered_by_both_spans(pos:p)) |
215 | }) |
216 | } |
217 | |