tinted_builder/scheme/
color.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4use crate::error::TintedBuilderError;
5
6#[derive(Debug, Clone, Deserialize, Serialize)]
7pub struct Color {
8    pub hex: (String, String, String),
9    pub rgb: (u8, u8, u8),
10    pub dec: (f32, f32, f32),
11}
12
13impl Color {
14    /// Creates a `Color` from a hex string like `"ff00ff"` or `"#ffcc00"`.
15    ///
16    /// # Errors
17    ///
18    /// Returns `Err(TintedBuilderError::HexInputFormat)` if `hex_color` is not a valid
19    /// 6-digit hexadecimal color (optionally prefixed with `#`).
20    pub fn new(hex_color: &str) -> Result<Self, TintedBuilderError> {
21        let hex_full = process_hex_input(hex_color).ok_or(TintedBuilderError::HexInputFormat)?;
22        let hex: (String, String, String) = (
23            hex_full[0..2].to_lowercase(),
24            hex_full[2..4].to_lowercase(),
25            hex_full[4..6].to_lowercase(),
26        );
27        let rgb = hex_to_rgb(&hex)?;
28        let dec: (f32, f32, f32) = (f32::from(rgb.0), f32::from(rgb.1), f32::from(rgb.2));
29
30        Ok(Self { hex, rgb, dec })
31    }
32
33    #[must_use]
34    pub fn to_hex(&self) -> String {
35        format!("{}{}{}", &self.hex.0, &self.hex.1, &self.hex.2)
36    }
37}
38
39impl fmt::Display for Color {
40    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41        write!(f, "#{}", &self.to_hex())
42    }
43}
44
45fn hex_to_rgb(hex: &(String, String, String)) -> Result<(u8, u8, u8), TintedBuilderError> {
46    let r = u8::from_str_radix(hex.0.as_str(), 16)?;
47    let g = u8::from_str_radix(hex.1.as_str(), 16)?;
48    let b = u8::from_str_radix(hex.2.as_str(), 16)?;
49
50    Ok((r, g, b))
51}
52
53fn process_hex_input(input: &str) -> Option<String> {
54    // Check and process the hash prefix
55    let hex_str = input.strip_prefix('#').unwrap_or(input);
56
57    match hex_str.len() {
58        // Convert 3-length hex to 6-length by duplicating each character
59        3 => {
60            if hex_str.chars().all(|c| c.is_ascii_hexdigit()) {
61                Some(
62                    hex_str
63                        .chars()
64                        .flat_map(|c| std::iter::repeat(c).take(2))
65                        .collect(),
66                )
67            } else {
68                None // Contains invalid characters
69            }
70        }
71        // Validate the 6-length hex value
72        6 => {
73            if hex_str.chars().all(|c| c.is_ascii_hexdigit()) {
74                Some(hex_str.to_string())
75            } else {
76                None // Contains invalid characters
77            }
78        }
79        // Invalid length
80        _ => None,
81    }
82}