Merge pull request #32 from teohhanhui/riir

Support foreground-background for vertical color alignment
This commit is contained in:
Teoh Han Hui 2024-07-19 23:34:25 +08:00 committed by GitHub
commit 8edb50c4d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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() {
match config.color_align {
ca @ ColorAlignment::Horizontal { .. } | ca @ ColorAlignment::Vertical { .. } => {
ca.with_fore_back(fore_back).context(
"failed to create color alignment with foreground-background configuration",
)?
},
ColorAlignment::Horizontal { .. } => ColorAlignment::Horizontal { fore_back },
ColorAlignment::Vertical { .. } => ColorAlignment::Vertical { fore_back },
ca @ ColorAlignment::Custom { .. } => ca,
}
} else {
@ -545,12 +542,12 @@ fn create_config(
fn print_flag_row(row: &[[String; 4]], color_mode: AnsiMode) {
for i in 0..4 {
let mut line = String::new();
let mut line = Vec::new();
for flag in row {
line.push_str(&flag[i]);
line.push_str(" ");
line.push(&*flag[i]);
}
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!();
}
@ -698,12 +695,11 @@ fn create_config(
})
.collect();
for i in 0..usize::from(test_ascii_height) {
let mut line = String::new();
let mut line = Vec::new();
for lines in &row {
line.push_str(&lines[i]);
line.push_str(" ");
line.push(&*lines[i]);
}
printc(line, color_mode)
printc(line.join(" "), color_mode)
.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
.try_into()
.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
.try_into()
.expect("`ascii_rows` should fit in `u8`");
@ -793,18 +789,8 @@ fn create_config(
// Displays horizontal and vertical arrangements in the first iteration, but
// hide them in later iterations
let hv_arrangements = [
(
"Horizontal",
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"),
),
("Horizontal", ColorAlignment::Horizontal { fore_back }),
("Vertical", ColorAlignment::Vertical { fore_back }),
];
let mut arrangements: IndexMap<Cow<str>, ColorAlignment> =
hv_arrangements.map(|(k, ca)| (k.into(), ca)).into();
@ -892,12 +878,11 @@ fn create_config(
// Print by row
for i in 0..usize::from(asc_lines) + 1 {
let mut line = String::new();
let mut line = Vec::new();
for lines in &row {
line.push_str(&lines[i]);
line.push_str(" ");
line.push(&*lines[i]);
}
printc(line, color_mode)
printc(line.join(" "), color_mode)
.expect("ascii line should not contain invalid color codes");
}

View file

@ -1,8 +1,9 @@
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fmt::Write as _;
#[cfg(windows)]
use std::io;
use std::io::Write;
use std::io::Write as _;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::OnceLock;
@ -57,33 +58,6 @@ pub enum 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.
#[tracing::instrument(level = "debug", skip(asc))]
pub fn recolor_ascii<S>(
@ -101,9 +75,6 @@ impl ColorAlignment {
let asc = match self {
&Self::Horizontal {
fore_back: Some((fore, back)),
}
| &Self::Vertical {
fore_back: Some((fore, back)),
} => {
let asc = fill_starting(asc)
.context("failed to fill in starting neofetch color codes")?;
@ -121,43 +92,28 @@ impl ColorAlignment {
.expect("foreground color should not be invalid"),
);
let lines: Vec<_> = asc.split('\n').collect();
// Add new colors
let asc = match self {
Self::Horizontal { .. } => {
let ColorProfile { colors } = {
let length = lines.len();
let length: u8 =
length.try_into().expect("`length` should fit in `u8`");
color_profile
.with_length(length)
.context("failed to spread color profile to length")?
};
lines
.into_iter()
.enumerate()
.map(|(i, line)| {
let line = line.replace(
&format!("${{c{back}}}", back = u8::from(back)),
&colors[i].to_ansi_string(color_mode, {
// note: this is "background" in the ascii art, but
// foreground text in terminal
ForegroundBackground::Foreground
}),
);
format!("{line}{reset}")
})
.join("\n")
},
Self::Vertical { .. } => {
unimplemented!(
"vertical color alignment with fore and back colors not implemented"
);
},
_ => {
unreachable!();
},
let asc = {
let ColorProfile { colors } = {
let (_, length) = ascii_size(&asc);
color_profile
.with_length(length)
.context("failed to spread color profile to length")?
};
asc.split('\n')
.enumerate()
.map(|(i, line)| {
let line = line.replace(
&format!("${{c{back}}}", back = u8::from(back)),
&colors[i].to_ansi_string(color_mode, {
// note: this is "background" in the ascii art, but
// foreground text in terminal
ForegroundBackground::Foreground
}),
);
format!("{line}{reset}")
})
.join("\n")
};
// Remove existing colors
@ -171,6 +127,105 @@ impl ColorAlignment {
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 } => {
// Remove existing colors
let asc = {
@ -187,9 +242,7 @@ impl ColorAlignment {
match self {
Self::Horizontal { .. } => {
let ColorProfile { colors } = {
let length = lines.len();
let length: u8 =
length.try_into().expect("`length` should fit in `u8`");
let (_, length) = ascii_size(&asc);
color_profile
.with_length(length)
.context("failed to spread color profile to length")?
@ -215,7 +268,7 @@ impl ColorAlignment {
false,
)
.context("failed to color text using color profile")?;
Ok(format!("{line}{reset}"))
Ok(line)
})
.collect::<Result<Vec<_>>>()?
.join("\n"),
@ -248,6 +301,12 @@ impl ColorAlignment {
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
},
};