use owo_colors::AnsiColors;
use owo_colors::Styled;
use owo_colors::DynColors;
use owo_colors::OwoColorize;
use owo_colors::Style;
use std::fmt::Write;
use std::str::Chars;
pub struct AsciiArt<'a> {
content: Box<dyn 'a + Iterator<Item = &'a str>>,
colors: &'a [DynColors],
bold: bool,
start: usize,
end: usize
}
impl<'a> AsciiArt<'a> {
pub fn new(input: &'a str, colors: &'a [DynColors], bold: bool) -> AsciiArt<'a> {
let mut lines: Vec<_> = input.lines().skip_while(|line: &&str| line.is_empty()).collect();
while let Some(line) = lines.last() {
if Tokens(line).has_no_solid_tokens() {
lines.pop();
} else {
break;
}
}
let (start, end): (usize, usize) = get_min_start_max_end(&lines);
AsciiArt {
content: Box::new(lines.into_iter()),
colors,
bold,
start,
end
}
}
}
fn get_min_start_max_end(lines: &[&str]) -> (usize, usize) {
lines.iter().map(|line: &&str| {
let line_start: usize = Tokens(line).leading_spaces();
let line_end: usize = Tokens(line).true_length();
(line_start, line_end)
}).fold((usize::MAX, 0), |(acc_s, acc_e): (usize, usize), (line_s, line_e): (usize, usize)| {
(acc_s.min(line_s), acc_e.max(line_e))
})
}
impl Iterator for AsciiArt<'_> {
type Item = String;
fn next(&mut self) -> Option<String> {
self.content.next().map(|line: &str| Tokens(line).render(self.colors, self.start, self.end, self.bold))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum Token {
Color(u32),
Char(char),
Space
}
impl std::fmt::Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Token::Color(c) => write!(f, "{{{}}}", c),
Token::Char(c) => write!(f, "{}", c),
Token::Space => write!(f, " "),
}
}
}
impl Token {
fn is_solid(&self) -> bool {
matches!(*self, Token::Char(_))
}
fn is_space(&self) -> bool {
matches!(*self, Token::Space)
}
fn has_zero_width(&self) -> bool {
matches!(*self, Token::Color(_))
}
}
#[derive(Clone, Debug)]
struct Tokens<'a>(&'a str);
impl Iterator for Tokens<'_> {
type Item = Token;
fn next(&mut self) -> Option<Token> {
let (s, tok): (&str, Token) = color_token(self.0).or_else(|| space_token(self.0)).or_else(|| char_token(self.0))?;
self.0 = s;
Some(tok)
}
}
impl<'a> Tokens<'a> {
fn has_no_solid_tokens(&mut self) -> bool {
for token in self {
if token.is_solid() {
return false;
}
}
true
}
fn true_length(&mut self) -> usize {
let mut last_non_space: usize = 0;
let mut last: usize = 0;
for token in self {
if token.has_zero_width() {
continue;
}
last += 1;
if !token.is_space() {
last_non_space = last;
}
}
last_non_space
}
fn leading_spaces(&mut self) -> usize {
self.take_while(|token: &Token| !token.is_solid()).filter(Token::is_space).count()
}
fn truncate(self, mut start: usize, end: usize) -> impl 'a + Iterator<Item = Token> {
assert!(start <= end);
let mut width: usize = end - start;
self.filter(move |token: &Token| {
if (start > 0) && !token.has_zero_width() {
start -= 1;
return false;
}
true
}).take_while(move |token: &Token| {
if width == 0 {
return false;
}
if !token.has_zero_width() {
width -= 1;
}
true
})
}
fn render(self, colors: &[DynColors], start: usize, end: usize, bold: bool) -> String {
assert!(start <= end);
let mut width: usize = end - start;
let mut colored_segment: String = String::new();
let mut whole_string: String = String::new();
let mut color: &DynColors = &DynColors::Ansi(AnsiColors::Default);
self.truncate(start, end).for_each(|token: Token| {
match token {
Token::Char(chr) => {
width = width.saturating_sub(1);
colored_segment.push(chr);
},
Token::Color(col) => {
add_styled_segment(&mut whole_string, &colored_segment, *color, bold);
colored_segment = String::new();
color = colors.get(col as usize).unwrap_or(&DynColors::Ansi(AnsiColors::Default));
},
Token::Space => {
width = width.saturating_sub(1);
colored_segment.push(' ')
}
};
});
add_styled_segment(&mut whole_string, &colored_segment, *color, bold);
(0..width).for_each(|_| whole_string.push(' '));
whole_string
}
}
fn succeed_when<I>(predicate: impl FnOnce(I) -> bool) -> impl FnOnce(I) -> Option<()> {
|input: I| {
if predicate(input) {
Some(())
} else {
None
}
}
}
fn add_styled_segment(base: &mut String, segment: &str, color: DynColors, bold: bool) -> () {
let mut style: Style = Style::new().color(color);
if bold {
style = style.bold();
}
let formatted_segment: Styled<&&str> = segment.style(style);
let _ = write!(base, "{}", formatted_segment);
}
type ParseResult<'a, R> = Option<(&'a str, R)>;
fn token<R>(s: &str, predicate: impl FnOnce(char) -> Option<R>) -> ParseResult<R> {
let mut chars: Chars = s.chars();
let token: char = chars.next()?;
let result: R = predicate(token)?;
Some((chars.as_str(), result))
}
fn color_token(s: &str) -> ParseResult<Token> {
let (s, _): (&str, ()) = token(s, succeed_when(|c: char| c == '{'))?;
let (s, color_index): (&str, u32) = token(s, |c: char| c.to_digit(10))?;
let (s, _): (&str, ()) = token(s, succeed_when(|c: char| c == '}'))?;
Some((s, Token::Color(color_index)))
}
fn space_token(s: &str) -> ParseResult<Token> {
token(s, succeed_when(|c: char| c == ' ')).map(|(s, _): (&str, _)| (s, Token::Space))
}
fn char_token(s: &str) -> ParseResult<Token> {
token(s, |c: char| Some(Token::Char(c)))
}