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
6use 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)]
77pub struct BorderSpanCorrection;
78
79impl<R, D> TableOption<R, D, ColoredConfig> for BorderSpanCorrection
80where
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
89fn 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
187fn 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
196fn 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
205fn 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