#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Border {
pub top: &'static str,
pub bottom: &'static str,
pub left: &'static str,
pub right: &'static str,
pub top_left: &'static str,
pub top_right: &'static str,
pub bottom_left: &'static str,
pub bottom_right: &'static str,
pub middle_left: &'static str,
pub middle_right: &'static str,
pub middle: &'static str,
pub middle_top: &'static str,
pub middle_bottom: &'static str,
}
impl Border {
#[allow(clippy::too_many_arguments)]
pub const fn new(
top: &'static str,
bottom: &'static str,
left: &'static str,
right: &'static str,
top_left: &'static str,
top_right: &'static str,
bottom_left: &'static str,
bottom_right: &'static str,
middle_left: &'static str,
middle_right: &'static str,
middle: &'static str,
middle_top: &'static str,
middle_bottom: &'static str,
) -> Self {
Self {
top,
bottom,
left,
right,
top_left,
top_right,
bottom_left,
bottom_right,
middle_left,
middle_right,
middle,
middle_top,
middle_bottom,
}
}
pub fn get_top_size(&self) -> usize {
get_border_edge_width(&[self.top_left, self.top, self.top_right])
}
pub fn get_right_size(&self) -> usize {
get_border_edge_width(&[self.top_right, self.right, self.bottom_right])
}
pub fn get_bottom_size(&self) -> usize {
get_border_edge_width(&[self.bottom_left, self.bottom, self.bottom_right])
}
pub fn get_left_size(&self) -> usize {
get_border_edge_width(&[self.top_left, self.left, self.bottom_left])
}
}
pub const fn normal_border() -> Border {
Border::new(
"─", "─", "│", "│", "┌", "┐", "└", "┘", "├", "┤", "┼", "┬", "┴",
)
}
pub const fn rounded_border() -> Border {
Border::new(
"─", "─", "│", "│", "╭", "╮", "╰", "╯", "├", "┤", "┼", "┬", "┴",
)
}
pub const fn block_border() -> Border {
Border::new(
"█", "█", "█", "█", "█", "█", "█", "█", "█", "█", "█", "█", "█",
)
}
pub const fn thick_border() -> Border {
Border::new(
"━", "━", "┃", "┃", "┏", "┓", "┗", "┛", "┣", "┫", "╋", "┳", "┻",
)
}
pub const fn double_border() -> Border {
Border::new(
"═", "═", "║", "║", "╔", "╗", "╚", "╝", "╠", "╣", "╬", "╦", "╩",
)
}
pub const fn hidden_border() -> Border {
Border::new(
" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ",
)
}
pub const fn markdown_border() -> Border {
Border::new(
"-", "-", "|", "|", "|", "|", "|", "|", "|", "|", "|", "|", "|",
)
}
pub const fn ascii_border() -> Border {
Border::new(
"-", "-", "|", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+",
)
}
pub const fn outer_half_block_border() -> Border {
Border::new("▀", "▄", "▌", "▐", "▛", "▜", "▙", "▟", "", "", "", "", "")
}
pub const fn inner_half_block_border() -> Border {
Border::new("▄", "▀", "▐", "▌", "▗", "▖", "▝", "▘", "", "", "", "", "")
}
use unicode_width::UnicodeWidthChar;
fn max_rune_width(s: &str) -> usize {
let mut maxw = 0usize;
for ch in s.chars() {
let w = UnicodeWidthChar::width(ch).unwrap_or(0);
if w > maxw {
maxw = w;
}
}
maxw
}
fn get_border_edge_width(parts: &[&str]) -> usize {
let mut maxw = 0usize;
for p in parts {
let w = max_rune_width(p);
if w > maxw {
maxw = w;
}
}
maxw
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normal_border_fields_match_go() {
let b = normal_border();
assert_eq!(b.top, "─");
assert_eq!(b.bottom, "─");
assert_eq!(b.left, "│");
assert_eq!(b.right, "│");
assert_eq!(b.top_left, "┌");
assert_eq!(b.top_right, "┐");
assert_eq!(b.bottom_left, "└");
assert_eq!(b.bottom_right, "┘");
assert_eq!(b.middle_left, "├");
assert_eq!(b.middle_right, "┤");
assert_eq!(b.middle, "┼");
assert_eq!(b.middle_top, "┬");
assert_eq!(b.middle_bottom, "┴");
}
#[test]
fn double_border_fields_match_go() {
let b = double_border();
assert_eq!(b.top, "═");
assert_eq!(b.bottom, "═");
assert_eq!(b.left, "║");
assert_eq!(b.right, "║");
assert_eq!(b.top_left, "╔");
assert_eq!(b.top_right, "╗");
assert_eq!(b.bottom_left, "╚");
assert_eq!(b.bottom_right, "╝");
assert_eq!(b.middle_left, "╠");
assert_eq!(b.middle_right, "╣");
assert_eq!(b.middle, "╬");
assert_eq!(b.middle_top, "╦");
assert_eq!(b.middle_bottom, "╩");
}
#[test]
fn ascii_border_fields_match_go() {
let b = ascii_border();
assert_eq!(b.top, "-");
assert_eq!(b.bottom, "-");
assert_eq!(b.left, "|");
assert_eq!(b.right, "|");
assert_eq!(b.top_left, "+");
assert_eq!(b.top_right, "+");
assert_eq!(b.bottom_left, "+");
assert_eq!(b.bottom_right, "+");
assert_eq!(b.middle_left, "+");
assert_eq!(b.middle_right, "+");
assert_eq!(b.middle, "+");
assert_eq!(b.middle_top, "+");
assert_eq!(b.middle_bottom, "+");
}
#[test]
fn hidden_border_is_spaces() {
let b = hidden_border();
assert_eq!(b.top, " ");
assert_eq!(b.bottom, " ");
assert_eq!(b.left, " ");
assert_eq!(b.right, " ");
assert_eq!(b.top_left, " ");
assert_eq!(b.top_right, " ");
assert_eq!(b.bottom_left, " ");
assert_eq!(b.bottom_right, " ");
assert_eq!(b.middle_left, " ");
assert_eq!(b.middle_right, " ");
assert_eq!(b.middle, " ");
assert_eq!(b.middle_top, " ");
assert_eq!(b.middle_bottom, " ");
}
#[test]
fn edge_sizes_width_one_for_single_cell_borders() {
for b in [
normal_border(),
rounded_border(),
block_border(),
thick_border(),
double_border(),
ascii_border(),
markdown_border(),
outer_half_block_border(),
inner_half_block_border(),
] {
assert_eq!(b.get_top_size(), 1);
assert_eq!(b.get_right_size(), 1);
assert_eq!(b.get_bottom_size(), 1);
assert_eq!(b.get_left_size(), 1);
}
}
#[test]
fn edge_sizes_account_for_wide_runes() {
let wide = "太"; let b = Border::new(
wide, "-", "|", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+",
);
assert!(
b.get_top_size() >= 2,
"expected top size to be >= 2 for wide rune"
);
assert_eq!(b.get_right_size(), 1);
assert_eq!(b.get_bottom_size(), 1);
assert_eq!(b.get_left_size(), 1);
}
#[test]
fn half_block_borders_fields() {
let outer = outer_half_block_border();
assert_eq!(outer.top, "▀");
assert_eq!(outer.bottom, "▄");
assert_eq!(outer.left, "▌");
assert_eq!(outer.right, "▐");
assert_eq!(outer.top_left, "▛");
assert_eq!(outer.top_right, "▜");
assert_eq!(outer.bottom_left, "▙");
assert_eq!(outer.bottom_right, "▟");
let inner = inner_half_block_border();
assert_eq!(inner.top, "▄");
assert_eq!(inner.bottom, "▀");
assert_eq!(inner.left, "▐");
assert_eq!(inner.right, "▌");
assert_eq!(inner.top_left, "▗");
assert_eq!(inner.top_right, "▖");
assert_eq!(inner.bottom_left, "▝");
assert_eq!(inner.bottom_right, "▘");
}
#[test]
fn edge_size_methods_match_manual_computation() {
fn manual_max(parts: &[&str]) -> usize {
let mut m = 0;
for p in parts {
let w = max_rune_width(p);
if w > m {
m = w;
}
}
m
}
let borders = [
normal_border(),
thick_border(),
double_border(),
ascii_border(),
markdown_border(),
outer_half_block_border(),
];
for b in borders {
assert_eq!(
b.get_top_size(),
manual_max(&[b.top_left, b.top, b.top_right])
);
assert_eq!(
b.get_right_size(),
manual_max(&[b.top_right, b.right, b.bottom_right])
);
assert_eq!(
b.get_bottom_size(),
manual_max(&[b.bottom_left, b.bottom, b.bottom_right])
);
assert_eq!(
b.get_left_size(),
manual_max(&[b.top_left, b.left, b.bottom_left])
);
}
}
#[test]
fn joiners_match_go_for_remaining_presets() {
let r = rounded_border();
assert_eq!(r.middle_left, "├");
assert_eq!(r.middle_right, "┤");
assert_eq!(r.middle, "┼");
assert_eq!(r.middle_top, "┬");
assert_eq!(r.middle_bottom, "┴");
let t = thick_border();
assert_eq!(t.middle_left, "┣");
assert_eq!(t.middle_right, "┫");
assert_eq!(t.middle, "╋");
assert_eq!(t.middle_top, "┳");
assert_eq!(t.middle_bottom, "┻");
let b = block_border();
assert_eq!(b.middle_left, "█");
assert_eq!(b.middle_right, "█");
assert_eq!(b.middle, "█");
assert_eq!(b.middle_top, "█");
assert_eq!(b.middle_bottom, "█");
let hb_outer = outer_half_block_border();
assert_eq!(hb_outer.middle_left, "");
assert_eq!(hb_outer.middle_right, "");
assert_eq!(hb_outer.middle, "");
assert_eq!(hb_outer.middle_top, "");
assert_eq!(hb_outer.middle_bottom, "");
let hb_inner = inner_half_block_border();
assert_eq!(hb_inner.middle_left, "");
assert_eq!(hb_inner.middle_right, "");
assert_eq!(hb_inner.middle, "");
assert_eq!(hb_inner.middle_top, "");
assert_eq!(hb_inner.middle_bottom, "");
}
}