Support foreground-background for vertical color alignment

This commit is contained in:
Teoh Han Hui 2024-07-19 23:29:42 +08:00
parent 0c7c43d71c
commit 14ff66b836
No known key found for this signature in database
GPG key ID: D43E2BABAF97DCAE
2 changed files with 145 additions and 101 deletions

View file

@ -162,11 +162,8 @@ fn main() -> Result<()> {
}; };
let color_align = if fore_back.is_some() { let color_align = if fore_back.is_some() {
match config.color_align { match config.color_align {
ca @ ColorAlignment::Horizontal { .. } | ca @ ColorAlignment::Vertical { .. } => { ColorAlignment::Horizontal { .. } => ColorAlignment::Horizontal { fore_back },
ca.with_fore_back(fore_back).context( ColorAlignment::Vertical { .. } => ColorAlignment::Vertical { fore_back },
"failed to create color alignment with foreground-background configuration",
)?
},
ca @ ColorAlignment::Custom { .. } => ca, ca @ ColorAlignment::Custom { .. } => ca,
} }
} else { } else {
@ -545,12 +542,12 @@ fn create_config(
fn print_flag_row(row: &[[String; 4]], color_mode: AnsiMode) { fn print_flag_row(row: &[[String; 4]], color_mode: AnsiMode) {
for i in 0..4 { for i in 0..4 {
let mut line = String::new(); let mut line = Vec::new();
for flag in row { for flag in row {
line.push_str(&flag[i]); line.push(&*flag[i]);
line.push_str(" ");
} }
printc(line, color_mode).expect("flag line should not contain invalid color codes"); printc(line.join(" "), color_mode)
.expect("flag line should not contain invalid color codes");
} }
println!(); println!();
} }
@ -698,12 +695,11 @@ fn create_config(
}) })
.collect(); .collect();
for i in 0..usize::from(test_ascii_height) { for i in 0..usize::from(test_ascii_height) {
let mut line = String::new(); let mut line = Vec::new();
for lines in &row { for lines in &row {
line.push_str(&lines[i]); line.push(&*lines[i]);
line.push_str(" ");
} }
printc(line, color_mode) printc(line.join(" "), color_mode)
.expect("test ascii line should not contain invalid color codes"); .expect("test ascii line should not contain invalid color codes");
} }
} }
@ -783,7 +779,7 @@ fn create_config(
let ascii_per_row: u8 = ascii_per_row let ascii_per_row: u8 = ascii_per_row
.try_into() .try_into()
.expect("`ascii_per_row` should fit in `u8`"); .expect("`ascii_per_row` should fit in `u8`");
let ascii_rows = cmp::max(1, (term_h - 8) / u16::from(asc_lines)); let ascii_rows = cmp::max(1, (term_h - 8) / (u16::from(asc_lines) + 1));
let ascii_rows: u8 = ascii_rows let ascii_rows: u8 = ascii_rows
.try_into() .try_into()
.expect("`ascii_rows` should fit in `u8`"); .expect("`ascii_rows` should fit in `u8`");
@ -793,18 +789,8 @@ fn create_config(
// Displays horizontal and vertical arrangements in the first iteration, but // Displays horizontal and vertical arrangements in the first iteration, but
// hide them in later iterations // hide them in later iterations
let hv_arrangements = [ let hv_arrangements = [
( ("Horizontal", ColorAlignment::Horizontal { fore_back }),
"Horizontal", ("Vertical", ColorAlignment::Vertical { fore_back }),
ColorAlignment::Horizontal { fore_back: None }
.with_fore_back(fore_back)
.expect("horizontal color alignment should not be invalid"),
),
(
"Vertical",
ColorAlignment::Vertical { fore_back: None }
.with_fore_back(fore_back)
.expect("vertical color alignment should not be invalid"),
),
]; ];
let mut arrangements: IndexMap<Cow<str>, ColorAlignment> = let mut arrangements: IndexMap<Cow<str>, ColorAlignment> =
hv_arrangements.map(|(k, ca)| (k.into(), ca)).into(); hv_arrangements.map(|(k, ca)| (k.into(), ca)).into();
@ -892,12 +878,11 @@ fn create_config(
// Print by row // Print by row
for i in 0..usize::from(asc_lines) + 1 { for i in 0..usize::from(asc_lines) + 1 {
let mut line = String::new(); let mut line = Vec::new();
for lines in &row { for lines in &row {
line.push_str(&lines[i]); line.push(&*lines[i]);
line.push_str(" ");
} }
printc(line, color_mode) printc(line.join(" "), color_mode)
.expect("ascii line should not contain invalid color codes"); .expect("ascii line should not contain invalid color codes");
} }

View file

@ -1,8 +1,9 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fmt::Write as _;
#[cfg(windows)] #[cfg(windows)]
use std::io; use std::io;
use std::io::Write; use std::io::Write as _;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::sync::OnceLock; use std::sync::OnceLock;
@ -57,33 +58,6 @@ pub enum ColorAlignment {
} }
impl ColorAlignment { impl ColorAlignment {
/// Creates a new color alignment, with the specified foreground-background
/// configuration.
pub fn with_fore_back(&self, fore_back: Option<ForeBackColorPair>) -> Result<Self> {
match self {
Self::Horizontal { .. } => Ok(Self::Horizontal { fore_back }),
Self::Vertical { .. } => {
if fore_back.is_some() {
debug!(
"foreground-background configuration not implemented for vertical color \
alignment; ignoring"
);
}
Ok(Self::Vertical { fore_back: None })
},
Self::Custom { colors } => {
if fore_back.is_some() {
return Err(anyhow!(
"foreground-background configuration not supported for custom colors"
));
}
Ok(Self::Custom {
colors: colors.clone(),
})
},
}
}
/// Uses the color alignment to recolor an ascii art. /// Uses the color alignment to recolor an ascii art.
#[tracing::instrument(level = "debug", skip(asc))] #[tracing::instrument(level = "debug", skip(asc))]
pub fn recolor_ascii<S>( pub fn recolor_ascii<S>(
@ -101,9 +75,6 @@ impl ColorAlignment {
let asc = match self { let asc = match self {
&Self::Horizontal { &Self::Horizontal {
fore_back: Some((fore, back)), fore_back: Some((fore, back)),
}
| &Self::Vertical {
fore_back: Some((fore, back)),
} => { } => {
let asc = fill_starting(asc) let asc = fill_starting(asc)
.context("failed to fill in starting neofetch color codes")?; .context("failed to fill in starting neofetch color codes")?;
@ -121,43 +92,28 @@ impl ColorAlignment {
.expect("foreground color should not be invalid"), .expect("foreground color should not be invalid"),
); );
let lines: Vec<_> = asc.split('\n').collect();
// Add new colors // Add new colors
let asc = match self { let asc = {
Self::Horizontal { .. } => { let ColorProfile { colors } = {
let ColorProfile { colors } = { let (_, length) = ascii_size(&asc);
let length = lines.len(); color_profile
let length: u8 = .with_length(length)
length.try_into().expect("`length` should fit in `u8`"); .context("failed to spread color profile to length")?
color_profile };
.with_length(length) asc.split('\n')
.context("failed to spread color profile to length")? .enumerate()
}; .map(|(i, line)| {
lines let line = line.replace(
.into_iter() &format!("${{c{back}}}", back = u8::from(back)),
.enumerate() &colors[i].to_ansi_string(color_mode, {
.map(|(i, line)| { // note: this is "background" in the ascii art, but
let line = line.replace( // foreground text in terminal
&format!("${{c{back}}}", back = u8::from(back)), ForegroundBackground::Foreground
&colors[i].to_ansi_string(color_mode, { }),
// note: this is "background" in the ascii art, but );
// foreground text in terminal format!("{line}{reset}")
ForegroundBackground::Foreground })
}), .join("\n")
);
format!("{line}{reset}")
})
.join("\n")
},
Self::Vertical { .. } => {
unimplemented!(
"vertical color alignment with fore and back colors not implemented"
);
},
_ => {
unreachable!();
},
}; };
// Remove existing colors // Remove existing colors
@ -171,6 +127,105 @@ impl ColorAlignment {
asc asc
}, },
&Self::Vertical {
fore_back: Some((fore, back)),
} => {
let asc = fill_starting(asc)
.context("failed to fill in starting neofetch color codes")?;
let color_profile = {
let (length, _) = ascii_size(&asc);
color_profile
.with_length(length)
.context("failed to spread color profile to length")?
};
// Apply colors
let asc = {
let ac = NEOFETCH_COLORS_AC
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
asc.split('\n')
.map(|line| {
let mut matches = ac.find_iter(line).peekable();
let mut dst = String::new();
let mut offset = 0;
loop {
let current = matches.next();
let next = matches.peek();
let (neofetch_color_idx, span, done) = match (current, next) {
(Some(m), Some(m_next)) => {
let neofetch_color_idx: NeofetchAsciiIndexedColor = line
[m.start() + 3..m.end() - 1]
.parse()
.expect("neofetch color index should be valid");
offset += m.len();
let mut span = m.span();
span.start = m.end();
span.end = m_next.start();
(neofetch_color_idx, span, false)
},
(Some(m), None) => {
// Last color code
let neofetch_color_idx: NeofetchAsciiIndexedColor = line
[m.start() + 3..m.end() - 1]
.parse()
.expect("neofetch color index should be valid");
offset += m.len();
let mut span = m.span();
span.start = m.end();
span.end = line.len();
(neofetch_color_idx, span, true)
},
(None, _) => {
// No color code in the entire line
unreachable!(
"`fill_starting` ensured each line of ascii art \
starts with neofetch color code"
);
},
};
let txt = &line[span];
if neofetch_color_idx == fore {
let fore = color(
match theme {
TerminalTheme::Light => "&0",
TerminalTheme::Dark => "&f",
},
color_mode,
)
.expect("foreground color should not be invalid");
write!(dst, "{fore}{txt}{reset}").unwrap();
} else if neofetch_color_idx == back {
dst.push_str(
&ColorProfile::new(Vec::from(
&color_profile.colors
[span.start - offset..span.end - offset],
))
.color_text(
txt,
color_mode,
ForegroundBackground::Foreground,
false,
)
.context("failed to color text using color profile")?,
);
} else {
dst.push_str(txt);
}
if done {
break;
}
}
Ok(dst)
})
.collect::<Result<Vec<_>>>()?
.join("\n")
};
asc
},
Self::Horizontal { fore_back: None } | Self::Vertical { fore_back: None } => { Self::Horizontal { fore_back: None } | Self::Vertical { fore_back: None } => {
// Remove existing colors // Remove existing colors
let asc = { let asc = {
@ -187,9 +242,7 @@ impl ColorAlignment {
match self { match self {
Self::Horizontal { .. } => { Self::Horizontal { .. } => {
let ColorProfile { colors } = { let ColorProfile { colors } = {
let length = lines.len(); let (_, length) = ascii_size(&asc);
let length: u8 =
length.try_into().expect("`length` should fit in `u8`");
color_profile color_profile
.with_length(length) .with_length(length)
.context("failed to spread color profile to length")? .context("failed to spread color profile to length")?
@ -215,7 +268,7 @@ impl ColorAlignment {
false, false,
) )
.context("failed to color text using color profile")?; .context("failed to color text using color profile")?;
Ok(format!("{line}{reset}")) Ok(line)
}) })
.collect::<Result<Vec<_>>>()? .collect::<Result<Vec<_>>>()?
.join("\n"), .join("\n"),
@ -248,6 +301,12 @@ impl ColorAlignment {
ac.replace_all(&asc, &replacements) ac.replace_all(&asc, &replacements)
}; };
// Reset colors at end of each line to prevent color bleeding
let asc = asc
.split('\n')
.map(|line| format!("{line}{reset}"))
.join("\n");
asc asc
}, },
}; };