#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RowLevel {
Head,
Row,
Foot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Box {
pub top_left: char,
pub top: char,
pub top_divider: char,
pub top_right: char,
pub head_left: char,
pub head_vertical: char,
pub head_right: char,
pub head_row_left: char,
pub head_row_horizontal: char,
pub head_row_cross: char,
pub head_row_right: char,
pub mid_left: char,
pub mid_vertical: char,
pub mid_right: char,
pub row_left: char,
pub row_horizontal: char,
pub row_cross: char,
pub row_right: char,
pub foot_row_left: char,
pub foot_row_horizontal: char,
pub foot_row_cross: char,
pub foot_row_right: char,
pub foot_left: char,
pub foot_vertical: char,
pub foot_right: char,
pub bottom_left: char,
pub bottom: char,
pub bottom_divider: char,
pub bottom_right: char,
}
impl Box {
const fn parse(template: &str) -> Box {
let rows = split_rows(template);
Box {
top_left: rows[0][0],
top: rows[0][1],
top_divider: rows[0][2],
top_right: rows[0][3],
head_left: rows[1][0],
head_vertical: rows[1][2],
head_right: rows[1][3],
head_row_left: rows[2][0],
head_row_horizontal: rows[2][1],
head_row_cross: rows[2][2],
head_row_right: rows[2][3],
mid_left: rows[3][0],
mid_vertical: rows[3][2],
mid_right: rows[3][3],
row_left: rows[4][0],
row_horizontal: rows[4][1],
row_cross: rows[4][2],
row_right: rows[4][3],
foot_row_left: rows[5][0],
foot_row_horizontal: rows[5][1],
foot_row_cross: rows[5][2],
foot_row_right: rows[5][3],
foot_left: rows[6][0],
foot_vertical: rows[6][2],
foot_right: rows[6][3],
bottom_left: rows[7][0],
bottom: rows[7][1],
bottom_divider: rows[7][2],
bottom_right: rows[7][3],
}
}
pub fn get_top(&self, widths: &[usize], edge: bool) -> String {
let mut parts = String::new();
if edge {
parts.push(self.top_left);
}
let last = widths.len().saturating_sub(1);
for (index, &width) in widths.iter().enumerate() {
for _ in 0..width {
parts.push(self.top);
}
if index != last {
parts.push(self.top_divider);
}
}
if edge {
parts.push(self.top_right);
}
parts
}
pub fn get_row(&self, widths: &[usize], level: RowLevel, edge: bool) -> String {
let (left, horizontal, cross, right) = match level {
RowLevel::Head => (
self.head_row_left,
self.head_row_horizontal,
self.head_row_cross,
self.head_row_right,
),
RowLevel::Row => (
self.row_left,
self.row_horizontal,
self.row_cross,
self.row_right,
),
RowLevel::Foot => (
self.foot_row_left,
self.foot_row_horizontal,
self.foot_row_cross,
self.foot_row_right,
),
};
let mut parts = String::new();
if edge {
parts.push(left);
}
let last = widths.len().saturating_sub(1);
for (index, &width) in widths.iter().enumerate() {
for _ in 0..width {
parts.push(horizontal);
}
if index != last {
parts.push(cross);
}
}
if edge {
parts.push(right);
}
parts
}
pub fn substitute(&self, legacy_windows: bool, safe: bool, ascii_only: bool) -> Box {
let mut result = *self;
if legacy_windows && safe && (result == ROUNDED || result == HEAVY || result == HEAVY_HEAD)
{
result = SQUARE;
}
if ascii_only && result != ASCII {
result = ASCII;
}
result
}
pub fn get_bottom(&self, widths: &[usize], edge: bool) -> String {
let mut parts = String::new();
if edge {
parts.push(self.bottom_left);
}
let last = widths.len().saturating_sub(1);
for (index, &width) in widths.iter().enumerate() {
for _ in 0..width {
parts.push(self.bottom);
}
if index != last {
parts.push(self.bottom_divider);
}
}
if edge {
parts.push(self.bottom_right);
}
parts
}
}
const fn split_rows(template: &str) -> [[char; 4]; 8] {
let bytes = template.as_bytes();
let mut rows = [[' '; 4]; 8];
let mut i = 0usize; let mut row = 0usize;
let mut col = 0usize;
while i < bytes.len() {
let (ch, width) = next_char(bytes, i);
if ch == '\n' {
row += 1;
col = 0;
i += width;
continue;
}
if row < 8 && col < 4 {
rows[row][col] = ch;
}
col += 1;
i += width;
}
rows
}
const fn next_char(bytes: &[u8], i: usize) -> (char, usize) {
let b0 = bytes[i];
if b0 < 0x80 {
(b0 as char, 1)
} else if b0 >> 5 == 0b110 {
let cp = ((b0 as u32 & 0x1f) << 6) | (bytes[i + 1] as u32 & 0x3f);
(char_from_u32(cp), 2)
} else if b0 >> 4 == 0b1110 {
let cp = ((b0 as u32 & 0x0f) << 12)
| ((bytes[i + 1] as u32 & 0x3f) << 6)
| (bytes[i + 2] as u32 & 0x3f);
(char_from_u32(cp), 3)
} else {
let cp = ((b0 as u32 & 0x07) << 18)
| ((bytes[i + 1] as u32 & 0x3f) << 12)
| ((bytes[i + 2] as u32 & 0x3f) << 6)
| (bytes[i + 3] as u32 & 0x3f);
(char_from_u32(cp), 4)
}
}
const fn char_from_u32(cp: u32) -> char {
match char::from_u32(cp) {
Some(c) => c,
None => '\u{fffd}',
}
}
pub const ASCII: Box = Box::parse("+--+\n| ||\n|-+|\n| ||\n|-+|\n|-+|\n| ||\n+--+\n");
pub const SQUARE: Box = Box::parse("┌─┬┐\n│ ││\n├─┼┤\n│ ││\n├─┼┤\n├─┼┤\n│ ││\n└─┴┘\n");
pub const ROUNDED: Box = Box::parse("╭─┬╮\n│ ││\n├─┼┤\n│ ││\n├─┼┤\n├─┼┤\n│ ││\n╰─┴╯\n");
pub const HEAVY: Box = Box::parse("┏━┳┓\n┃ ┃┃\n┣━╋┫\n┃ ┃┃\n┣━╋┫\n┣━╋┫\n┃ ┃┃\n┗━┻┛\n");
pub const HEAVY_HEAD: Box = Box::parse("┏━┳┓\n┃ ┃┃\n┡━╇┩\n│ ││\n├─┼┤\n├─┼┤\n│ ││\n└─┴┘\n");
pub const DOUBLE: Box = Box::parse("╔═╦╗\n║ ║║\n╠═╬╣\n║ ║║\n╠═╬╣\n╠═╬╣\n║ ║║\n╚═╩╝\n");
pub const MINIMAL: Box = Box::parse(" ╷ \n │ \n╶─┼╴\n │ \n╶─┼╴\n╶─┼╴\n │ \n ╵ \n");
pub const ASCII2: Box = Box::parse("+-++\n| ||\n+-++\n| ||\n+-++\n+-++\n| ||\n+-++\n");
pub const ASCII_DOUBLE_HEAD: Box = Box::parse("+-++\n| ||\n+=++\n| ||\n+-++\n+-++\n| ||\n+-++\n");
pub const SQUARE_DOUBLE_HEAD: Box = Box::parse("┌─┬┐\n│ ││\n╞═╪╡\n│ ││\n├─┼┤\n├─┼┤\n│ ││\n└─┴┘\n");
pub const MINIMAL_HEAVY_HEAD: Box = Box::parse(" ╷ \n │ \n╺━┿╸\n │ \n╶─┼╴\n╶─┼╴\n │ \n ╵ \n");
pub const MINIMAL_DOUBLE_HEAD: Box = Box::parse(" ╷ \n │ \n ═╪ \n │ \n ─┼ \n ─┼ \n │ \n ╵ \n");
pub const NONE: Box = Box::parse(" \n \n \n \n \n \n \n \n");
pub const SIMPLE: Box = Box::parse(" \n \n ── \n \n \n ── \n \n \n");
pub const SIMPLE_HEAD: Box = Box::parse(" \n \n ── \n \n \n \n \n \n");
pub const SIMPLE_HEAVY: Box = Box::parse(" \n \n ━━ \n \n \n ━━ \n \n \n");
pub const HORIZONTALS: Box = Box::parse(" ── \n \n ── \n \n ── \n ── \n \n ── \n");
pub const HEAVY_EDGE: Box = Box::parse("┏━┯┓\n┃ │┃\n┠─┼┨\n┃ │┃\n┠─┼┨\n┠─┼┨\n┃ │┃\n┗━┷┛\n");
pub const DOUBLE_EDGE: Box = Box::parse("╔═╤╗\n║ │║\n╟─┼╢\n║ │║\n╟─┼╢\n╟─┼╢\n║ │║\n╚═╧╝\n");
pub const MARKDOWN: Box = Box::parse(" \n| ||\n|-||\n| ||\n|-||\n|-||\n| ||\n \n");
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rounded_corners() {
assert_eq!(ROUNDED.top_left, '╭');
assert_eq!(ROUNDED.top_right, '╮');
assert_eq!(ROUNDED.bottom_left, '╰');
assert_eq!(ROUNDED.bottom_right, '╯');
assert_eq!(ROUNDED.top, '─');
assert_eq!(ROUNDED.mid_left, '│');
assert_eq!(ROUNDED.mid_right, '│');
}
#[test]
fn square_and_ascii() {
assert_eq!(SQUARE.top_left, '┌');
assert_eq!(ASCII.top_left, '+');
assert_eq!(ASCII.top, '-');
assert_eq!(ASCII.mid_left, '|');
}
#[test]
fn get_top_and_bottom_single_column() {
assert_eq!(ROUNDED.get_top(&[3], true), "╭───╮");
assert_eq!(ROUNDED.get_bottom(&[3], true), "╰───╯");
assert_eq!(ROUNDED.get_top(&[3], false), "───");
assert_eq!(SQUARE.get_row(&[2, 2], RowLevel::Head, false), "──┼──");
}
#[test]
fn additional_boxes_parse() {
assert_eq!(SIMPLE.top_left, ' ');
assert_eq!(SIMPLE.head_row_horizontal, '─');
assert_eq!(SIMPLE_HEAVY.head_row_horizontal, '━');
assert_eq!(MARKDOWN.mid_left, '|');
assert_eq!(MARKDOWN.head_row_horizontal, '-');
assert_eq!(DOUBLE_EDGE.top_left, '╔');
assert_eq!(DOUBLE_EDGE.mid_vertical, '│');
assert_eq!(HEAVY_EDGE.top_left, '┏');
assert_eq!(SQUARE_DOUBLE_HEAD.head_row_horizontal, '═');
assert_eq!(ASCII2.head_row_cross, '+');
}
#[test]
fn substitute_legacy_and_ascii() {
assert_eq!(ROUNDED.substitute(true, true, false), SQUARE);
assert_eq!(HEAVY.substitute(true, true, false), SQUARE);
assert_eq!(HEAVY_HEAD.substitute(true, true, false), SQUARE);
assert_eq!(DOUBLE.substitute(true, true, false), DOUBLE);
assert_eq!(SQUARE.substitute(true, true, false), SQUARE);
assert_eq!(ROUNDED.substitute(true, false, false), ROUNDED);
assert_eq!(ROUNDED.substitute(false, true, true), ASCII);
assert_eq!(DOUBLE.substitute(false, true, true), ASCII);
assert_eq!(ASCII.substitute(false, true, true), ASCII);
assert_eq!(ROUNDED.substitute(false, true, false), ROUNDED);
}
}