use std::fmt;
use std::error::Error;
const FONT_MAP: &[(&str, &[&str])] = &[
("A", &[
"██████ ",
"██ ██ ",
"████████ ",
"██ ██ "
]),
("B", &[
"██████ ",
"██ ██ ",
"██████ ",
"████████ "
]),
("C", &[
" ████ ",
"██ ",
"██ ",
"██████ "
]),
("D", &[
"██████ ",
"██ ██ ",
"██ ██ ",
"██████ "
]),
("E", &[
"██████ ",
"██ ",
"████ ",
"██████ "
]),
("F", &[
" ████ ",
"██ ",
"██████ ",
"██ "
]),
("G", &[
" ████ ",
"██ ",
"██ ██ ",
"██████ "
]),
("H", &[
"██ ██ ",
"██ ██ ",
"████████ ",
"██ ██ "
]),
("I", &[
"██ ",
"██ ",
"██ ",
"██ "
]),
("J", &[
" ██ ",
" ██ ",
"██ ██ ",
"██████ "
]),
("K", &[
"██ ██ ",
"██ ██ ",
"████ ",
"██ ██ "
]),
("L", &[
"██ ",
"██ ",
"██ ",
"██████ "
]),
("M", &[
"████████ ",
"██ ██ ██ ",
"██ ██ ██ ",
"██ ██ ██ "
]),
("N", &[
"██████ ",
"██ ██ ",
"██ ██ ",
"██ ██ "
]),
("O", &[
"██████ ",
"██ ██ ",
"██ ██ ",
" ██████ "
]),
("P", &[
" ██████ ",
"██ ██ ",
"██████ ",
"██ "
]),
("Q", &[
"██████ ",
"██ ██ ",
"██ ████ ",
"████ ██ "
]),
("R", &[
"██████ ",
"██ ██ ",
"██████ ",
"██ ██ "
]),
("S", &[
"██████ ",
"██ ",
" ██ ",
"██████ "
]),
("T", &[
"████████ ",
" ██ ",
" ██ ",
" ██ "
]),
("U", &[
"██ ██ ",
"██ ██ ",
"██ ██ ",
" ██████ "
]),
("V", &[
"██ ██ ",
"██ ██ ",
"██ ██ ",
"████ "
]),
("W", &[
"██ ██ ██ ",
"██ ██ ██ ",
"██ ██ ██ ",
" ████████ "
]),
("X", &[
"██ ██ ",
" ██ ",
" ██ ",
"██ ██ "
]),
("Y", &[
"██ ██ ",
"████████ ",
" ██ ",
" ██ "
]),
("Z", &[
"████ ██ ",
" ██ ",
" ██ ",
"████████ "
]),
(" ", &[
" ",
" ",
" ",
" "
]),
("1", &[
" ██ ",
"████ ",
" ██ ",
"██████ "
]),
("2", &[
"██████ ",
" ██ ",
"██ ",
"██████ "
]),
("3", &[
"██████ ",
" ██ ",
" ████ ",
"██████ "
]),
("4", &[
"██ ██ ",
"██ ██ ",
"██████ ",
" ██ "
]),
("5", &[
"██████ ",
"██ ",
" ██ ",
"██████ "
]),
("6", &[
"██ ",
"██████ ",
"██ ██ ",
"██████ "
]),
("7", &[
"██████ ",
" ██ ",
" ██ ",
"██ "
]),
("8", &[
"██████ ",
"██ ██ ",
"██ ██ ",
"██████ "
]),
("9", &[
"██████ ",
"██ ██ ",
"██████ ",
" ██ "
]),
(".", &[
" ",
" ",
"██ ",
"██ "
]),
(",", &[
" ",
" ",
"██ ",
"██ "
]),
("!", &[
"██ ",
"██ ",
" ",
"██ "
]),
("?", &[
"██████ ",
" ██ ",
" ",
" ██ "
]),
("-", &[
" ",
"██████",
" ",
" "
]),
("+", &[
" ██ ",
"██████",
" ██ ",
" "
]),
("=", &[
" ",
"██████",
"██████",
" "
]),
("@", &[
"██████ ",
"██ ████",
"██ ██",
" ██████"
]),
("#", &[
" ██ ██ ",
"████████",
"████████",
" ██ ██ "
]),
("$", &[
"██ ",
"██████",
"██████",
" ██"
]),
("%", &[
"██ ██",
" ██ ",
"██ ",
"██ ██"
]),
("&", &[
"████ ",
"██ ██",
" ██ ",
"██ ██"
]),
("*", &[
"██ ██",
" ██ ",
"██ ██",
" "
]),
("(", &[
" ██",
"██ ",
"██ ",
" ██"
]),
(")", &[
"██ ",
" ██",
" ██",
"██ "
]),
("[", &[
"████",
"██ ",
"██ ",
"████"
]),
("]", &[
"████",
" ██",
" ██",
"████"
]),
("{", &[
" ██",
"██ ",
"██ ",
" ██"
]),
("}", &[
"██ ",
" ██",
" ██",
"██ "
]),
("|", &[
"██ ",
"██ ",
"██ ",
"██ "
]),
("/", &[
" ██",
" ██ ",
" ██ ",
"██ "
]),
("\\", &[
"██ ",
" ██ ",
" ██ ",
" ██"
]),
("_", &[
" ",
" ",
" ",
"██████"
]),
("^", &[
" ██ ",
"██ ██",
" ",
" "
]),
("~", &[
" ",
"██ ██ ",
" ██ ██",
" "
]),
("'", &[
"██",
"██",
" ",
" "
]),
("\"", &[
"██ ██",
"██ ██",
" ",
" "
]),
(":", &[
" ",
"██",
" ",
"██"
]),
(";", &[
" ",
"██",
" ",
"██"
]),
("<", &[
" ██",
"██ ",
"██ ",
" ██"
]),
(">", &[
"██ ",
" ██",
" ██",
"██ "
]),
];
const BLOCK: &str = "██";
#[derive(Debug)]
pub enum IncrediError {
InvalidConfig(String),
}
impl fmt::Display for IncrediError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
IncrediError::InvalidConfig(msg) => write!(f, "Invalid banner configuration: {}", msg),
}
}
}
impl Error for IncrediError {}
#[derive(Debug, Clone)]
struct RgbColor {
r: u8,
g: u8,
b: u8,
}
impl RgbColor {
fn new(r: u8, g: u8, b: u8) -> Self {
RgbColor { r, g, b }
}
fn to_ansi(&self) -> String {
format!("\x1b[38;2;{};{};{}m██\x1b[0m", self.r, self.g, self.b)
}
}
const GRADIENT_COLORS: &[(u8, u8, u8)] = &[
(230, 230, 230), (230, 230, 230),
(230, 230, 230),
(230, 230, 230),
(230, 230, 230),
(230, 230, 230), ];
const RAINBOW_COLORS: &[(u8, u8, u8)] = &[
(63, 81, 181), (33, 150, 243), (3, 169, 244), (0, 150, 136), (76, 175, 80), (205, 220, 57), (255, 193, 7), (255, 152, 0), (255, 87, 34), (244, 67, 54), ];
#[derive(Debug)]
pub struct UnvalidatedBanner {
text: String,
colors: bool,
subtitle: Option<String>,
line_length: Option<usize>,
}
#[derive(Debug)]
pub struct Banner {
text: String,
colors: bool,
subtitle: Option<String>,
line_length: usize,
}
impl UnvalidatedBanner {
pub fn build(self) -> Result<Banner, IncrediError> {
if self.text.is_empty() {
return Err(IncrediError::InvalidConfig("Text cannot be empty".into()));
}
Ok(Banner {
text: self.text.to_uppercase(),
colors: self.colors,
subtitle: self.subtitle,
line_length: self.line_length.unwrap_or(80),
})
}
pub fn with_colors(mut self) -> Self {
self.colors = true;
self
}
pub fn with_subtitle<S: Into<String>>(mut self, subtitle: S) -> Self {
self.subtitle = Some(subtitle.into());
self
}
pub fn with_line_length(mut self, length: usize) -> Self {
self.line_length = Some(length);
self
}
}
impl Banner {
pub fn new<S: Into<String>>(text: S) -> UnvalidatedBanner {
UnvalidatedBanner {
text: text.into(),
colors: false,
subtitle: None,
line_length: None,
}
}
fn count_blocks(text: &str) -> usize {
text.matches(BLOCK).count()
}
pub fn render(&self) -> String {
let mut result = String::new();
let chars: Vec<char> = self.text.chars().collect();
let lines: Vec<Vec<&str>> = (0..4).map(|i| {
chars.iter()
.map(|c| get_char_line(&c.to_string(), i))
.collect()
}).collect();
for line in &lines {
let line_text = line.join("");
if self.colors {
let total_blocks = Self::count_blocks(&line_text);
let mut block_count = 0;
let mut chars = line_text.chars().peekable();
while let Some(c) = chars.next() {
if c == '█' && chars.peek() == Some(&'█') {
let color = if block_count + RAINBOW_COLORS.len() >= total_blocks {
let rainbow_index = RAINBOW_COLORS.len() - (total_blocks - block_count);
let (r, g, b) = RAINBOW_COLORS[rainbow_index];
RgbColor::new(r, g, b)
} else {
RgbColor::new(230, 230, 230)
};
result.push_str(&color.to_ansi());
block_count += 1;
chars.next(); } else {
result.push(c);
}
}
} else {
result.push_str(&line_text);
}
result.push('\n');
}
if let Some(subtitle) = &self.subtitle {
result.push_str(subtitle);
result.push('\n');
}
result
}
}
fn get_char_line(c: &str, line: usize) -> &'static str {
FONT_MAP
.iter()
.find(|(ch, _)| *ch == c)
.map(|(_, lines)| lines[line])
.unwrap_or(FONT_MAP.last().unwrap().1[line])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_banner_creation() {
let banner = Banner::new("TEST")
.with_colors()
.with_subtitle("test")
.with_line_length(80)
.build()
.unwrap();
assert!(banner.render().contains("██████"));
}
#[test]
fn test_empty_input() {
let result = Banner::new("")
.build();
assert!(result.is_err());
}
#[test]
fn test_invalid_character() {
let result = Banner::new("Test!")
.build();
assert!(result.is_err());
}
#[test]
fn test_colors() {
let banner = Banner::new("TEST")
.with_colors()
.build()
.unwrap();
assert!(banner.render().contains("\x1b[38;2;"));
}
}