tabled/features/style/
span_border_correction.rs

1//! This module contains [`StyleCorrectSpan`] structure, which can be usefull when [`Span`] is used, and
2//! you wan't to fix the intersections symbols which are left intact by default.
3//!
4//! [`Span`]: crate::Span
5
6use papergrid::{records::Records, Position};
7
8use crate::{Table, TableOption};
9
10/// A correctnes function of style for [`Table`] which has [`Span`]s.
11///
12/// See [`Style::correct_spans`].
13///
14/// [`Table`]: crate::Table
15/// [`Span`]: crate::Span
16/// [`Style::correct_spans`]: crate::Style::correct_spans
17#[derive(Debug)]
18pub struct StyleCorrectSpan;
19
20impl<R> TableOption<R> for StyleCorrectSpan
21where
22    R: Records,
23{
24    fn change(&mut self, table: &mut Table<R>) {
25        correct_span_styles(table);
26    }
27}
28
29fn correct_span_styles<R>(table: &mut Table<R>)
30where
31    R: Records,
32{
33    let spans = table
34        .get_config()
35        .iter_column_spans(table.shape())
36        .collect::<Vec<_>>();
37    for &((row, c), span) in &spans {
38        for col in c..c + span {
39            if col == 0 {
40                continue;
41            }
42
43            let is_first = col == c;
44            let has_up = row > 0 && has_left(table, (row - 1, col));
45            let has_down = row + 1 < table.shape().0 && has_left(table, (row + 1, col));
46
47            let mut border = table.get_config().get_border((row, col), table.shape());
48            let borders = table.get_config().get_borders();
49
50            let has_top_border = border.left_top_corner.is_some() && border.top.is_some();
51            if has_top_border {
52                if has_up && is_first {
53                    border.left_top_corner = borders.intersection;
54                } else if has_up {
55                    border.left_top_corner = borders.bottom_intersection;
56                } else if is_first {
57                    border.left_top_corner = borders.top_intersection;
58                } else {
59                    border.left_top_corner = border.top;
60                }
61            }
62
63            let has_bottom_border = border.left_bottom_corner.is_some() && border.bottom.is_some();
64            if has_bottom_border {
65                if has_down && is_first {
66                    border.left_bottom_corner = borders.intersection;
67                } else if has_down {
68                    border.left_bottom_corner = borders.top_intersection;
69                } else if is_first {
70                    border.left_bottom_corner = borders.bottom_intersection;
71                } else {
72                    border.left_bottom_corner = border.bottom;
73                }
74            }
75
76            table.get_config_mut().set_border((row, col), border);
77        }
78    }
79
80    let spans = table
81        .get_config()
82        .iter_row_spans(table.shape())
83        .collect::<Vec<_>>();
84    for &((r, col), span) in &spans {
85        for row in r + 1..r + span {
86            let mut border = table.get_config().get_border((row, col), table.shape());
87            let borders = table.get_config().get_borders();
88
89            let has_left_border = border.left_top_corner.is_some();
90            if has_left_border {
91                let has_left = col > 0 && has_top(table, (row, col - 1));
92                if has_left {
93                    border.left_top_corner = borders.horizontal_right;
94                } else {
95                    border.left_top_corner = borders.vertical;
96                }
97            }
98
99            let has_right_border = border.right_top_corner.is_some();
100            if has_right_border {
101                let has_right = col + 1 < table.shape().1 && has_top(table, (row, col + 1));
102                if has_right {
103                    border.right_top_corner = borders.horizontal_left;
104                } else {
105                    border.right_top_corner = borders.vertical;
106                }
107            }
108
109            table.get_config_mut().set_border((row, col), border);
110        }
111    }
112
113    let cells = iter_totaly_spanned_cells(table).collect::<Vec<_>>();
114    for (row, col) in cells {
115        if row == 0 {
116            continue;
117        }
118
119        let mut border = table.get_config().get_border((row, col), table.shape());
120        let borders = table.get_config().get_borders();
121
122        let has_right = col + 1 < table.shape().1 && has_top(table, (row, col + 1));
123        let has_up = has_left(table, (row - 1, col));
124        if has_up && !has_right {
125            border.right_top_corner = borders.horizontal_right;
126        }
127
128        let has_down = row + 1 < table.shape().0 && has_left(table, (row + 1, col));
129        if has_down {
130            border.left_bottom_corner = borders.top_intersection;
131        }
132
133        table.get_config_mut().set_border((row, col), border);
134    }
135}
136
137fn has_left<R>(table: &Table<R>, pos: Position) -> bool
138where
139    R: Records,
140{
141    let cfg = table.get_config();
142    if cfg.is_cell_covered_by_both_spans(pos, table.shape())
143        || cfg.is_cell_covered_by_column_span(pos, table.shape())
144    {
145        return false;
146    }
147
148    let border = cfg.get_border(pos, table.shape());
149    border.left.is_some() || border.left_top_corner.is_some() || border.left_bottom_corner.is_some()
150}
151
152fn has_top<R>(table: &Table<R>, pos: Position) -> bool
153where
154    R: Records,
155{
156    let cfg = table.get_config();
157    if cfg.is_cell_covered_by_both_spans(pos, table.shape())
158        || cfg.is_cell_covered_by_row_span(pos, table.shape())
159    {
160        return false;
161    }
162
163    let border = cfg.get_border(pos, table.shape());
164    border.top.is_some() || border.left_top_corner.is_some() || border.right_top_corner.is_some()
165}
166
167fn iter_totaly_spanned_cells<R>(table: &Table<R>) -> impl Iterator<Item = Position> + '_
168where
169    R: Records,
170{
171    // todo: can be optimized
172    let (count_rows, count_cols) = table.shape();
173    (0..count_rows).flat_map(move |row| {
174        (0..count_cols)
175            .map(move |col| (row, col))
176            .filter(move |&p| {
177                table
178                    .get_config()
179                    .is_cell_covered_by_both_spans(p, (count_rows, count_cols))
180            })
181    })
182}