pub fn letters_to_num(letters: &str) -> Option { let mut num: u8 = 0; for (i, letter) in letters.to_ascii_uppercase().chars().rev().enumerate() { let n = letter as u8; if !(65..=90).contains(&n) { return None; } num = num.checked_add((26_i32.pow(i as u32) * (n as i32 - 64)).try_into().ok()?)?; } Some(num) } pub fn num_to_letters(num: u8) -> Option { if num == 0 { return None; } // Otherwise, we will not return none! // As another note, we can't represent higher than "IV" anyway; // thus there's no reason for a loop (26^n with n>1 will NOT occur). let mut text = "".to_string(); let mut digit1 = num.div_euclid(26u8); let mut digit2 = num.rem_euclid(26u8); if digit1 > 0 && digit2 == 0u8 { digit1 -= 1; digit2 = 26; } if digit1 != 0 { text.push((64 + digit1) as char) } text.push((64 + digit2) as char); Some(text.to_string()) } // // Palettes moved from pt-web // // Sources: // https://iquilezles.org/articles/palettes/ // http://dev.thi.ng/gradients/ #[derive(Clone, Copy, PartialEq, Debug)] pub struct ColorPalette { a: [f64; 3], b: [f64; 3], c: [f64; 3], d: [f64; 3], } impl ColorPalette { pub fn _new(a: [f64; 3], b: [f64; 3], c: [f64; 3], d: [f64; 3]) -> Self { ColorPalette { a, b, c, d } } pub fn get(&self, t: f64) -> [f64; 3] { [ (self.a[0] + self.b[0] * f64::cos(std::f64::consts::TAU * (self.c[0] * t + self.d[0]))) * 255.0, (self.a[1] + self.b[1] * f64::cos(std::f64::consts::TAU * (self.c[1] * t + self.d[1]))) * 255.0, (self.a[2] + self.b[2] * f64::cos(std::f64::consts::TAU * (self.c[2] * t + self.d[2]))) * 255.0, ] } #[allow(dead_code)] // Preserve old implementation for reference fn get_u8(&self, t: u8) -> [f64; 3] { assert!(t > 0, "t must be greater than zero!"); self.get((2f64.powi(-(t.ilog2() as i32))) * (t as f64 + 0.5f64) - 1.0f64) } // pub fn get_uuid(&self, t: uuid::Uuid) -> [f64; 3] { // // self.get(t.as_u128() as f64 / (u128::MAX) as f64) // let mut r = SmallRng::seed_from_u64(t.as_u128() as u64); // self.get(r.gen_range(0.0..1.0f64)) // } pub fn get_ordered(&self, t: uuid::Uuid, ordered_uuids: &[uuid::Uuid]) -> [f64; 3] { let index = ordered_uuids.iter().position(|&x| x == t).expect("uuid must be in list of uuids") + 1; self.get(Self::space_evenly(index)) } pub fn get_linear(&self, t: f64, max: f64) -> [f64; 3] { let scaled = t / max; self.get(scaled) } fn space_evenly(x: usize) -> f64 { let e: usize = (x.ilog2() + 1) as usize; let d: usize = 2usize.pow(e as u32); let n: usize = (2*x + 1) % d; (n as f64) / (d as f64) } } #[non_exhaustive] pub struct Palettes; #[allow(dead_code)] impl Palettes { pub const RAINBOW: ColorPalette = ColorPalette { a: [0.500, 0.500, 0.500], b: [0.700, 0.700, 0.700], c: [0.800, 0.800, 0.800], d: [0.000, 0.333, 0.667], }; pub const YELLOW_PINK: ColorPalette = ColorPalette { a: [0.500, 0.500, 0.320], b: [0.500, 0.500, 0.500], c: [0.100, 0.500, 0.360], d: [0.000, 0.000, 0.650], }; } #[cfg(test)] mod tests { use super::{letters_to_num, num_to_letters}; #[test] fn test_letters_to_num() { assert_eq!(letters_to_num("D"), Some(4)); assert_eq!(letters_to_num("d"), None); assert_eq!(letters_to_num("AD"), Some(26 + 4)); assert_eq!(letters_to_num("CG"), Some(3 * 26 + 7)); } #[test] fn test_num_to_letters() { println!("27 is {:?}", num_to_letters(27)); assert_eq!(num_to_letters(1), Some("A".to_string())); assert_eq!(num_to_letters(26), Some("Z".to_string())); assert_eq!(num_to_letters(27), Some("AA".to_string())); assert_eq!(num_to_letters(111), Some("DG".to_string())); } #[test] fn test_l2n_and_n2l() { assert_eq!( num_to_letters(letters_to_num("A").unwrap()), Some("A".to_string()) ); assert_eq!( num_to_letters(letters_to_num("BJ").unwrap()), Some("BJ".to_string()) ); for i in 1..=255 { assert_eq!(letters_to_num(&num_to_letters(i).unwrap()), Some(i)); } } }