use serde::Deserialize; use serde_yaml::Value; use crate::errors::IncompleteMappingError; use crate::image::{Image, Pixel}; use std::collections::{HashMap, HashSet}; #[derive(Deserialize, Debug)] pub struct Mapping(pub HashMap>); #[derive(Deserialize, Debug)] pub struct ColorEntry { pub color: Pixel, pub name: String, pub symbol: String, } impl ColorEntry { fn from_entry( entry: HashMap, ) -> Result<(Pixel, Option), Box> { let color = match entry.get("color") { Some(Value::Sequence(v)) => { let r = v[0].as_u64().unwrap().try_into().unwrap(); let g = v[1].as_u64().unwrap().try_into().unwrap(); let b = v[2].as_u64().unwrap().try_into().unwrap(); (r, g, b) } _ => panic!("Missing `color` in entry"), }; if let Some(_) = entry.get("blank") { return Ok((color, None)); } let name = entry["name"].as_str().unwrap().to_owned(); let symbol = entry["symbol"].as_str().unwrap().to_owned(); Ok(( color, Some(ColorEntry { color, name, symbol, }), )) } } impl Mapping { pub fn load( path: impl AsRef, img: &Image, ) -> Result> { let path = path.as_ref().to_path_buf(); let data: Vec> = serde_yaml::from_reader(std::fs::File::open(&path)?)?; // do validation to make sure all pixel colors are handled let color_map = data .into_iter() .map(|entry| { let (c, e) = ColorEntry::from_entry(entry)?; Ok((c, e)) }) .collect::>, Box>>()?; let all_image_colors = img .iter() .map(|(_, color)| color) .collect::>(); let missing_colors = all_image_colors .into_iter() .filter(|c| !color_map.contains_key(&c)) .collect::>(); if !missing_colors.is_empty() { return Err(Box::new(IncompleteMappingError { path, missing_colors, })); } Ok(Mapping(color_map)) } pub fn lookup(&self, color: Pixel) -> &str { if let Some(ref e) = self.0[&color] { &e.symbol } else { "" } } } #[derive(Deserialize)] pub struct Config { pub grid_every: u32, pub line_weight: f64, pub major_line_weight: f64, pub grid_size: f64, pub font: String, } impl Config { pub fn scale(&self, n: u32) -> f64 { n as f64 * self.grid_size } } impl Default for Config { fn default() -> Config { Config { grid_every: 10, line_weight: 1.0, major_line_weight: 3.0, grid_size: 24.0, font: "Fira Sans 12".to_string(), } } }