#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct PantoneColor {
pub code: String,
pub name: String,
pub srgb: [u8; 3],
}
impl PantoneColor {
pub fn new(code: impl Into<String>, name: impl Into<String>, srgb: [u8; 3]) -> Self {
Self {
code: code.into(),
name: name.into(),
srgb,
}
}
pub fn hex_string(&self) -> String {
format!(
"#{:02X}{:02X}{:02X}",
self.srgb[0], self.srgb[1], self.srgb[2]
)
}
pub fn srgb_f32(&self) -> [f32; 3] {
[
self.srgb[0] as f32 / 255.0,
self.srgb[1] as f32 / 255.0,
self.srgb[2] as f32 / 255.0,
]
}
}
#[derive(Debug, Default, Clone)]
pub struct PantonePalette {
pub colors: Vec<PantoneColor>,
}
impl PantonePalette {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, color: PantoneColor) {
self.colors.push(color);
}
pub fn find_by_code(&self, code: &str) -> Option<&PantoneColor> {
self.colors.iter().find(|c| c.code == code)
}
pub fn count(&self) -> usize {
self.colors.len()
}
}
pub fn export_pantone_csv(palette: &PantonePalette) -> String {
let mut out = String::from("code,name,hex\n");
for c in &palette.colors {
out.push_str(&format!("{},{},{}\n", c.code, c.name, c.hex_string()));
}
out
}
pub fn closest_pantone(palette: &PantonePalette, target: [u8; 3]) -> Option<&PantoneColor> {
palette.colors.iter().min_by_key(|c| {
let dr = (c.srgb[0] as i32 - target[0] as i32).pow(2);
let dg = (c.srgb[1] as i32 - target[1] as i32).pow(2);
let db = (c.srgb[2] as i32 - target[2] as i32).pow(2);
dr + dg + db
})
}
pub fn validate_pantone_palette(palette: &PantonePalette) -> bool {
palette
.colors
.iter()
.all(|c| !c.code.is_empty() && !c.name.is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
fn red_pantone() -> PantoneColor {
PantoneColor::new("485 C", "Red 485", [218, 41, 28])
}
#[test]
fn test_hex_string() {
assert_eq!(red_pantone().hex_string(), "#DA291C");
}
#[test]
fn test_srgb_f32_range() {
let vals = red_pantone().srgb_f32();
assert!(vals.iter().all(|&v| (0.0..=1.0).contains(&v)));
}
#[test]
fn test_add_color() {
let mut palette = PantonePalette::new();
palette.add(red_pantone());
assert_eq!(palette.count(), 1);
}
#[test]
fn test_find_by_code_found() {
let mut palette = PantonePalette::new();
palette.add(red_pantone());
assert!(palette.find_by_code("485 C").is_some());
}
#[test]
fn test_find_by_code_not_found() {
let palette = PantonePalette::new();
assert!(palette.find_by_code("999 C").is_none());
}
#[test]
fn test_export_csv_header() {
let palette = PantonePalette::new();
assert!(export_pantone_csv(&palette).starts_with("code"));
}
#[test]
fn test_closest_pantone_empty() {
let palette = PantonePalette::new();
assert!(closest_pantone(&palette, [255, 0, 0]).is_none());
}
#[test]
fn test_closest_pantone_single() {
let mut palette = PantonePalette::new();
palette.add(red_pantone());
assert!(closest_pantone(&palette, [0, 0, 0]).is_some());
}
#[test]
fn test_validate_valid() {
let mut palette = PantonePalette::new();
palette.add(red_pantone());
assert!(validate_pantone_palette(&palette));
}
#[test]
fn test_validate_empty_code_fails() {
let mut palette = PantonePalette::new();
palette.add(PantoneColor::new("", "red", [255, 0, 0]));
assert!(!validate_pantone_palette(&palette));
}
}