Merge pull request #15 from teohhanhui/riir
Implement recolored ascii output
This commit is contained in:
commit
46b618252f
8 changed files with 694 additions and 118 deletions
155
Cargo.lock
generated
155
Cargo.lock
generated
|
@ -26,6 +26,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_colours"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a1558bd2075d341b9ca698ec8eb6fcc55a746b1fc4255585aad5b141d918a80"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.86"
|
||||
|
@ -114,26 +120,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "1.0.0-beta.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d"
|
||||
dependencies = [
|
||||
"derive_more-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more-impl"
|
||||
version = "1.0.0-beta.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "5.0.1"
|
||||
|
@ -152,7 +138,16 @@ dependencies = [
|
|||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enable-ansi-support"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa4ff3ae2a9aa54bf7ee0983e59303224de742818c1822d89f07da9856d9bc60"
|
||||
dependencies = [
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -161,12 +156,28 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast-srgb8"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
|
@ -194,12 +205,14 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|||
name = "hyfetch"
|
||||
version = "1.4.11"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"ansi_colours",
|
||||
"anyhow",
|
||||
"bpaf",
|
||||
"chrono",
|
||||
"deranged",
|
||||
"derive_more",
|
||||
"directories",
|
||||
"enable-ansi-support",
|
||||
"indexmap",
|
||||
"palette",
|
||||
"regex",
|
||||
|
@ -208,6 +221,7 @@ dependencies = [
|
|||
"serde_path_to_error",
|
||||
"shell-words",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
@ -291,6 +305,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
|
@ -433,6 +453,19 @@ version = "0.8.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
|
@ -549,6 +582,18 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.61"
|
||||
|
@ -763,6 +808,21 @@ dependencies = [
|
|||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
|
@ -772,6 +832,15 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
|
@ -803,6 +872,12 @@ dependencies = [
|
|||
"windows_x86_64_msvc 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
|
@ -815,6 +890,12 @@ version = "0.52.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
|
@ -827,6 +908,12 @@ version = "0.52.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
|
@ -845,6 +932,12 @@ version = "0.52.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
|
@ -857,6 +950,12 @@ version = "0.52.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
|
@ -869,6 +968,12 @@ version = "0.52.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
|
@ -881,6 +986,12 @@ version = "0.52.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
|
|
|
@ -12,23 +12,24 @@ repository = "https://github.com/hykilpikonna/hyfetch"
|
|||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
aho-corasick = { version = "1.1.3", default-features = false }
|
||||
ansi_colours = { version = "1.2.2", default-features = false }
|
||||
# anstream = { version = "0.6.14", default-features = false }
|
||||
anyhow = { version = "1.0.86", default-features = false }
|
||||
bpaf = { version = "0.9.12", default-features = false }
|
||||
bytemuck = { version = "1.16.1", default-features = false }
|
||||
chrono = { version = "0.4.38", default-features = false }
|
||||
deranged = { version = "0.3.11", default-features = false }
|
||||
derive_more = { version = "1.0.0-beta.6", default-features = false }
|
||||
directories = { version = "5.0.1", default-features = false }
|
||||
enable-ansi-support = { version = "0.2.1", default-features = false }
|
||||
indexmap = { version = "2.2.6", default-features = false }
|
||||
palette = { version = "0.7.6", default-features = false }
|
||||
regex = { version = "1.10.5", default-features = false }
|
||||
rgb = { version = "0.8.37", default-features = false }
|
||||
serde = { version = "1.0.203", default-features = false }
|
||||
serde_json = { version = "1.0.118", default-features = false }
|
||||
serde_path_to_error = { version = "0.1.16", default-features = false }
|
||||
shell-words = { version = "1.1.0", default-features = false }
|
||||
strum = { version = "0.26.3", default-features = false }
|
||||
tempfile = { version = "3.10.1", default-features = false }
|
||||
thiserror = { version = "1.0.61", default-features = false }
|
||||
tracing = { version = "0.1.40", default-features = false }
|
||||
tracing-subscriber = { version = "0.3.18", default-features = false }
|
||||
|
|
|
@ -10,23 +10,22 @@ license = { workspace = true }
|
|||
default-run = "hyfetch"
|
||||
|
||||
[dependencies]
|
||||
# ansi_colours = { workspace = true, features = ["rgb"] }
|
||||
aho-corasick = { workspace = true, features = ["perf-literal", "std"] }
|
||||
ansi_colours = { workspace = true, features = [] }
|
||||
# anstream = { workspace = true, features = ["auto"] }
|
||||
anyhow = { workspace = true, features = ["std"] }
|
||||
bpaf = { workspace = true, features = [] }
|
||||
# bytemuck = { workspace = true, features = [] }
|
||||
chrono = { workspace = true, features = ["clock", "std"] }
|
||||
deranged = { workspace = true, features = ["serde", "std"] }
|
||||
derive_more = { workspace = true, features = ["from", "from_str", "into", "std"] }
|
||||
directories = { workspace = true, features = [] }
|
||||
indexmap = { workspace = true, features = ["serde", "std"] }
|
||||
palette = { workspace = true, features = ["std"] }
|
||||
regex = { workspace = true, features = ["perf", "std", "unicode"] }
|
||||
# rgb = { workspace = true, features = [] }
|
||||
serde = { workspace = true, features = ["derive", "std"] }
|
||||
serde_json = { workspace = true, features = ["std"] }
|
||||
serde_path_to_error = { workspace = true, features = [] }
|
||||
shell-words = { workspace = true, features = ["std"] }
|
||||
strum = { workspace = true, features = ["derive", "std"] }
|
||||
tempfile = { workspace = true, features = [] }
|
||||
thiserror = { workspace = true, features = [] }
|
||||
tracing = { workspace = true, features = ["attributes", "std"] }
|
||||
tracing-subscriber = { workspace = true, features = ["ansi", "fmt", "smallvec", "std", "tracing-log"] }
|
||||
|
@ -36,6 +35,9 @@ indexmap = { workspace = true, features = ["std"] }
|
|||
regex = { workspace = true, features = ["perf", "std", "unicode"] }
|
||||
unicode-normalization = { workspace = true, features = ["std"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
enable-ansi-support = { workspace = true, features = [] }
|
||||
|
||||
[features]
|
||||
default = ["autocomplete", "color"]
|
||||
autocomplete = ["bpaf/autocomplete"]
|
||||
|
|
|
@ -13,6 +13,9 @@ use hyfetch::utils::get_cache_path;
|
|||
use tracing::debug;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
#[cfg(windows)]
|
||||
enable_ansi_support::enable_ansi_support();
|
||||
|
||||
let options = options().run();
|
||||
|
||||
init_tracing_subsriber(options.debug).context("failed to init tracing subscriber")?;
|
||||
|
@ -97,7 +100,8 @@ fn main() -> Result<()> {
|
|||
};
|
||||
let asc = config
|
||||
.color_align
|
||||
.recolor_ascii(asc, color_profile, color_mode, config.light_dark);
|
||||
.recolor_ascii(asc, color_profile, color_mode, config.light_dark)
|
||||
.context("failed to recolor ascii")?;
|
||||
neofetch_util::run(asc, backend, args).context("failed to run")?;
|
||||
|
||||
if options.ask_exit {
|
||||
|
|
|
@ -1,16 +1,63 @@
|
|||
use std::num::ParseFloatError;
|
||||
use std::num::{ParseFloatError, ParseIntError};
|
||||
use std::str::FromStr;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use anyhow::Result;
|
||||
use aho_corasick::AhoCorasick;
|
||||
use ansi_colours::AsRGB;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use deranged::RangedU8;
|
||||
use derive_more::{From, FromStr, Into};
|
||||
use palette::Srgb;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::types::AnsiMode;
|
||||
|
||||
const MINECRAFT_COLORS: [(&str, &str); 30] = [
|
||||
// Minecraft formatting codes
|
||||
// ==========================
|
||||
("&0", "\x1b[38;5;0m"),
|
||||
("&1", "\x1b[38;5;4m"),
|
||||
("&2", "\x1b[38;5;2m"),
|
||||
("&3", "\x1b[38;5;6m"),
|
||||
("&4", "\x1b[38;5;1m"),
|
||||
("&5", "\x1b[38;5;5m"),
|
||||
("&6", "\x1b[38;5;3m"),
|
||||
("&7", "\x1b[38;5;7m"),
|
||||
("&8", "\x1b[38;5;8m"),
|
||||
("&9", "\x1b[38;5;12m"),
|
||||
("&a", "\x1b[38;5;10m"),
|
||||
("&b", "\x1b[38;5;14m"),
|
||||
("&c", "\x1b[38;5;9m"),
|
||||
("&d", "\x1b[38;5;13m"),
|
||||
("&e", "\x1b[38;5;11m"),
|
||||
("&f", "\x1b[38;5;15m"),
|
||||
("&l", "\x1b[1m"), // Enable bold text
|
||||
("&o", "\x1b[3m"), // Enable italic text
|
||||
("&n", "\x1b[4m"), // Enable underlined text
|
||||
("&k", "\x1b[8m"), // Enable hidden text
|
||||
("&m", "\x1b[9m"), // Enable strikethrough text
|
||||
("&r", "\x1b[0m"), // Reset everything
|
||||
// Extended codes (not officially in Minecraft)
|
||||
// ============================================
|
||||
("&-", "\n"), // Line break
|
||||
("&~", "\x1b[39m"), // Reset text color
|
||||
("&*", "\x1b[49m"), // Reset background color
|
||||
("&L", "\x1b[22m"), // Disable bold text
|
||||
("&O", "\x1b[23m"), // Disable italic text
|
||||
("&N", "\x1b[24m"), // Disable underlined text
|
||||
("&K", "\x1b[28m"), // Disable hidden text
|
||||
("&M", "\x1b[29m"), // Disable strikethrough text
|
||||
];
|
||||
const RGB_COLOR_PATTERNS: [&str; 2] = ["&gf(", "&gb("];
|
||||
|
||||
static MINECRAFT_COLORS_AC: OnceLock<(AhoCorasick, Box<[&str; 30]>)> = OnceLock::new();
|
||||
static RGB_COLORS_AC: OnceLock<AhoCorasick> = OnceLock::new();
|
||||
|
||||
/// Represents the lightness component in HSL.
|
||||
///
|
||||
/// The range of valid values is
|
||||
/// `(`[`Lightness::MIN`]`..=`[`Lightness::MAX`]`)`.
|
||||
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Deserialize, Into, Serialize)]
|
||||
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
|
||||
pub struct Lightness(f32);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -37,21 +84,7 @@ pub enum ParseLightnessError {
|
|||
/// The range of valid values as supported in neofetch is
|
||||
/// `(`[`NeofetchAsciiIndexedColor::MIN`]`..
|
||||
/// =`[`NeofetchAsciiIndexedColor::MAX`]`)`.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
Debug,
|
||||
Deserialize,
|
||||
From,
|
||||
FromStr,
|
||||
Into,
|
||||
Serialize,
|
||||
)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
|
||||
pub struct NeofetchAsciiIndexedColor(
|
||||
RangedU8<{ NeofetchAsciiIndexedColor::MIN }, { NeofetchAsciiIndexedColor::MAX }>,
|
||||
);
|
||||
|
@ -61,22 +94,21 @@ pub struct NeofetchAsciiIndexedColor(
|
|||
///
|
||||
/// The range of valid values depends on the number of unique colors in a
|
||||
/// certain preset.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
Debug,
|
||||
Deserialize,
|
||||
From,
|
||||
FromStr,
|
||||
Into,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct PresetIndexedColor(usize);
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
|
||||
pub struct PresetIndexedColor(u8);
|
||||
|
||||
/// Whether the color is for foreground text or background color.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
pub enum ForegroundBackground {
|
||||
Foreground,
|
||||
Background,
|
||||
}
|
||||
|
||||
pub trait ToAnsiString {
|
||||
/// Converts RGB to ANSI escape code.
|
||||
fn to_ansi_string(&self, mode: AnsiMode, foreground_background: ForegroundBackground)
|
||||
-> String;
|
||||
}
|
||||
|
||||
impl Lightness {
|
||||
const MAX: f32 = 1.0f32;
|
||||
|
@ -107,7 +139,175 @@ impl FromStr for Lightness {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Lightness> for f32 {
|
||||
fn from(value: Lightness) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl NeofetchAsciiIndexedColor {
|
||||
const MAX: u8 = 6;
|
||||
const MIN: u8 = 1;
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for NeofetchAsciiIndexedColor {
|
||||
type Error = deranged::TryFromIntError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
Ok(Self(value.try_into()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for NeofetchAsciiIndexedColor {
|
||||
type Err = deranged::ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(s.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NeofetchAsciiIndexedColor> for u8 {
|
||||
fn from(value: NeofetchAsciiIndexedColor) -> Self {
|
||||
value.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for PresetIndexedColor {
|
||||
fn from(value: u8) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PresetIndexedColor {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(s.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PresetIndexedColor> for u8 {
|
||||
fn from(value: PresetIndexedColor) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAnsiString for Srgb<u8> {
|
||||
fn to_ansi_string(
|
||||
&self,
|
||||
mode: AnsiMode,
|
||||
foreground_background: ForegroundBackground,
|
||||
) -> String {
|
||||
let c: u8 = match foreground_background {
|
||||
ForegroundBackground::Foreground => 38,
|
||||
ForegroundBackground::Background => 48,
|
||||
};
|
||||
match mode {
|
||||
AnsiMode::Rgb => {
|
||||
let [r, g, b]: [u8; 3] = (*self).into();
|
||||
format!("\x1b[{c};2;{r};{g};{b}m")
|
||||
},
|
||||
AnsiMode::Ansi256 => {
|
||||
let rgb: [u8; 3] = (*self).into();
|
||||
let indexed = rgb.to_ansi256();
|
||||
format!("\x1b[{c};5;{indexed}m")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces extended minecraft color codes in message.
|
||||
///
|
||||
/// Returns message with escape codes.
|
||||
pub fn color<S>(msg: S, mode: AnsiMode) -> Result<String>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let msg = msg.as_ref();
|
||||
|
||||
let msg = {
|
||||
let (ac, escape_codes) = MINECRAFT_COLORS_AC.get_or_init(|| {
|
||||
let (color_codes, escape_codes): (Vec<_>, Vec<_>) =
|
||||
MINECRAFT_COLORS.into_iter().unzip();
|
||||
let ac = AhoCorasick::new(color_codes).unwrap();
|
||||
(
|
||||
ac,
|
||||
escape_codes.try_into().expect(
|
||||
"`MINECRAFT_COLORS` should have the same number of elements as \
|
||||
`MINECRAFT_COLORS_AC.get_or_init(...).1`",
|
||||
),
|
||||
)
|
||||
});
|
||||
ac.replace_all(msg, &escape_codes[..])
|
||||
};
|
||||
|
||||
let ac = RGB_COLORS_AC.get_or_init(|| AhoCorasick::new(RGB_COLOR_PATTERNS).unwrap());
|
||||
let mut dst = String::new();
|
||||
let mut ret_err = None;
|
||||
ac.replace_all_with(&msg, &mut dst, |m, _, dst| {
|
||||
let start = m.end();
|
||||
let end = msg[start..]
|
||||
.find(')')
|
||||
.ok_or_else(|| anyhow!("missing closing brace for color code"));
|
||||
let end = match end {
|
||||
Ok(end) => end,
|
||||
Err(err) => {
|
||||
ret_err = Some(err);
|
||||
return false;
|
||||
},
|
||||
};
|
||||
let code = &msg[start..end];
|
||||
let foreground_background = if m.pattern().as_usize() == 0 {
|
||||
ForegroundBackground::Foreground
|
||||
} else {
|
||||
ForegroundBackground::Background
|
||||
};
|
||||
|
||||
let rgb: Srgb<u8> = if code.starts_with('#') {
|
||||
let rgb = code.parse().context("failed to parse hex color");
|
||||
match rgb {
|
||||
Ok(rgb) => rgb,
|
||||
Err(err) => {
|
||||
ret_err = Some(err);
|
||||
return false;
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let rgb: Result<[&str; 3], _> = code
|
||||
.split(&[',', ';', ' '])
|
||||
.filter(|x| x.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.map_err(|_| anyhow!("wrong number of rgb components"));
|
||||
let rgb = match rgb {
|
||||
Ok(rgb) => rgb,
|
||||
Err(err) => {
|
||||
ret_err = Some(err);
|
||||
return false;
|
||||
},
|
||||
};
|
||||
let rgb = rgb
|
||||
.into_iter()
|
||||
.map(u8::from_str)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.context("failed to parse rgb components");
|
||||
let rgb: [u8; 3] = match rgb {
|
||||
Ok(rgb) => rgb.try_into().unwrap(),
|
||||
Err(err) => {
|
||||
ret_err = Some(err);
|
||||
return false;
|
||||
},
|
||||
};
|
||||
rgb.into()
|
||||
};
|
||||
|
||||
dst.push_str(&rgb.to_ansi_string(mode, foreground_background));
|
||||
|
||||
true
|
||||
});
|
||||
if let Some(err) = ret_err {
|
||||
Err(err)?;
|
||||
}
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
|
|
|
@ -23,8 +23,12 @@ pub struct Config {
|
|||
impl Config {
|
||||
pub fn default_lightness(term: &LightDark) -> Lightness {
|
||||
match term {
|
||||
LightDark::Dark => Lightness::new(0.65).unwrap(),
|
||||
LightDark::Light => Lightness::new(0.4).unwrap(),
|
||||
LightDark::Dark => {
|
||||
Lightness::new(0.65).expect("default lightness should not be invalid")
|
||||
},
|
||||
LightDark::Light => {
|
||||
Lightness::new(0.4).expect("default lightness should not be invalid")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::Write;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::ExitStatusExt as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::process::{Command, ExitStatus};
|
||||
use std::sync::OnceLock;
|
||||
use std::{env, fmt};
|
||||
use std::{env, fmt, iter};
|
||||
|
||||
use aho_corasick::AhoCorasick;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use indexmap::IndexMap;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tempfile::NamedTempFile;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::color_util::{NeofetchAsciiIndexedColor, PresetIndexedColor};
|
||||
use crate::color_util::{
|
||||
color, ForegroundBackground, NeofetchAsciiIndexedColor, PresetIndexedColor, ToAnsiString,
|
||||
};
|
||||
use crate::distros::Distro;
|
||||
use crate::presets::ColorProfile;
|
||||
use crate::types::{AnsiMode, Backend, LightDark};
|
||||
|
||||
const NEOFETCH_COLOR_PATTERN: &str = r"\$\{c[0-6]\}";
|
||||
static NEOFETCH_COLOR_RE: OnceLock<Regex> = OnceLock::new();
|
||||
const NEOFETCH_COLOR_PATTERNS: [&str; 6] = ["${c1}", "${c2}", "${c3}", "${c4}", "${c5}", "${c6}"];
|
||||
static NEOFETCH_COLORS_AC: OnceLock<AhoCorasick> = OnceLock::new();
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "mode")]
|
||||
|
@ -39,14 +43,101 @@ pub enum ColorAlignment {
|
|||
|
||||
impl ColorAlignment {
|
||||
/// Uses the color alignment to recolor an ascii art.
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub fn recolor_ascii(
|
||||
&self,
|
||||
asc: String,
|
||||
color_profile: ColorProfile,
|
||||
color_mode: AnsiMode,
|
||||
term: LightDark,
|
||||
) -> String {
|
||||
) -> Result<String> {
|
||||
let asc = fill_starting(asc).context("failed to fill in starting neofetch color codes")?;
|
||||
|
||||
let reset = color("&~&*", color_mode).expect("color reset should not be invalid");
|
||||
|
||||
let asc = match self {
|
||||
Self::Horizontal {
|
||||
fore_back: Some((fore, back)),
|
||||
}
|
||||
| Self::Vertical {
|
||||
fore_back: Some((fore, back)),
|
||||
} => {
|
||||
let fore: u8 = (*fore).into();
|
||||
let back: u8 = (*back).into();
|
||||
|
||||
// Replace foreground colors
|
||||
let asc = asc.replace(
|
||||
&format!("${{c{fore}}}"),
|
||||
&color(
|
||||
match term {
|
||||
LightDark::Light => "&0",
|
||||
LightDark::Dark => "&f",
|
||||
},
|
||||
color_mode,
|
||||
)
|
||||
.expect("foreground color should not be invalid"),
|
||||
);
|
||||
|
||||
let lines: Vec<_> = asc.split('\n').collect();
|
||||
|
||||
// Add new colors
|
||||
let asc = match self {
|
||||
Self::Horizontal { .. } => {
|
||||
let length = lines.len();
|
||||
let length: u8 = length.try_into().expect("`length` should fit in `u8`");
|
||||
let ColorProfile { colors } = 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() {
|
||||
let line = line.replace(
|
||||
&format!("${{c{back}}}"),
|
||||
&colors[i].to_ansi_string(color_mode, {
|
||||
// note: this is "background" in the ascii art, but foreground
|
||||
// text in terminal
|
||||
ForegroundBackground::Foreground
|
||||
}),
|
||||
);
|
||||
asc.push_str(&line);
|
||||
asc.push_str(&reset);
|
||||
asc.push('\n');
|
||||
}
|
||||
asc
|
||||
},
|
||||
Self::Vertical { .. } => {
|
||||
unimplemented!(
|
||||
"vertical color alignment with fore and back colors not implemented"
|
||||
);
|
||||
},
|
||||
_ => {
|
||||
unreachable!();
|
||||
},
|
||||
};
|
||||
|
||||
// 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();
|
||||
let replacements: [&str; N] = iter::repeat("")
|
||||
.take(N)
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
ac.replace_all(&asc, &replacements)
|
||||
};
|
||||
|
||||
asc
|
||||
},
|
||||
Self::Horizontal { fore_back: None } | Self::Vertical { fore_back: None } => {
|
||||
todo!()
|
||||
},
|
||||
Self::Custom { colors } => {
|
||||
todo!()
|
||||
},
|
||||
};
|
||||
|
||||
Ok(asc)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,8 +200,24 @@ where
|
|||
todo!()
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub fn run(asc: String, backend: Backend, args: Option<&Vec<String>>) -> Result<()> {
|
||||
todo!()
|
||||
match backend {
|
||||
Backend::Neofetch => {
|
||||
run_neofetch(asc, args).context("failed to run neofetch")?;
|
||||
},
|
||||
Backend::Fastfetch => {
|
||||
todo!();
|
||||
},
|
||||
Backend::FastfetchOld => {
|
||||
todo!();
|
||||
},
|
||||
Backend::Qwqfetch => {
|
||||
todo!();
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets distro ascii width and height, ignoring color code.
|
||||
|
@ -120,18 +227,26 @@ where
|
|||
{
|
||||
let asc = asc.as_ref();
|
||||
|
||||
let Some(width) = NEOFETCH_COLOR_RE
|
||||
.get_or_init(|| Regex::new(NEOFETCH_COLOR_PATTERN).unwrap())
|
||||
.replace_all(asc, "")
|
||||
.split('\n')
|
||||
.map(|line| line.len())
|
||||
.max()
|
||||
else {
|
||||
let asc = {
|
||||
let ac =
|
||||
NEOFETCH_COLORS_AC.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
|
||||
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
|
||||
let replacements: [&str; N] = iter::repeat("")
|
||||
.take(N)
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
ac.replace_all(asc, &replacements)
|
||||
};
|
||||
|
||||
let Some(width) = asc.split('\n').map(|line| line.len()).max() else {
|
||||
unreachable!();
|
||||
};
|
||||
let width: u8 = width.try_into().expect("`width` should fit in `u8`");
|
||||
let height = asc.split('\n').count();
|
||||
let height: u8 = height.try_into().expect("`height` should fit in `u8`");
|
||||
|
||||
(width as u8, height as u8)
|
||||
(width, height)
|
||||
}
|
||||
|
||||
/// Makes sure every line are the same width.
|
||||
|
@ -146,13 +261,69 @@ where
|
|||
let mut buf = String::new();
|
||||
for line in asc.split('\n') {
|
||||
let (line_w, _) = ascii_size(line);
|
||||
buf.push_str(line);
|
||||
let pad = " ".repeat((w - line_w) as usize);
|
||||
buf.push_str(&format!("{line}{pad}\n"))
|
||||
buf.push_str(&pad);
|
||||
buf.push('\n');
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
/// Fills the missing starting placeholders.
|
||||
///
|
||||
/// e.g. `"${c1}...\n..."` -> `"${c1}...\n${c1}..."`
|
||||
fn fill_starting<S>(asc: S) -> Result<String>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let asc = asc.as_ref();
|
||||
|
||||
let ac = NEOFETCH_COLORS_AC.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
|
||||
|
||||
let mut new = String::new();
|
||||
let mut last = None;
|
||||
for line in asc.split('\n') {
|
||||
let mut matches = ac.find_iter(line).peekable();
|
||||
|
||||
match matches.peek() {
|
||||
Some(m) if m.start() == 0 => {
|
||||
// line starts with neofetch color code, do nothing
|
||||
},
|
||||
_ => {
|
||||
new.push_str(last.ok_or_else(|| {
|
||||
anyhow!("failed to find neofetch color code from a previous line")
|
||||
})?);
|
||||
},
|
||||
}
|
||||
new.push_str(line);
|
||||
new.push('\n');
|
||||
|
||||
// Get the last placeholder for the next line
|
||||
if let Some(m) = matches.last() {
|
||||
last = Some(&line[m.span()])
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
/// Runs neofetch command.
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn run_neofetch_command<S>(args: &[S]) -> Result<()>
|
||||
where
|
||||
S: AsRef<OsStr> + fmt::Debug,
|
||||
{
|
||||
let mut command = make_neofetch_command(args).context("failed to make neofetch command")?;
|
||||
|
||||
let status = command
|
||||
.status()
|
||||
.context("failed to execute neofetch command as child process")?;
|
||||
process_command_status(&status).context("neofetch command exited with error")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs neofetch command, returning the piped stdout output.
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn run_neofetch_command_piped<S>(args: &[S]) -> Result<String>
|
||||
|
@ -165,26 +336,7 @@ where
|
|||
.output()
|
||||
.context("failed to execute neofetch as child process")?;
|
||||
debug!(?output, "neofetch output");
|
||||
|
||||
if !output.status.success() {
|
||||
let err = if let Some(code) = output.status.code() {
|
||||
anyhow!("neofetch process exited with status code: {code}")
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
anyhow!(
|
||||
"neofetch process terminated by signal: {}",
|
||||
output
|
||||
.status
|
||||
.signal()
|
||||
.expect("either one of status code or signal should be set")
|
||||
)
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
unimplemented!("status code not expected to be `None` on non-Unix platforms")
|
||||
};
|
||||
Err(err)?;
|
||||
}
|
||||
process_command_status(&output.status).context("neofetch command exited with error")?;
|
||||
|
||||
let out = String::from_utf8(output.stdout)
|
||||
.context("failed to process neofetch output as it contains invalid UTF-8")?
|
||||
|
@ -210,8 +362,67 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn process_command_status(status: &ExitStatus) -> Result<()> {
|
||||
if status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let err = if let Some(code) = status.code() {
|
||||
anyhow!("child process exited with status code: {code}")
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
anyhow!(
|
||||
"child process terminated by signal: {}",
|
||||
status
|
||||
.signal()
|
||||
.expect("either one of status code or signal should be set")
|
||||
)
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
unimplemented!("status code not expected to be `None` on non-Unix platforms")
|
||||
};
|
||||
Err(err)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn get_distro_name() -> Result<String> {
|
||||
run_neofetch_command_piped(&["ascii_distro_name"])
|
||||
.context("failed to get distro name from neofetch")
|
||||
}
|
||||
|
||||
/// Runs neofetch with colors.
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn run_neofetch(asc: String, args: Option<&Vec<String>>) -> Result<()> {
|
||||
// Escape backslashes here because backslashes are escaped in neofetch for
|
||||
// printf
|
||||
let asc = asc.replace('\\', r"\\");
|
||||
|
||||
// Write temp file
|
||||
let mut temp_file =
|
||||
NamedTempFile::with_prefix("ascii.txt").context("failed to create temp file for ascii")?;
|
||||
temp_file
|
||||
.write_all(asc.as_bytes())
|
||||
.context("failed to write ascii to temp file")?;
|
||||
|
||||
// Call neofetch with the temp file
|
||||
let temp_file_path = temp_file.into_temp_path();
|
||||
let args = {
|
||||
let mut v = vec![
|
||||
"--ascii",
|
||||
"--source",
|
||||
temp_file_path
|
||||
.to_str()
|
||||
.expect("temp file path should not contain invalid UTF-8"),
|
||||
"--ascii-colors",
|
||||
];
|
||||
if let Some(args) = args {
|
||||
let args: Vec<_> = args.iter().map(|s| &**s).collect();
|
||||
v.extend(args);
|
||||
}
|
||||
v
|
||||
};
|
||||
run_neofetch_command(&args).context("failed to run neofetch command")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -146,8 +146,9 @@ impl Preset {
|
|||
// sourced from https://www.flagcolorcodes.com/autoromantic
|
||||
Self::Autoromantic => ColorProfile::from_hex_colors(
|
||||
// symbol interpreted
|
||||
vec!["#99D9EA", "#99D9EA", "#3DA542", "#7F7F7F", "#7F7F7F"],
|
||||
),
|
||||
vec!["#99D9EA", "#3DA542", "#7F7F7F"],
|
||||
)
|
||||
.and_then(|c| c.with_weights(vec![2, 1, 2])),
|
||||
|
||||
// sourced from https://www.flagcolorcodes.com/autosexual
|
||||
Self::Autosexual => ColorProfile::from_hex_colors(vec!["#99D9EA", "#7F7F7F"]),
|
||||
|
@ -192,15 +193,17 @@ impl Preset {
|
|||
|
||||
// used colorpicker to source form https://www.deviantart.com/pride-flags/art/Demifae-870194777
|
||||
Self::Demifae => ColorProfile::from_hex_colors(vec![
|
||||
"#7F7F7F", "#7F7F7F", "#C5C5C5", "#C5C5C5", "#97C3A4", "#C4DEAE", "#FFFFFF",
|
||||
"#FCA2C5", "#AB7EDF", "#C5C5C5", "#C5C5C5", "#7F7F7F", "#7F7F7F",
|
||||
]),
|
||||
"#7F7F7F", "#C5C5C5", "#97C3A4", "#C4DEAE", "#FFFFFF", "#FCA2C5", "#AB7EDF",
|
||||
"#C5C5C5", "#7F7F7F",
|
||||
])
|
||||
.and_then(|c| c.with_weights(vec![2, 2, 1, 1, 1, 1, 1, 2, 2])),
|
||||
|
||||
// sourced from https://www.flagcolorcodes.com/demifaun
|
||||
Self::Demifaun => ColorProfile::from_hex_colors(vec![
|
||||
"#7F7F7F", "#7F7F7F", "#C6C6C6", "#C6C6C6", "#FCC688", "#FFF19C", "#FFFFFF",
|
||||
"#8DE0D5", "#9682EC", "#C6C6C6", "#C6C6C6", "#7F7F7F", "#7F7F7F",
|
||||
]),
|
||||
"#7F7F7F", "#C6C6C6", "#FCC688", "#FFF19C", "#FFFFFF", "#8DE0D5", "#9682EC",
|
||||
"#C6C6C6", "#7F7F7F",
|
||||
])
|
||||
.and_then(|c| c.with_weights(vec![2, 2, 1, 1, 1, 1, 1, 2, 2])),
|
||||
|
||||
// yellow sourced from https://lgbtqia.fandom.com/f/p/4400000000000041031
|
||||
// other colors sourced from demiboy and demigirl flags
|
||||
|
@ -272,9 +275,9 @@ impl Preset {
|
|||
|
||||
// sourced from https://www.flagcolorcodes.com/greygender
|
||||
Self::Greygender => ColorProfile::from_hex_colors(vec![
|
||||
"#B3B3B3", "#B3B3B3", "#FFFFFF", "#062383", "#062383", "#FFFFFF", "#535353",
|
||||
"#535353",
|
||||
]),
|
||||
"#B3B3B3", "#FFFFFF", "#062383", "#FFFFFF", "#535353",
|
||||
])
|
||||
.and_then(|c| c.with_weights(vec![2, 1, 2, 1, 2])),
|
||||
|
||||
// sourced from https://www.flagcolorcodes.com/greysexual
|
||||
Self::Greysexual => ColorProfile::from_hex_colors(vec![
|
||||
|
@ -381,7 +384,7 @@ impl Preset {
|
|||
"#FF6692", "#FF9A98", "#FFB883", "#FBFFA8", "#85BCFF", "#9D85FF", "#A510FF",
|
||||
]),
|
||||
})
|
||||
.expect("presets should not be invalid")
|
||||
.expect("preset color profiles should not be invalid")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,6 +427,46 @@ impl ColorProfile {
|
|||
Ok(Self::new(weighted_colors))
|
||||
}
|
||||
|
||||
/// Creates a new color profile, with the colors spread to the specified
|
||||
/// length.
|
||||
pub fn with_length(&self, length: u8) -> Result<Self> {
|
||||
let orig_len = self.colors.len();
|
||||
let orig_len: u8 = orig_len.try_into().expect("`orig_len` should fit in `u8`");
|
||||
if length < orig_len {
|
||||
unimplemented!("compressing length of color profile not implemented");
|
||||
}
|
||||
let center_i = (orig_len as f32 / 2.0).floor() as usize;
|
||||
|
||||
// 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: u8 = repeats.try_into().expect("`repeats` should fit in `u8`");
|
||||
let mut weights: Vec<u8> = iter::repeat(repeats).take(orig_len as usize).collect();
|
||||
|
||||
// How many extra spaces left?
|
||||
let mut extras = length % orig_len;
|
||||
|
||||
// If there is an odd space left, extend the center by one space
|
||||
if extras % 2 == 1 {
|
||||
extras -= 1;
|
||||
weights[center_i] += 1;
|
||||
}
|
||||
|
||||
// Add weight to border until there's no space left (extras must be even at this
|
||||
// point)
|
||||
// TODO: this gives a horrible result when `extras` is still large relative to
|
||||
// `orig_len` - we should probably distribute even if slightly uneven
|
||||
let mut border_i = 0;
|
||||
while extras > 0 {
|
||||
extras -= 2;
|
||||
weights[border_i] += 1;
|
||||
let weights_len = weights.len();
|
||||
weights[weights_len - border_i - 1] += 1;
|
||||
border_i += 1;
|
||||
}
|
||||
|
||||
self.with_weights(weights)
|
||||
}
|
||||
|
||||
/// Creates a new color profile, with the colors lightened by a multiplier.
|
||||
pub fn lighten(&self, multiplier: f32) -> Self {
|
||||
let mut rgb_f32_colors: Vec<LinSrgb> =
|
||||
|
|
Loading…
Reference in a new issue