Track only fg color indices in ascii art; the rest are bg colors

* Fix vertical fore-back coloring when there are non-ASCII chars
* Allow 0-width / 0-height ascii art (again)

Co-authored-by: Luna <contact@luna.computer>
This commit is contained in:
Teoh Han Hui 2024-07-28 02:35:32 +08:00
parent 52844b55ad
commit bcdc720d8a
No known key found for this signature in database
GPG key ID: D43E2BABAF97DCAE
4 changed files with 261 additions and 296 deletions

View file

@ -1,11 +1,12 @@
use std::borrow::Cow;
use std::fmt::Write as _;
use std::num::NonZeroU8;
use std::ops::Range;
use aho_corasick::AhoCorasick;
use anyhow::{Context as _, Result};
use itertools::Itertools as _;
use indexmap::IndexMap;
use tracing::debug;
use unicode_segmentation::UnicodeSegmentation;
use crate::color_util::{
color, ForegroundBackground, NeofetchAsciiIndexedColor, ToAnsiString as _,
@ -21,25 +22,23 @@ use crate::types::{AnsiMode, TerminalTheme};
pub struct RawAsciiArt {
pub asc: String,
pub fg: Vec<NeofetchAsciiIndexedColor>,
pub bg: Vec<NeofetchAsciiIndexedColor>,
}
/// Normalized ascii art where every line has the same width.
#[derive(Clone, Debug)]
pub struct NormalizedAsciiArt {
pub lines: Vec<String>,
pub w: NonZeroU8,
pub h: NonZeroU8,
pub w: u8,
pub h: u8,
pub fg: Vec<NeofetchAsciiIndexedColor>,
pub bg: Vec<NeofetchAsciiIndexedColor>,
}
/// Recolored ascii art with all color codes replaced.
#[derive(Clone, Debug)]
pub struct RecoloredAsciiArt {
pub lines: Vec<String>,
pub w: NonZeroU8,
pub h: NonZeroU8,
pub w: u8,
pub h: u8,
}
impl RawAsciiArt {
@ -55,7 +54,7 @@ impl RawAsciiArt {
.lines()
.map(|line| {
let (line_w, _) = ascii_size(line).unwrap();
let pad = " ".repeat(usize::from(w.get().checked_sub(line_w.get()).unwrap()));
let pad = " ".repeat(usize::from(w.checked_sub(line_w).unwrap()));
format!("{line}{pad}")
})
.collect();
@ -65,7 +64,6 @@ impl RawAsciiArt {
w,
h,
fg: self.fg.clone(),
bg: self.bg.clone(),
})
}
}
@ -82,12 +80,18 @@ impl NormalizedAsciiArt {
) -> Result<RecoloredAsciiArt> {
debug!("recolor ascii");
if self.lines.is_empty() {
return Ok(RecoloredAsciiArt {
lines: self.lines.clone(),
w: 0,
h: 0,
});
}
let reset = color("&~&*", color_mode).expect("color reset should not be invalid");
let lines = match (color_align, self) {
(ColorAlignment::Horizontal, Self { fg, bg, .. })
if !fg.is_empty() || !bg.is_empty() =>
{
(ColorAlignment::Horizontal, Self { fg, .. }) => {
let Self { lines, .. } = self
.fill_starting()
.context("failed to fill in starting neofetch color codes")?;
@ -117,156 +121,160 @@ impl NormalizedAsciiArt {
// Add new colors
let lines = {
let ColorProfile { colors } =
color_profile.with_length(self.h).with_context(|| {
let ColorProfile { colors } = color_profile
.with_length(self.h.try_into().expect("`h` should not be 0"))
.with_context(|| {
format!("failed to spread color profile to length {h}", h = self.h)
})?;
lines.enumerate().map(move |(i, line)| {
let mut replacements = NEOFETCH_COLOR_PATTERNS;
let bg_color = colors[i].to_ansi_string(color_mode, {
// This is "background" in the ascii art, but foreground text in
// terminal
ForegroundBackground::Foreground
});
for &back in bg {
replacements[usize::from(u8::from(back)).checked_sub(1).unwrap()] =
&bg_color;
}
let bg_color =
colors[i].to_ansi_string(color_mode, ForegroundBackground::Foreground);
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
let replacements = [&bg_color; N];
ac.replace_all(line, &replacements)
})
};
// Remove existing colors
let asc = {
let mut lines = lines;
let asc = lines.join("\n");
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
let replacements: [&str; N] = [&reset; N];
ac.replace_all(&asc, &replacements)
};
let lines = asc.lines();
// Reset colors at end of each line to prevent color bleeding
let lines = lines.map(|line| format!("{line}{reset}"));
lines.collect()
lines.map(|line| format!("{line}{reset}")).collect()
},
(ColorAlignment::Vertical, Self { fg, bg, .. }) if !fg.is_empty() || !bg.is_empty() => {
(ColorAlignment::Vertical, Self { fg, .. }) if !fg.is_empty() => {
if self.w == 0 {
return Ok(RecoloredAsciiArt {
lines: self.lines.clone(),
w: 0,
h: self.h,
});
}
let Self { lines, .. } = self
.fill_starting()
.context("failed to fill in starting neofetch color codes")?;
let color_profile = color_profile.with_length(self.w).with_context(|| {
format!("failed to spread color profile to length {w}", w = self.w)
})?;
let color_profile = color_profile
.with_length(self.w.try_into().expect("`w` should not be 0"))
.with_context(|| {
format!("failed to spread color profile to length {w}", w = self.w)
})?;
// Apply colors
let lines: Vec<_> = {
let ac = NEOFETCH_COLORS_AC
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
lines
.into_iter()
.map(|line| {
let line: &str = line.as_ref();
let ac = NEOFETCH_COLORS_AC
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
lines
.into_iter()
.map(|line| {
let line: &str = line.as_ref();
let mut matches = ac.find_iter(line).peekable();
let mut dst = String::new();
let mut offset: u8 = 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 ai_start = m.start().checked_add(3).unwrap();
let ai_end = m.end().checked_sub(1).unwrap();
let neofetch_color_idx: NeofetchAsciiIndexedColor = line
[ai_start..ai_end]
.parse()
.expect("neofetch color index should be valid");
offset = offset
.checked_add(u8::try_from(m.len()).unwrap())
.unwrap();
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 ai_start = m.start().checked_add(3).unwrap();
let ai_end = m.end().checked_sub(1).unwrap();
let neofetch_color_idx: NeofetchAsciiIndexedColor = line
[ai_start..ai_end]
.parse()
.expect("neofetch color index should be valid");
offset = offset
.checked_add(u8::try_from(m.len()).unwrap())
.unwrap();
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];
// `AhoCorasick` operates on bytes; we need to map that back to grapheme
// clusters (i.e. a character as seen on the terminal)
// See https://github.com/BurntSushi/aho-corasick/issues/72#issuecomment-821128859
let byte_idx_to_grapheme_idx: IndexMap<usize, usize> = {
let mut m: IndexMap<_, _> = line
.grapheme_indices(true)
.enumerate()
.map(|(gr_idx, (byte_idx, _))| (byte_idx, gr_idx))
.collect();
// Add an extra entry at the end, to support lookup using exclusive
// range end
m.insert(line.len(), m.len());
m
};
if fg.contains(&neofetch_color_idx) {
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 bg.contains(&neofetch_color_idx) {
let adjusted_start =
span.start.checked_sub(usize::from(offset)).unwrap();
let adjusted_end =
span.end.checked_sub(usize::from(offset)).unwrap();
dst.push_str(
&ColorProfile::new(Vec::from(
&color_profile.colors[adjusted_start..adjusted_end],
))
let mut matches = ac.find_iter(line).peekable();
let mut dst = String::new();
let mut offset: u8 = 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 ai_start = m.start().checked_add(3).unwrap();
let ai_end = m.end().checked_sub(1).unwrap();
let neofetch_color_idx: NeofetchAsciiIndexedColor = line
[ai_start..ai_end]
.parse()
.expect("neofetch color index should be valid");
if offset == 0 && m.start() > 0 {
dst.push_str(&line[..m.start()]);
}
offset =
offset.checked_add(u8::try_from(m.len()).unwrap()).unwrap();
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 ai_start = m.start().checked_add(3).unwrap();
let ai_end = m.end().checked_sub(1).unwrap();
let neofetch_color_idx: NeofetchAsciiIndexedColor = line
[ai_start..ai_end]
.parse()
.expect("neofetch color index should be valid");
if offset == 0 && m.start() > 0 {
dst.push_str(&line[..m.start()]);
}
offset =
offset.checked_add(u8::try_from(m.len()).unwrap()).unwrap();
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 fg.contains(&neofetch_color_idx) {
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 {
let mut c_range: Range<usize> = span.into();
c_range.start = byte_idx_to_grapheme_idx
.get(&c_range.start)
.unwrap()
.checked_sub(usize::from(offset))
.unwrap();
c_range.end = byte_idx_to_grapheme_idx
.get(&c_range.end)
.unwrap()
.checked_sub(usize::from(offset))
.unwrap();
dst.push_str(
&ColorProfile::new(Vec::from(&color_profile.colors[c_range]))
.color_text(
txt,
color_mode,
{
// This is "background" in the ascii art, but
// foreground text in terminal
ForegroundBackground::Foreground
},
ForegroundBackground::Foreground,
false,
)
.context("failed to color text using color profile")?,
);
} else {
dst.push_str(txt);
}
if done {
break;
}
);
}
Ok(dst)
})
.collect::<Result<_>>()?
};
lines
if done {
break;
}
}
Ok(dst)
})
.collect::<Result<_>>()?
},
(ColorAlignment::Horizontal, Self { fg, bg, .. })
| (ColorAlignment::Vertical, Self { fg, bg, .. })
if fg.is_empty() && bg.is_empty() =>
{
(ColorAlignment::Vertical, Self { fg, .. }) if fg.is_empty() => {
// Remove existing colors
let asc = {
let asc = self.lines.join("\n");
@ -279,38 +287,14 @@ impl NormalizedAsciiArt {
let lines = asc.lines();
// Add new colors
match color_align {
ColorAlignment::Horizontal => {
let ColorProfile { colors } =
color_profile.with_length(self.h).with_context(|| {
format!("failed to spread color profile to length {h}", h = self.h)
})?;
lines
.enumerate()
.map(|(i, line)| {
let fore = colors[i]
.to_ansi_string(color_mode, ForegroundBackground::Foreground);
format!("{fore}{line}{reset}")
})
.collect()
},
ColorAlignment::Vertical => lines
.map(|line| {
let line = color_profile
.color_text(
line,
color_mode,
ForegroundBackground::Foreground,
false,
)
.context("failed to color text using color profile")?;
Ok(line)
})
.collect::<Result<_>>()?,
_ => {
unreachable!();
},
}
lines
.map(|line| {
let line = color_profile
.color_text(line, color_mode, ForegroundBackground::Foreground, false)
.context("failed to color text using color profile")?;
Ok(line)
})
.collect::<Result<_>>()?
},
(
ColorAlignment::Custom {
@ -344,9 +328,7 @@ impl NormalizedAsciiArt {
let lines = asc.lines();
// Reset colors at end of each line to prevent color bleeding
let lines = lines.map(|line| format!("{line}{reset}"));
lines.collect()
lines.map(|line| format!("{line}{reset}")).collect()
},
_ => {
unreachable!()
@ -382,19 +364,24 @@ impl NormalizedAsciiArt {
if m.start() == 0
|| line[0..m.start()].trim_end_matches(' ').is_empty() =>
{
// line starts with neofetch color code, do nothing
// Line starts with neofetch color code
last = Some(&line[m.span()]);
},
_ => {
Some(_) => {
new.push_str(last.context(
"failed to find neofetch color code from a previous line",
)?);
},
None => {
new.push_str(last.unwrap_or(NEOFETCH_COLOR_PATTERNS[0]));
},
}
new.push_str(line);
// Get the last placeholder for the next line
if let Some(m) = matches.last() {
last = Some(&line[m.span()])
last.context("non-space character seen before first color code")?;
last = Some(&line[m.span()]);
}
Ok(new)
@ -404,7 +391,6 @@ impl NormalizedAsciiArt {
Ok(Self {
lines,
fg: self.fg.clone(),
bg: self.bg.clone(),
..*self
})
}

View file

@ -4,7 +4,7 @@ use std::fmt::Write as _;
use std::fs::{self, File};
use std::io::{self, IsTerminal as _, Read as _, Write as _};
use std::iter::zip;
use std::num::{NonZeroU16, NonZeroU8, NonZeroUsize};
use std::num::NonZeroU8;
use std::path::{Path, PathBuf};
use aho_corasick::AhoCorasick;
@ -22,8 +22,8 @@ use hyfetch::models::Config;
#[cfg(feature = "macchina")]
use hyfetch::neofetch_util::macchina_path;
use hyfetch::neofetch_util::{
self, ascii_size, fastfetch_path, get_distro_ascii, literal_input, ColorAlignment,
NEOFETCH_COLORS_AC, NEOFETCH_COLOR_PATTERNS, TEST_ASCII,
self, fastfetch_path, get_distro_ascii, literal_input, ColorAlignment, NEOFETCH_COLORS_AC,
NEOFETCH_COLOR_PATTERNS, TEST_ASCII,
};
use hyfetch::presets::{AssignLightness, Preset};
use hyfetch::pride_month;
@ -132,7 +132,6 @@ fn main() -> Result<()> {
asc: fs::read_to_string(&path)
.with_context(|| format!("failed to read ascii from {path:?}"))?,
fg: Vec::new(),
bg: Vec::new(),
}
} else {
get_distro_ascii(distro, backend).context("failed to get distro ascii")?
@ -267,14 +266,14 @@ fn create_config(
let (Width(term_w), Height(term_h)) =
terminal_size().context("failed to get terminal size")?;
let (term_w_min, term_h_min) = (
NonZeroU16::from(asc.w)
.checked_mul(NonZeroU16::new(2).unwrap())
u16::from(asc.w)
.checked_mul(2)
.unwrap()
.checked_add(4)
.unwrap(),
NonZeroU16::new(30).unwrap(),
30,
);
if term_w < term_w_min.get() || term_h < term_h_min.get() {
if term_w < term_w_min || term_h < term_h_min {
printc(
format!(
"&cWarning: Your terminal is too small ({term_w} * {term_h}).\nPlease resize \
@ -612,9 +611,15 @@ fn create_config(
//////////////////////////////
// 4. Dim/lighten colors
let test_ascii = &TEST_ASCII[1..TEST_ASCII.len().checked_sub(1).unwrap()];
let (test_ascii_width, test_ascii_height) =
ascii_size(test_ascii).expect("test ascii should have valid width and height");
let test_ascii = {
let asc = &TEST_ASCII[1..TEST_ASCII.len().checked_sub(1).unwrap()];
let asc = RawAsciiArt {
asc: asc.to_owned(),
fg: Vec::new(),
};
asc.to_normalized()
.expect("normalizing test ascii should not fail")
};
let select_lightness = || -> Result<Lightness> {
clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
@ -642,12 +647,7 @@ fn create_config(
let (Width(term_w), _) = terminal_size().context("failed to get terminal size")?;
let num_cols = cmp::max(
1,
term_w.div_euclid(
NonZeroU16::from(test_ascii_width)
.checked_add(2)
.unwrap()
.get(),
),
term_w.div_euclid(u16::from(test_ascii.w).checked_add(2).unwrap()),
);
let num_cols: u8 = num_cols.try_into().expect("`num_cols` should fit in `u8`");
const MIN: f32 = 0.15;
@ -661,20 +661,20 @@ fn create_config(
});
let row: Vec<Vec<String>> = ratios
.map(|r| {
let asc = RawAsciiArt {
asc: test_ascii.replace(
let mut asc = test_ascii.clone();
asc.lines = asc
.lines
.join("\n")
.replace(
"{txt}",
&format!(
"{lightness:^5}",
lightness = format!("{lightness:.0}%", lightness = r * 100.0)
),
),
fg: Vec::new(),
bg: Vec::new(),
};
let asc = asc
.to_normalized()
.expect("normalizing test ascii should not fail");
)
.lines()
.map(ToOwned::to_owned)
.collect();
let asc = asc
.to_recolored(
&color_align,
@ -690,7 +690,7 @@ fn create_config(
asc.lines
})
.collect();
for i in 0..NonZeroUsize::from(test_ascii_height).get() {
for i in 0..usize::from(test_ascii.h) {
let mut line = Vec::new();
for lines in &row {
line.push(&*lines[i]);
@ -769,7 +769,7 @@ fn create_config(
terminal_size().context("failed to get terminal size")?;
let ascii_per_row = cmp::max(
1,
term_w.div_euclid(NonZeroU16::from(asc.w).checked_add(2).unwrap().get()),
term_w.div_euclid(u16::from(asc.w).checked_add(2).unwrap()),
);
let ascii_per_row: u8 = ascii_per_row
.try_into()
@ -778,7 +778,7 @@ fn create_config(
1,
term_h
.saturating_sub(8)
.div_euclid(NonZeroU16::from(asc.h).checked_add(1).unwrap().get()),
.div_euclid(u16::from(asc.h).checked_add(1).unwrap()),
);
let ascii_rows: u8 = ascii_rows
.try_into()
@ -866,10 +866,7 @@ fn create_config(
.to_recolored(ca, &color_profile, color_mode, theme)
.context("failed to recolor ascii")?
.lines;
v.push(format!(
"{k:^asc_width$}",
asc_width = NonZeroUsize::from(asc.w).get()
));
v.push(format!("{k:^asc_width$}", asc_width = usize::from(asc.w)));
Ok(v)
})
.collect::<Result<_>>()?;
@ -878,7 +875,7 @@ fn create_config(
let row: Vec<Vec<String>> = row.collect();
// Print by row
for i in 0..NonZeroUsize::from(asc.h).checked_add(1).unwrap().get() {
for i in 0..usize::from(asc.h).checked_add(1).unwrap() {
let mut line = Vec::new();
for lines in &row {
line.push(&*lines[i]);

View file

@ -5,7 +5,6 @@ use std::fs;
#[cfg(windows)]
use std::io;
use std::io::{self, Write as _};
use std::num::{NonZeroU8, NonZeroUsize};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::OnceLock;
@ -290,9 +289,9 @@ where
// Try new codegen-based detection method
if let Some(distro) = Distro::detect(&distro) {
let asc = distro.ascii_art().to_owned();
let (fg, bg) = fore_back(&distro);
let fg = ascii_foreground(&distro);
return Ok(RawAsciiArt { asc, fg, bg });
return Ok(RawAsciiArt { asc, fg });
}
debug!(%distro, "could not find a match for distro; falling back to neofetch");
@ -308,7 +307,6 @@ where
Ok(RawAsciiArt {
asc,
fg: Vec::new(),
bg: Vec::new(),
})
}
@ -333,12 +331,16 @@ pub fn run(asc: RecoloredAsciiArt, backend: Backend, args: Option<&Vec<String>>)
}
/// Gets distro ascii width and height, ignoring color code.
pub fn ascii_size<S>(asc: S) -> Result<(NonZeroU8, NonZeroU8)>
pub fn ascii_size<S>(asc: S) -> Result<(u8, u8)>
where
S: AsRef<str>,
{
let asc = asc.as_ref();
if asc.is_empty() {
return Ok((0, 0));
}
let asc = {
let ac =
NEOFETCH_COLORS_AC.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
@ -347,21 +349,23 @@ where
ac.replace_all(asc, &REPLACEMENTS)
};
if asc.is_empty() {
return Ok((0, 0));
}
let width = asc
.lines()
.map(|line| line.graphemes(true).count())
.max()
.expect("line iterator should not be empty");
let width: NonZeroUsize = width.try_into().context("`asc` should not be empty")?;
let width: NonZeroU8 = width.try_into().with_context(|| {
let width: u8 = width.try_into().with_context(|| {
format!(
"`asc` should not have more than {limit} characters per line",
limit = u8::MAX
)
})?;
let height = asc.lines().count();
let height: NonZeroUsize = height.try_into().context("`asc` should not be empty")?;
let height: NonZeroU8 = height.try_into().with_context(|| {
let height: u8 = height.try_into().with_context(|| {
format!(
"`asc` should not have more than {limit} lines",
limit = u8::MAX
@ -827,52 +831,41 @@ fn run_macchina(asc: String, args: Option<&Vec<String>>) -> Result<()> {
Ok(())
}
/// Gets recommended foreground-background configuration for distro.
fn fore_back(
distro: &Distro,
) -> (
Vec<NeofetchAsciiIndexedColor>,
Vec<NeofetchAsciiIndexedColor>,
) {
let (fg, bg): (Vec<u8>, Vec<u8>) = match distro {
Distro::Anarchy => (vec![2], vec![1]),
Distro::Antergos => (vec![1], vec![2]),
Distro::ArchStrike => (vec![2], vec![1]),
Distro::Astra_Linux => (vec![2], vec![1]),
Distro::Chapeau => (vec![2], vec![1]),
Distro::Fedora => (vec![2], vec![1]),
Distro::Fedora_Silverblue => (vec![2], vec![1, 3]),
Distro::GalliumOS => (vec![2], vec![1]),
Distro::KrassOS => (vec![2], vec![1]),
Distro::Kubuntu => (vec![2], vec![1]),
Distro::Lubuntu => (vec![2], vec![1]),
Distro::openEuler => (vec![2], vec![1]),
Distro::Peppermint => (vec![2], vec![1]),
Distro::Pop__OS => (vec![2], vec![1]),
Distro::Ubuntu_Cinnamon => (vec![2], vec![1]),
Distro::Ubuntu_Kylin => (vec![2], vec![1]),
Distro::Ubuntu_MATE => (vec![2], vec![1]),
Distro::Ubuntu_old => (vec![2], vec![1]),
Distro::Ubuntu_Studio => (vec![2], vec![1]),
Distro::Ubuntu_Sway => (vec![2], vec![1]),
Distro::Ultramarine_Linux => (vec![2], vec![1]),
Distro::Univention => (vec![2], vec![1]),
Distro::Vanilla => (vec![2], vec![1]),
Distro::Xubuntu => (vec![2], vec![1]),
_ => (Vec::new(), Vec::new()),
/// Gets the color indices that should be considered as foreground, for a
/// particular distro's ascii art.
fn ascii_foreground(distro: &Distro) -> Vec<NeofetchAsciiIndexedColor> {
let fg: Vec<u8> = match distro {
Distro::Anarchy => vec![2],
Distro::Antergos => vec![1],
Distro::ArchStrike => vec![2],
Distro::Astra_Linux => vec![2],
Distro::Chapeau => vec![2],
Distro::Fedora => vec![2],
Distro::Fedora_Silverblue => vec![2],
Distro::GalliumOS => vec![2],
Distro::KrassOS => vec![2],
Distro::Kubuntu => vec![2],
Distro::Lubuntu => vec![2],
Distro::openEuler => vec![2],
Distro::Peppermint => vec![2],
Distro::Pop__OS => vec![2],
Distro::Ubuntu_Cinnamon => vec![2],
Distro::Ubuntu_Kylin => vec![2],
Distro::Ubuntu_MATE => vec![2],
Distro::Ubuntu_old => vec![2],
Distro::Ubuntu_Studio => vec![2],
Distro::Ubuntu_Sway => vec![2],
Distro::Ultramarine_Linux => vec![2],
Distro::Univention => vec![2],
Distro::Vanilla => vec![2],
Distro::Xubuntu => vec![2],
_ => Vec::new(),
};
(
fg.into_iter()
.map(|fore| {
fore.try_into()
.expect("`fore` should be a valid neofetch color index")
})
.collect(),
bg.into_iter()
.map(|back| {
back.try_into()
.expect("`back` should be a valid neofetch color index")
})
.collect(),
)
fg.into_iter()
.map(|fore| {
fore.try_into()
.expect("`fore` should be a valid neofetch color index")
})
.collect()
}

View file

@ -1,5 +1,5 @@
use std::io::{self, Write as _};
use std::num::{NonZeroU16, NonZeroU8, NonZeroUsize, Wrapping};
use std::num::{NonZeroU16, NonZeroUsize, Wrapping};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
@ -48,54 +48,47 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
const TEXT_BORDER_WIDTH: u16 = 2;
const NOTICE_BORDER_WIDTH: u16 = 1;
const VERTICAL_MARGIN: u16 = 1;
let notice_w: NonZeroUsize = NOTICE
.len()
.try_into()
.expect("`NOTICE` should not be empty");
let notice_w: NonZeroU8 = notice_w
let notice_w = NOTICE.len();
let notice_w: u8 = notice_w
.try_into()
.expect("`NOTICE` width should fit in `u8`");
let notice_h: NonZeroUsize = NOTICE
.lines()
.count()
.try_into()
.expect("`NOTICE` should not be empty");
let notice_h: NonZeroU8 = notice_h
let notice_h = NOTICE.lines().count();
let notice_h: u8 = notice_h
.try_into()
.expect("`NOTICE` height should fit in `u8`");
let term_w_min = cmp::max(
NonZeroU16::from(text_width)
u16::from(text_width)
.checked_add(TEXT_BORDER_WIDTH.checked_mul(2).unwrap())
.unwrap(),
NonZeroU16::from(notice_w)
u16::from(notice_w)
.checked_add(NOTICE_BORDER_WIDTH.checked_mul(2).unwrap())
.unwrap(),
);
let term_h_min = NonZeroU16::from(text_height)
.checked_add(notice_h.get().into())
let term_h_min = u16::from(text_height)
.checked_add(notice_h.into())
.unwrap()
.checked_add(VERTICAL_MARGIN.checked_mul(2).unwrap())
.unwrap();
if w >= term_w_min && h >= term_h_min {
if w.get() >= term_w_min && h.get() >= term_h_min {
(text, text_width, text_height)
} else {
let text = &TEXT_ASCII_SMALL[1..TEXT_ASCII_SMALL.len().checked_sub(1).unwrap()];
let (text_width, text_height) =
ascii_size(text).expect("text ascii should have valid width and height");
let term_w_min = cmp::max(
NonZeroU16::from(text_width)
u16::from(text_width)
.checked_add(TEXT_BORDER_WIDTH.checked_mul(2).unwrap())
.unwrap(),
NonZeroU16::from(notice_w)
u16::from(notice_w)
.checked_add(NOTICE_BORDER_WIDTH.checked_mul(2).unwrap())
.unwrap(),
);
let term_h_min = NonZeroU16::from(text_height)
.checked_add(notice_h.get().into())
let term_h_min = u16::from(text_height)
.checked_add(notice_h.into())
.unwrap()
.checked_add(VERTICAL_MARGIN.checked_mul(2).unwrap())
.unwrap();
if w < term_w_min || h < term_h_min {
if w.get() < term_w_min || h.get() < term_h_min {
return Err(anyhow!(
"terminal size should be at least ({term_w_min} * {term_h_min})"
));
@ -115,19 +108,15 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
let text_start_y = h
.get()
.div_euclid(2)
.checked_sub(u16::from(text_height.get() / 2))
.unwrap();
let text_end_y = text_start_y
.checked_add(NonZeroU16::from(text_height).get())
.checked_sub((text_height / 2).into())
.unwrap();
let text_end_y = text_start_y.checked_add(text_height.into()).unwrap();
let text_start_x = w
.get()
.div_euclid(2)
.checked_sub(u16::from(text_width.get() / 2))
.unwrap();
let text_end_x = text_start_x
.checked_add(NonZeroU16::from(text_width).get())
.checked_sub((text_width / 2).into())
.unwrap();
let text_end_x = text_start_x.checked_add(text_width.into()).unwrap();
let notice_start_x = w
.get()