Repository: zhiburt/tabled
Pull request #166 (2022-06-09 13:00:09)

Add Style::correct_spans function


Patch:
diff --git a/examples/span.rs b/examples/span.rs
index 7ad58a4..3051fb2 100644
--- a/examples/span.rs
+++ b/examples/span.rs
@@ -10,7 +10,6 @@ fn main() {
 
     let table = data
         .table()
-        .with(Style::modern())
         .with(
             Modify::new(Cell(0, 0))
                 .with(Span::column(5))
@@ -45,7 +44,9 @@ fn main() {
             Modify::new(Cell(4, 1))
                 .with(Span::column(4))
                 .with(|_: &str| "span 4 columns".to_string()),
-        );
+        )
+        .with(Style::modern())
+        .with(Style::correct_spans());
 
     println!("{}", table);
 }
diff --git a/papergrid/src/lib.rs b/papergrid/src/lib.rs
index 1bdea55..bd19a78 100644
--- a/papergrid/src/lib.rs
+++ b/papergrid/src/lib.rs
@@ -611,6 +611,13 @@ impl Grid {
             .map(|((start, end), _)| end - start)
     }
 
+    /// Get a span value of the cell, if any is set.
+    pub fn iter_column_spans(&self) -> impl Iterator<Item = (Position, usize)> + '_ {
+        self.spans.iter().flat_map(move |(&(start, end), rows)| {
+            rows.iter().map(move |&row| ((row, start), end - start))
+        })
+    }
+
     /// Verifies if there's any spans set.
     pub fn has_column_spans(&self) -> bool {
         !self.spans.is_empty()
diff --git a/src/style.rs b/src/style.rs
index e0f7da8..723f524 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -85,7 +85,7 @@
 use std::{borrow::Cow, marker::PhantomData};
 
 use crate::{CellOption, TableOption};
-use papergrid::{Borders, Entity, Grid, Settings};
+use papergrid::{Borders, Entity, Grid, Position, Settings};
 
 /// Style is represents a theme of a [Table].
 ///
@@ -278,6 +278,62 @@ impl Style {
     pub const fn re_structured_text() -> CustomStyle<On, On, (), (), (), On, On> {
         CustomStyle::new(RE_STRUCTURED_TEXT)
     }
+
+    /// Try to fix the style when table contains spans.
+    ///
+    /// By default [Style] doesn't implies any logic to better render split lines when
+    /// [crate::Span] is used.
+    ///
+    /// So this function can be used to set the split lines in regard of spans used.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use tabled::{TableIteratorExt, Style, Modify, Format, Span, object::Cell};
+    ///
+    /// let data = vec![
+    ///     ("09", "June", "2022"),
+    ///     ("10", "July", "2022"),
+    /// ];
+    ///
+    /// let table = data.table()
+    ///     .with(
+    ///         Modify::new(Cell(0, 0))
+    ///             .with(Format::new(|_| String::from("date")))
+    ///             .with(Span::column(3))
+    ///     );
+    ///
+    /// assert_eq!(
+    ///     table.to_string(),
+    ///     concat!(
+    ///         "+----+------+------+\n",
+    ///         "|       date       |\n",
+    ///         "+----+------+------+\n",
+    ///         "| 09 | June | 2022 |\n",
+    ///         "+----+------+------+\n",
+    ///         "| 10 | July | 2022 |\n",
+    ///         "+----+------+------+\n",
+    ///     )
+    /// );
+    ///
+    /// let table = table.with(Style::correct_spans());
+    ///
+    /// assert_eq!(
+    ///     table.to_string(),
+    ///     concat!(
+    ///         "+------------------+\n",
+    ///         "|       date       |\n",
+    ///         "+----+------+------+\n",
+    ///         "| 09 | June | 2022 |\n",
+    ///         "+----+------+------+\n",
+    ///         "| 10 | July | 2022 |\n",
+    ///         "+----+------+------+\n",
+    ///     )
+    /// );
+    /// ```
+    pub const fn correct_spans() -> StyleCorrectSpan {
+        StyleCorrectSpan
+    }
 }
 
 const EMPTY: StyleSettings = StyleSettings::new(Frame::empty(), Line::empty(), Line::empty(), None);
@@ -1207,3 +1263,87 @@ pub use papergrid::Symbol;
 
 #[cfg(not(feature = "color"))]
 use papergrid::Symbol;
+
+/// A correctnes function of style for [crate::Table] which has [crate::Span]s.
+///
+/// See [Style::correct_spans].
+#[derive(Debug)]
+pub struct StyleCorrectSpan;
+
+impl TableOption for StyleCorrectSpan {
+    fn change(&mut self, grid: &mut Grid) {
+        correct_span_styles(grid);
+    }
+}
+
+fn correct_span_styles(grid: &mut Grid) {
+    let borders = grid.get_borders();
+    let inner_intersection = borders.intersection.clone();
+    let bottom_intersection = borders.bottom_intersection.clone();
+    let top_intersection = borders.top_intersection.clone();
+
+    let spans = grid.iter_column_spans().collect::<Vec<_>>();
+
+    for &((row, c), span) in &spans {
+        for col in c..c + span {
+            if col == 0 {
+                continue;
+            }
+
+            let is_first = col == c;
+            let has_up = row > 0 && has_vertical(grid, &spans, (row - 1, col));
+            let has_down = has_vertical(grid, &spans, (row + 1, col));
+
+            let mut border = grid.get_border(row, col);
+
+            let has_top_border = border.left_top_corner.is_some() && border.top.is_some();
+            if has_top_border {
+                if has_up && is_first {
+                    border.left_top_corner = inner_intersection.clone();
+                } else if has_up {
+                    border.left_top_corner = bottom_intersection.clone();
+                } else if is_first {
+                    border.left_top_corner = top_intersection.clone();
+                } else {
+                    border.left_top_corner = border.top.clone();
+                }
+            }
+
+            let has_bottom_border = border.left_bottom_corner.is_some() && border.bottom.is_some();
+            if has_bottom_border {
+                if has_down && is_first {
+                    border.left_bottom_corner = inner_intersection.clone();
+                } else if has_down {
+                    border.left_bottom_corner = top_intersection.clone();
+                } else if is_first {
+                    border.left_bottom_corner = bottom_intersection.clone();
+                } else {
+                    border.left_bottom_corner = border.bottom.clone();
+                }
+            }
+
+            grid.set_border(Entity::Cell(row, col), border);
+        }
+    }
+}
+
+fn has_vertical(grid: &Grid, spans: &[(Position, usize)], pos: Position) -> bool {
+    if is_in_span_range(spans, pos) {
+        return spans.iter().any(|&(p, _)| p == pos);
+    }
+
+    if grid.is_cell_visible(pos) {
+        let border = grid.get_border(pos.0, pos.1);
+        return border.left.is_some()
+            || border.left_top_corner.is_some()
+            || border.left_bottom_corner.is_some();
+    }
+
+    false
+}
+
+fn is_in_span_range(spans: &[(Position, usize)], pos: Position) -> bool {
+    spans
+        .iter()
+        .any(|&((row, col), span)| row == pos.0 && pos.1 > col && pos.1 < col + span)
+}


Test patch:
diff --git a/papergrid/tests/render.rs b/papergrid/tests/render.rs
index a117171..609f098 100644
--- a/papergrid/tests/render.rs
+++ b/papergrid/tests/render.rs
@@ -705,17 +705,41 @@ fn hieroglyph_handling() {
 }
 
 #[test]
-#[ignore = "I am not sure what is the right behaiviour here"]
 fn hieroglyph_multiline_handling() {
     let mut grid = util::new_grid::<1, 2>();
     grid.set(Entity::Cell(0, 0), Settings::new().text("哈哈"));
     grid.set(Entity::Cell(0, 1), Settings::new().text("哈\n哈"));
 
+    println!("{grid}");
+
     assert_eq!(
         grid.to_string(),
         "+----+--+\n\
-         |哈哈  |哈 |\n\
-         |    |哈 |\n\
+         |哈哈|哈|\n\
+         |    |哈|\n\
          +----+--+\n"
     )
 }
+
+#[test]
+fn hieroglyph_handling_2() {
+    let mut grid = util::new_grid::<2, 1>();
+    grid.set(
+        Entity::Cell(0, 0),
+        Settings::new().text("জী._ডি._ব্লক_সল্টলেক_দূর্গা_পুজো_২০১৮.jpg"),
+    );
+    grid.set(Entity::Cell(1, 0), Settings::new().text("Hello"));
+
+    println!("{grid}");
+
+    assert_eq!(
+        grid.to_string(),
+        concat!(
+            "+------------------------------------+\n",
+            "|জী._ডি._ব্লক_সল্টলেক_দূর্গা_পুজো_২০১৮.jpg|\n",
+            "+------------------------------------+\n",
+            "|Hello                               |\n",
+            "+------------------------------------+\n",
+        )
+    )
+}
diff --git a/tests/panel_test.rs b/tests/panel_test.rs
index 097a42f..1542b5f 100644
--- a/tests/panel_test.rs
+++ b/tests/panel_test.rs
@@ -228,6 +228,28 @@ fn panel_style_change() {
         static_table!(
             "┌───────────┐"
             "│  Numbers  │"
+            "├─────┬─────┤" // it's different because we use a top_intersection char by default when making style for `Panel`s.
+            "│ i32 │ i32 │"
+            "├─────┼─────┤"
+            "│  0  │  1  │"
+            "└─────┴─────┘"
+        )
+    );
+}
+
+#[test]
+fn panel_style_uses_most_left_and_right_cell_styles_correct() {
+    let table = Table::new(&[(0, 1)])
+        .with(tabled::Panel("Numbers", 0))
+        .with(Style::modern())
+        .with(Style::correct_spans())
+        .to_string();
+
+    assert_eq!(
+        table,
+        static_table!(
+            "┌───────────┐"
+            "│Numbers    │"
             "├─────┬─────┤"
             "│ i32 │ i32 │"
             "├─────┼─────┤"
@@ -237,6 +259,33 @@ fn panel_style_change() {
     );
 }
 
+#[test]
+fn panel_style_change_corect() {
+    let table = Table::new(&[(0, 1)])
+        .with(tabled::Panel("Numbers", 0))
+        .with(
+            Style::modern()
+                .top_intersection('─')
+                .header_intersection('┬'),
+        )
+        .with(Style::correct_spans())
+        .with(Modify::new(Cell(0, 0)).with(Alignment::center()))
+        .to_string();
+
+    assert_eq!(
+        table,
+        static_table!(
+            "┌───────────┐"
+            "│  Numbers  │"
+            "├───────────┤" // it's different because we use a top_intersection char by default when making style for `Panel`s.
+            "│ i32 │ i32 │"
+            "├─────┼─────┤"
+            "│  0  │  1  │"
+            "└─────┴─────┘"
+        )
+    );
+}
+
 #[test]
 fn panel_in_single_column() {
     let table = Table::new(&[(0)])
diff --git a/tests/span_test.rs b/tests/span_test.rs
index 239c3f0..8cf18e2 100644
--- a/tests/span_test.rs
+++ b/tests/span_test.rs
@@ -661,3 +661,74 @@ fn span_with_panel_test() {
         )
     );
 }
+
+#[test]
+fn span_with_panel_with_correction_test() {
+    let data = [[1, 2, 3]];
+    let table = Table::new(data)
+        .with(Panel("Tabled Releases", 0))
+        .with(Modify::new(Cell(1, 0)).with(Span::column(2)))
+        .with(Style::ascii())
+        .with(Style::correct_spans())
+        .to_string();
+
+    assert_eq!(
+        table,
+        static_table!(
+            "+---------------+"
+            "|Tabled Releases|"
+            "+----------+----+"
+            "|    0     | 2  |"
+            "+-----+----+----+"
+            "|  1  | 2  | 3  |"
+            "+-----+----+----+"
+        )
+    );
+
+    let data = [[1, 2, 3], [4, 5, 6]];
+    let table = Table::new(data)
+        .with(Panel("Tabled Releases", 0))
+        .with(Modify::new(Cell(2, 0)).with(Span::column(2)))
+        .with(Style::ascii())
+        .with(Style::correct_spans())
+        .to_string();
+
+    assert_eq!(
+        table,
+        static_table!(
+            "+---------------+"
+            "|Tabled Releases|"
+            "+-----+----+----+"
+            "|  0  | 1  | 2  |"
+            "+-----+----+----+"
+            "|    1     | 3  |"
+            "+-----+----+----+"
+            "|  4  | 5  | 6  |"
+            "+-----+----+----+"
+        )
+    );
+
+    let data = [[1, 2, 3], [4, 5, 6]];
+    let table = Table::new(data)
+        .with(Panel("Tabled Releases", 0))
+        .with(Modify::new(Cell(1, 0)).with(Span::column(2)))
+        .with(Modify::new(Cell(2, 0)).with(Span::column(2)))
+        .with(Style::ascii())
+        .with(Style::correct_spans())
+        .to_string();
+
+    assert_eq!(
+        table,
+        static_table!(
+            "+---------------+"
+            "|Tabled Releases|"
+            "+----------+----+"
+            "|    0     | 2  |"
+            "+----------+----+"
+            "|    1     | 3  |"
+            "+-----+----+----+"
+            "|  4  | 5  | 6  |"
+            "+-----+----+----+"
+        )
+    );
+}