Implement recoloring of ascii art that don't use fore-back
This commit is contained in:
parent
46b618252f
commit
c100ca52a6
5 changed files with 141 additions and 27 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -226,6 +226,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -711,6 +712,12 @@ dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -34,3 +34,4 @@ thiserror = { version = "1.0.61", default-features = false }
|
||||||
tracing = { version = "0.1.40", default-features = false }
|
tracing = { version = "0.1.40", default-features = false }
|
||||||
tracing-subscriber = { version = "0.3.18", default-features = false }
|
tracing-subscriber = { version = "0.3.18", default-features = false }
|
||||||
unicode-normalization = { version = "0.1.23", default-features = false }
|
unicode-normalization = { version = "0.1.23", default-features = false }
|
||||||
|
unicode-segmentation = { version = "1.11.0", default-features = false }
|
||||||
|
|
|
@ -29,6 +29,7 @@ tempfile = { workspace = true, features = [] }
|
||||||
thiserror = { workspace = true, features = [] }
|
thiserror = { workspace = true, features = [] }
|
||||||
tracing = { workspace = true, features = ["attributes", "std"] }
|
tracing = { workspace = true, features = ["attributes", "std"] }
|
||||||
tracing-subscriber = { workspace = true, features = ["ansi", "fmt", "smallvec", "std", "tracing-log"] }
|
tracing-subscriber = { workspace = true, features = ["ansi", "fmt", "smallvec", "std", "tracing-log"] }
|
||||||
|
unicode-segmentation = { workspace = true, features = [] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
indexmap = { workspace = true, features = ["std"] }
|
indexmap = { workspace = true, features = ["std"] }
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::os::unix::process::ExitStatusExt as _;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, ExitStatus};
|
use std::process::{Command, ExitStatus};
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use std::{env, fmt, iter};
|
use std::{env, fmt};
|
||||||
|
|
||||||
use aho_corasick::AhoCorasick;
|
use aho_corasick::AhoCorasick;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
@ -83,11 +83,14 @@ impl ColorAlignment {
|
||||||
// Add new colors
|
// Add new colors
|
||||||
let asc = match self {
|
let asc = match self {
|
||||||
Self::Horizontal { .. } => {
|
Self::Horizontal { .. } => {
|
||||||
let length = lines.len();
|
let ColorProfile { colors } = {
|
||||||
let length: u8 = length.try_into().expect("`length` should fit in `u8`");
|
let length = lines.len();
|
||||||
let ColorProfile { colors } = 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)
|
||||||
|
.context("failed to spread color profile to length")?
|
||||||
|
};
|
||||||
let mut asc = String::new();
|
let mut asc = String::new();
|
||||||
for (i, line) in lines.into_iter().enumerate() {
|
for (i, line) in lines.into_iter().enumerate() {
|
||||||
let line = line.replace(
|
let line = line.replace(
|
||||||
|
@ -119,18 +122,68 @@ impl ColorAlignment {
|
||||||
let ac = NEOFETCH_COLORS_AC
|
let ac = NEOFETCH_COLORS_AC
|
||||||
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
|
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
|
||||||
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
|
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
|
||||||
let replacements: [&str; N] = iter::repeat("")
|
const REPLACEMENTS: [&str; N] = [""; N];
|
||||||
.take(N)
|
ac.replace_all(&asc, &REPLACEMENTS)
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
ac.replace_all(&asc, &replacements)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
asc
|
asc
|
||||||
},
|
},
|
||||||
Self::Horizontal { fore_back: None } | Self::Vertical { fore_back: None } => {
|
Self::Horizontal { fore_back: None } | Self::Vertical { fore_back: None } => {
|
||||||
todo!()
|
// Remove existing colors
|
||||||
|
let asc = {
|
||||||
|
let ac = NEOFETCH_COLORS_AC
|
||||||
|
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
|
||||||
|
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
|
||||||
|
const REPLACEMENTS: [&str; N] = [""; N];
|
||||||
|
ac.replace_all(&asc, &REPLACEMENTS)
|
||||||
|
};
|
||||||
|
|
||||||
|
let lines: Vec<_> = asc.split('\n').collect();
|
||||||
|
|
||||||
|
// Add new colors
|
||||||
|
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")?
|
||||||
|
};
|
||||||
|
let mut asc = String::new();
|
||||||
|
for (i, line) in lines.into_iter().enumerate() {
|
||||||
|
asc.push_str(
|
||||||
|
&colors[i]
|
||||||
|
.to_ansi_string(color_mode, ForegroundBackground::Foreground),
|
||||||
|
);
|
||||||
|
asc.push_str(line);
|
||||||
|
asc.push_str(&reset);
|
||||||
|
asc.push('\n');
|
||||||
|
}
|
||||||
|
asc
|
||||||
|
},
|
||||||
|
Self::Vertical { .. } => {
|
||||||
|
let mut asc = String::new();
|
||||||
|
for line in lines {
|
||||||
|
let line = color_profile
|
||||||
|
.color_text(
|
||||||
|
line,
|
||||||
|
color_mode,
|
||||||
|
ForegroundBackground::Foreground,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.context("failed to color text using color profile")?;
|
||||||
|
asc.push_str(&line);
|
||||||
|
asc.push_str(&reset);
|
||||||
|
asc.push('\n');
|
||||||
|
}
|
||||||
|
asc
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
unreachable!();
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Self::Custom { colors } => {
|
Self::Custom { colors } => {
|
||||||
todo!()
|
todo!()
|
||||||
|
@ -193,11 +246,22 @@ where
|
||||||
};
|
};
|
||||||
debug!(%distro, "distro name");
|
debug!(%distro, "distro name");
|
||||||
|
|
||||||
|
// Try new codegen-based detection method
|
||||||
if let Some(distro) = Distro::detect(&distro) {
|
if let Some(distro) = Distro::detect(&distro) {
|
||||||
return Ok(normalize_ascii(distro.ascii_art()));
|
return Ok(normalize_ascii(distro.ascii_art()));
|
||||||
}
|
}
|
||||||
|
|
||||||
todo!()
|
debug!(%distro, "could not find a match for distro; falling back to neofetch");
|
||||||
|
|
||||||
|
// Old detection method that calls neofetch
|
||||||
|
let asc = run_neofetch_command_piped(&["print_ascii", "--ascii_distro", distro.as_ref()])
|
||||||
|
.context("failed to get ascii art from neofetch")?;
|
||||||
|
|
||||||
|
// Unescape backslashes here because backslashes are escaped in neofetch for
|
||||||
|
// printf
|
||||||
|
let asc = asc.replace(r"\\", r"\");
|
||||||
|
|
||||||
|
Ok(normalize_ascii(asc))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug")]
|
#[tracing::instrument(level = "debug")]
|
||||||
|
@ -231,12 +295,8 @@ where
|
||||||
let ac =
|
let ac =
|
||||||
NEOFETCH_COLORS_AC.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
|
NEOFETCH_COLORS_AC.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
|
||||||
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
|
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
|
||||||
let replacements: [&str; N] = iter::repeat("")
|
const REPLACEMENTS: [&str; N] = [""; N];
|
||||||
.take(N)
|
ac.replace_all(asc, &REPLACEMENTS)
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
ac.replace_all(asc, &replacements)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(width) = asc.split('\n').map(|line| line.len()).max() else {
|
let Some(width) = asc.split('\n').map(|line| line.len()).max() else {
|
||||||
|
|
|
@ -7,9 +7,10 @@ use palette::num::ClampAssign;
|
||||||
use palette::{Hsl, IntoColorMut, LinSrgb, Srgb};
|
use palette::{Hsl, IntoColorMut, LinSrgb, Srgb};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{EnumString, VariantNames};
|
use strum::{EnumString, VariantNames};
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::color_util::Lightness;
|
use crate::color_util::{ForegroundBackground, Lightness, ToAnsiString};
|
||||||
use crate::types::LightDark;
|
use crate::types::{AnsiMode, LightDark};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Hash, Debug, Deserialize, EnumString, Serialize, VariantNames)]
|
#[derive(Copy, Clone, Hash, Debug, Deserialize, EnumString, Serialize, VariantNames)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
@ -290,10 +291,10 @@ impl Preset {
|
||||||
},
|
},
|
||||||
|
|
||||||
// sourced from https://www.flagcolorcodes.com/intergender
|
// sourced from https://www.flagcolorcodes.com/intergender
|
||||||
Self::Intergender => ColorProfile::from_hex_colors(
|
Self::Intergender => {
|
||||||
// todo: use weighted spacing
|
ColorProfile::from_hex_colors(vec!["#900DC2", "#FFE54F", "#900DC2"])
|
||||||
vec!["#900DC2", "#900DC2", "#FFE54F", "#900DC2", "#900DC2"],
|
.and_then(|c| c.with_weights(vec![2, 1, 2]))
|
||||||
),
|
},
|
||||||
|
|
||||||
Self::Lesbian => ColorProfile::from_hex_colors(vec![
|
Self::Lesbian => ColorProfile::from_hex_colors(vec![
|
||||||
"#D62800", "#FF9B56", "#FFFFFF", "#D462A6", "#A40062",
|
"#D62800", "#FF9B56", "#FFFFFF", "#D462A6", "#A40062",
|
||||||
|
@ -440,7 +441,7 @@ impl ColorProfile {
|
||||||
// How many copies of each color should be displayed at least?
|
// How many copies of each color should be displayed at least?
|
||||||
let repeats = (length as f32 / orig_len as f32).floor() as usize;
|
let repeats = (length as f32 / orig_len as f32).floor() as usize;
|
||||||
let repeats: u8 = repeats.try_into().expect("`repeats` should fit in `u8`");
|
let repeats: u8 = repeats.try_into().expect("`repeats` should fit in `u8`");
|
||||||
let mut weights: Vec<u8> = iter::repeat(repeats).take(orig_len as usize).collect();
|
let mut weights = vec![repeats; orig_len as usize];
|
||||||
|
|
||||||
// How many extra spaces left?
|
// How many extra spaces left?
|
||||||
let mut extras = length % orig_len;
|
let mut extras = length % orig_len;
|
||||||
|
@ -467,6 +468,50 @@ impl ColorProfile {
|
||||||
self.with_weights(weights)
|
self.with_weights(weights)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Colors a text.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `foreground_background` - Whether the color is shown on the foreground
|
||||||
|
/// text or the background block
|
||||||
|
/// * `space_only` - Whether to only color spaces
|
||||||
|
pub fn color_text<S>(
|
||||||
|
&self,
|
||||||
|
txt: S,
|
||||||
|
color_mode: AnsiMode,
|
||||||
|
foreground_background: ForegroundBackground,
|
||||||
|
space_only: bool,
|
||||||
|
) -> Result<String>
|
||||||
|
where
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
let txt = txt.as_ref();
|
||||||
|
|
||||||
|
let ColorProfile { colors } = {
|
||||||
|
let length = txt.len();
|
||||||
|
let length: u8 = length.try_into().expect("`length` should fit in `u8`");
|
||||||
|
self.with_length(length)
|
||||||
|
.context("failed to spread color profile to length")?
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buf = String::new();
|
||||||
|
let txt: Vec<&str> = txt.graphemes(true).collect();
|
||||||
|
for (i, &gr) in txt.iter().enumerate() {
|
||||||
|
if space_only && gr != " " {
|
||||||
|
if i > 0 && txt[i - 1] == " " {
|
||||||
|
buf.push_str("\x1b[39;49m");
|
||||||
|
}
|
||||||
|
buf.push_str(gr);
|
||||||
|
} else {
|
||||||
|
buf.push_str(&colors[i].to_ansi_string(color_mode, foreground_background));
|
||||||
|
buf.push_str(gr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str("\x1b[39;49m");
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new color profile, with the colors lightened by a multiplier.
|
/// Creates a new color profile, with the colors lightened by a multiplier.
|
||||||
pub fn lighten(&self, multiplier: f32) -> Self {
|
pub fn lighten(&self, multiplier: f32) -> Self {
|
||||||
let mut rgb_f32_colors: Vec<LinSrgb> =
|
let mut rgb_f32_colors: Vec<LinSrgb> =
|
||||||
|
|
Loading…
Reference in a new issue