diff options
Diffstat (limited to 'vendor/tabled/src/settings/highlight')
-rw-r--r-- | vendor/tabled/src/settings/highlight/mod.rs | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/vendor/tabled/src/settings/highlight/mod.rs b/vendor/tabled/src/settings/highlight/mod.rs new file mode 100644 index 000000000..57df867b3 --- /dev/null +++ b/vendor/tabled/src/settings/highlight/mod.rs @@ -0,0 +1,452 @@ +//! This module contains a [`Highlight`] primitive, which helps +//! changing a [`Border`] style of any segment on a [`Table`]. +//! +//! [`Table`]: crate::Table + +use std::collections::HashSet; + +use crate::{ + grid::{ + config::{Border as GridBorder, ColoredConfig, Entity, Position, SpannedConfig}, + records::{ExactRecords, Records}, + }, + settings::{object::Object, style::BorderColor, Border, TableOption}, +}; + +/// Highlight modifies a table style by changing a border of a target [`Table`] segment. +/// +/// # Example +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{Highlight, Border, Style, object::Segment} +/// }; +/// +/// let data = [ +/// ("ELF", "Extensible Linking Format", true), +/// ("DWARF", "", true), +/// ("PE", "Portable Executable", false), +/// ]; +/// +/// let table = Table::new(data.iter().enumerate()) +/// .with(Style::markdown()) +/// .with(Highlight::new(Segment::all(), Border::default().top('^').bottom('v'))) +/// .to_string(); +/// +/// assert_eq!( +/// table, +/// concat!( +/// " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \n", +/// "| usize | &str | &str | bool |\n", +/// "|-------|-------|---------------------------|-------|\n", +/// "| 0 | ELF | Extensible Linking Format | true |\n", +/// "| 1 | DWARF | | true |\n", +/// "| 2 | PE | Portable Executable | false |\n", +/// " vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ", +/// ), +/// ); +/// ``` +/// +/// It's possible to use [`Highlight`] for many kinds of figures. +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{ +/// Highlight, Border, Style, +/// object::{Segment, Object} +/// } +/// }; +/// +/// let data = [ +/// ("ELF", "Extensible Linking Format", true), +/// ("DWARF", "", true), +/// ("PE", "Portable Executable", false), +/// ]; +/// +/// let table = Table::new(data.iter().enumerate()) +/// .with(Style::markdown()) +/// .with(Highlight::new(Segment::all().not((0,0).and((1, 0)).and((0, 1)).and((0, 3))), Border::filled('*'))) +/// .to_string(); +/// +/// println!("{}", table); +/// +/// assert_eq!( +/// table, +/// concat!( +/// " ***************************** \n", +/// "| usize | &str * &str * bool |\n", +/// "|-------*********---------------------------*********\n", +/// "| 0 * ELF | Extensible Linking Format | true *\n", +/// "********* *\n", +/// "* 1 | DWARF | | true *\n", +/// "* *\n", +/// "* 2 | PE | Portable Executable | false *\n", +/// "*****************************************************", +/// ), +/// ); +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Highlight<O> { + target: O, + border: Border, +} + +// todo: Add BorderColor. + +impl<O> Highlight<O> { + /// Build a new instance of [`Highlight`] + /// + /// BE AWARE: if target exceeds boundaries it may panic. + pub fn new(target: O, border: Border) -> Self { + Self { target, border } + } +} + +impl<O> Highlight<O> { + /// Build a new instance of [`HighlightColored`] + pub fn colored(target: O, border: BorderColor) -> HighlightColored<O> { + HighlightColored { target, border } + } +} + +impl<O, R, D> TableOption<R, D, ColoredConfig> for Highlight<O> +where + O: Object<R>, + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + let cells = self.target.cells(records); + let segments = split_segments(cells, count_rows, count_cols); + + for sector in segments { + set_border(cfg, §or, self.border); + } + } +} + +/// A [`Highlight`] object which works with a [`BorderColored`] +/// +/// [`BorderColored`]: crate::settings::style::BorderColor +#[derive(Debug)] +pub struct HighlightColored<O> { + target: O, + border: BorderColor, +} + +impl<O, R, D> TableOption<R, D, ColoredConfig> for HighlightColored<O> +where + O: Object<R>, + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + let cells = self.target.cells(records); + let segments = split_segments(cells, count_rows, count_cols); + + for sector in segments { + set_border_color(cfg, sector, &self.border); + } + } +} + +fn set_border_color( + cfg: &mut SpannedConfig, + sector: HashSet<(usize, usize)>, + border: &BorderColor, +) { + if sector.is_empty() { + return; + } + let color = border.clone().into(); + for &(row, col) in §or { + let border = build_cell_border(§or, (row, col), &color); + cfg.set_border_color((row, col), border); + } +} + +fn split_segments( + cells: impl Iterator<Item = Entity>, + count_rows: usize, + count_cols: usize, +) -> Vec<HashSet<(usize, usize)>> { + let mut segments: Vec<HashSet<(usize, usize)>> = Vec::new(); + for entity in cells { + for cell in entity.iter(count_rows, count_cols) { + let found_segment = segments + .iter_mut() + .find(|s| s.iter().any(|&c| is_cell_connected(cell, c))); + + match found_segment { + Some(segment) => { + let _ = segment.insert(cell); + } + None => { + let mut segment = HashSet::new(); + let _ = segment.insert(cell); + segments.push(segment); + } + } + } + } + + let mut squashed_segments: Vec<HashSet<(usize, usize)>> = Vec::new(); + while !segments.is_empty() { + let mut segment = segments.remove(0); + + let mut i = 0; + while i < segments.len() { + if is_segment_connected(&segment, &segments[i]) { + segment.extend(&segments[i]); + let _ = segments.remove(i); + } else { + i += 1; + } + } + + squashed_segments.push(segment); + } + + squashed_segments +} + +fn is_cell_connected((row1, col1): (usize, usize), (row2, col2): (usize, usize)) -> bool { + if col1 == col2 && row1 == row2 + 1 { + return true; + } + + if col1 == col2 && (row2 > 0 && row1 == row2 - 1) { + return true; + } + + if row1 == row2 && col1 == col2 + 1 { + return true; + } + + if row1 == row2 && (col2 > 0 && col1 == col2 - 1) { + return true; + } + + false +} + +fn is_segment_connected( + segment1: &HashSet<(usize, usize)>, + segment2: &HashSet<(usize, usize)>, +) -> bool { + for &cell1 in segment1.iter() { + for &cell2 in segment2.iter() { + if is_cell_connected(cell1, cell2) { + return true; + } + } + } + + false +} + +fn set_border(cfg: &mut SpannedConfig, sector: &HashSet<(usize, usize)>, border: Border) { + if sector.is_empty() { + return; + } + + let border = border.into(); + for &pos in sector { + let border = build_cell_border(sector, pos, &border); + cfg.set_border(pos, border); + } +} + +fn build_cell_border<T>( + sector: &HashSet<(usize, usize)>, + (row, col): Position, + border: &GridBorder<T>, +) -> GridBorder<T> +where + T: Default + Clone, +{ + let cell_has_top_neighbor = cell_has_top_neighbor(sector, row, col); + let cell_has_bottom_neighbor = cell_has_bottom_neighbor(sector, row, col); + let cell_has_left_neighbor = cell_has_left_neighbor(sector, row, col); + let cell_has_right_neighbor = cell_has_right_neighbor(sector, row, col); + + let this_has_left_top_neighbor = is_there_left_top_cell(sector, row, col); + let this_has_right_top_neighbor = is_there_right_top_cell(sector, row, col); + let this_has_left_bottom_neighbor = is_there_left_bottom_cell(sector, row, col); + let this_has_right_bottom_neighbor = is_there_right_bottom_cell(sector, row, col); + + let mut cell_border = GridBorder::default(); + if let Some(c) = border.top.clone() { + if !cell_has_top_neighbor { + cell_border.top = Some(c.clone()); + + if cell_has_right_neighbor && !this_has_right_top_neighbor { + cell_border.right_top_corner = Some(c); + } + } + } + if let Some(c) = border.bottom.clone() { + if !cell_has_bottom_neighbor { + cell_border.bottom = Some(c.clone()); + + if cell_has_right_neighbor && !this_has_right_bottom_neighbor { + cell_border.right_bottom_corner = Some(c); + } + } + } + if let Some(c) = border.left.clone() { + if !cell_has_left_neighbor { + cell_border.left = Some(c.clone()); + + if cell_has_bottom_neighbor && !this_has_left_bottom_neighbor { + cell_border.left_bottom_corner = Some(c); + } + } + } + if let Some(c) = border.right.clone() { + if !cell_has_right_neighbor { + cell_border.right = Some(c.clone()); + + if cell_has_bottom_neighbor && !this_has_right_bottom_neighbor { + cell_border.right_bottom_corner = Some(c); + } + } + } + if let Some(c) = border.left_top_corner.clone() { + if !cell_has_left_neighbor && !cell_has_top_neighbor { + cell_border.left_top_corner = Some(c); + } + } + if let Some(c) = border.left_bottom_corner.clone() { + if !cell_has_left_neighbor && !cell_has_bottom_neighbor { + cell_border.left_bottom_corner = Some(c); + } + } + if let Some(c) = border.right_top_corner.clone() { + if !cell_has_right_neighbor && !cell_has_top_neighbor { + cell_border.right_top_corner = Some(c); + } + } + if let Some(c) = border.right_bottom_corner.clone() { + if !cell_has_right_neighbor && !cell_has_bottom_neighbor { + cell_border.right_bottom_corner = Some(c); + } + } + { + if !cell_has_bottom_neighbor { + if !cell_has_left_neighbor && this_has_left_top_neighbor { + if let Some(c) = border.right_top_corner.clone() { + cell_border.left_top_corner = Some(c); + } + } + + if cell_has_left_neighbor && this_has_left_bottom_neighbor { + if let Some(c) = border.left_top_corner.clone() { + cell_border.left_bottom_corner = Some(c); + } + } + + if !cell_has_right_neighbor && this_has_right_top_neighbor { + if let Some(c) = border.left_top_corner.clone() { + cell_border.right_top_corner = Some(c); + } + } + + if cell_has_right_neighbor && this_has_right_bottom_neighbor { + if let Some(c) = border.right_top_corner.clone() { + cell_border.right_bottom_corner = Some(c); + } + } + } + + if !cell_has_top_neighbor { + if !cell_has_left_neighbor && this_has_left_bottom_neighbor { + if let Some(c) = border.right_bottom_corner.clone() { + cell_border.left_bottom_corner = Some(c); + } + } + + if cell_has_left_neighbor && this_has_left_top_neighbor { + if let Some(c) = border.left_bottom_corner.clone() { + cell_border.left_top_corner = Some(c); + } + } + + if !cell_has_right_neighbor && this_has_right_bottom_neighbor { + if let Some(c) = border.left_bottom_corner.clone() { + cell_border.right_bottom_corner = Some(c); + } + } + + if cell_has_right_neighbor && this_has_right_top_neighbor { + if let Some(c) = border.right_bottom_corner.clone() { + cell_border.right_top_corner = Some(c); + } + } + } + } + + cell_border +} + +fn cell_has_top_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + row > 0 && sector.contains(&(row - 1, col)) +} + +fn cell_has_bottom_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + sector.contains(&(row + 1, col)) +} + +fn cell_has_left_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + col > 0 && sector.contains(&(row, col - 1)) +} + +fn cell_has_right_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + sector.contains(&(row, col + 1)) +} + +fn is_there_left_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + row > 0 && col > 0 && sector.contains(&(row - 1, col - 1)) +} + +fn is_there_right_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + row > 0 && sector.contains(&(row - 1, col + 1)) +} + +fn is_there_left_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + col > 0 && sector.contains(&(row + 1, col - 1)) +} + +fn is_there_right_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + sector.contains(&(row + 1, col + 1)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_connected() { + assert!(is_cell_connected((0, 0), (0, 1))); + assert!(is_cell_connected((0, 0), (1, 0))); + assert!(!is_cell_connected((0, 0), (1, 1))); + + assert!(is_cell_connected((0, 1), (0, 0))); + assert!(is_cell_connected((1, 0), (0, 0))); + assert!(!is_cell_connected((1, 1), (0, 0))); + + assert!(is_cell_connected((1, 1), (0, 1))); + assert!(is_cell_connected((1, 1), (1, 0))); + assert!(is_cell_connected((1, 1), (2, 1))); + assert!(is_cell_connected((1, 1), (1, 2))); + assert!(!is_cell_connected((1, 1), (1, 1))); + } +} |