wasm_color_string/
lib.rs

1#[macro_use]
2extern crate lazy_static;
3
4use regex::{Match, Regex};
5use wasm_bindgen::prelude::*;
6use wee_alloc::WeeAlloc;
7
8// Use `wee_alloc` as the global allocator.
9#[global_allocator]
10static ALLOC: WeeAlloc = WeeAlloc::INIT;
11
12#[wasm_bindgen]
13#[derive(Debug, Copy, Clone, PartialEq, Eq)]
14pub enum Model {
15    Rgb,
16    Hsl,
17    Hwb,
18}
19
20#[wasm_bindgen]
21#[derive(Debug, PartialEq)]
22pub struct Color(pub Model, pub f32, pub f32, pub f32, pub f32);
23
24#[wasm_bindgen]
25#[derive(Debug, PartialEq)]
26pub struct Rgb {
27    pub r: u8,
28    pub g: u8,
29    pub b: u8,
30    pub a: f32,
31}
32
33#[wasm_bindgen]
34#[derive(Debug, PartialEq)]
35pub struct Hsl {
36    pub h: f32,
37    pub s: f32,
38    pub l: f32,
39    pub a: f32,
40}
41
42#[wasm_bindgen]
43#[derive(Debug, PartialEq)]
44pub struct Hwb {
45    pub h: f32,
46    pub w: f32,
47    pub b: f32,
48    pub a: f32,
49}
50
51#[wasm_bindgen]
52pub fn get_color(string: &str) -> Option<Color> {
53    let prefix = &string[0..3];
54    match prefix {
55        "hsl" => {
56            println!("HSL");
57            let hsl = get_hsl(string)?;
58            Some(Color(Model::Hsl, hsl.h, hsl.s, hsl.l, hsl.a as f32))
59        }
60        "hwb" => {
61            println!("HWB");
62            let hwb = get_hwb(string)?;
63            Some(Color(Model::Hwb, hwb.h, hwb.w, hwb.b, hwb.a as f32))
64        }
65        _ => {
66            let rgb = get_rgb(string)?;
67            Some(Color(
68                Model::Rgb,
69                rgb.r as f32,
70                rgb.g as f32,
71                rgb.b as f32,
72                rgb.a as f32,
73            ))
74        }
75    }
76}
77
78#[wasm_bindgen]
79pub fn get_rgb(string: &str) -> Option<Rgb> {
80    lazy_static! {
81        static ref RE_HEX_SHORT: Regex = Regex::new(r"^#([0-9A-Fa-f]{3})([0-9A-Fa-f]{1})?$").unwrap();
82        static ref RE_HEX_LONG: Regex = Regex::new(r"^#([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$").unwrap();
83        static ref RE_RGB: Regex = Regex::new(r"^rgba?\(\s*([+-]?\d+)\s*,?\s*([+-]?\d+)\s*,?\s*([+-]?\d+)\s*(?:[,/]\s*([+-]?\d*(?:\.\d+)?%?)\s*)?\)$").unwrap();
84        static ref RE_RGB_PERCENT: Regex = Regex::new(r"^rgba?\(\s*([+-]?[\d\.]+)%\s*,?\s*([+-]?[\d\.]+)%\s*,?\s*([+-]?[\d\.]+)%\s*(?:[,/]\s*([+-]?\d*(?:\.\d+)?%?)\s*)?\)$").unwrap();
85    }
86    if RE_HEX_SHORT.is_match(string) {
87        let groups = RE_HEX_SHORT.captures(string)?;
88        let rgb = groups.get(1)?.as_str();
89        let r: u8 = parse_u8_hex(&rgb[0..1], 0, 15)?;
90        let g: u8 = parse_u8_hex(&rgb[1..2], 0, 15)?;
91        let b: u8 = parse_u8_hex(&rgb[2..3], 0, 15)?;
92        let a: f32 = match groups.get(2) {
93            Some(value) => {
94                let alpha = parse_u8_hex(value.as_str(), 0, 15)?;
95                ((16 * alpha) + alpha) as f32 / 255.0
96            }
97            None => 1.0,
98        };
99        return Some(Rgb {
100            r: (16 * r) + r,
101            g: (16 * g) + g,
102            b: (16 * b) + b,
103            a,
104        });
105    } else if RE_HEX_LONG.is_match(string) {
106        let groups = RE_HEX_LONG.captures(string)?;
107        let rgb = groups.get(1)?.as_str();
108        let r: u8 = parse_u8_hex(&rgb[0..2], 0, 255)?;
109        let g: u8 = parse_u8_hex(&rgb[2..4], 0, 255)?;
110        let b: u8 = parse_u8_hex(&rgb[4..6], 0, 255)?;
111        let a: f32 = match groups.get(2) {
112            Some(value) => parse_u8_hex(value.as_str(), 0, 255)? as f32 / 255.0,
113            None => 1.0,
114        };
115        return Some(Rgb { r, g, b, a });
116    } else if RE_RGB.is_match(string) {
117        let groups = RE_RGB.captures(string)?;
118        let r: u8 = parse_u8(groups.get(1)?.as_str(), 0, 255)?;
119        let g: u8 = parse_u8(groups.get(2)?.as_str(), 0, 255)?;
120        let b: u8 = parse_u8(groups.get(3)?.as_str(), 0, 255)?;
121        let a: f32 = get_alpha(groups.get(4))?;
122        return Some(Rgb { r, g, b, a });
123    } else if RE_RGB_PERCENT.is_match(string) {
124        let groups = RE_RGB_PERCENT.captures(string)?;
125        let r: u8 = (get_percentage(groups.get(1)?.as_str())? * 2.55) as u8;
126        let g: u8 = (get_percentage(groups.get(1)?.as_str())? * 2.55) as u8;
127        let b: u8 = (get_percentage(groups.get(1)?.as_str())? * 2.55) as u8;
128        let a: f32 = get_alpha(groups.get(4))?;
129        return Some(Rgb { r, g, b, a });
130    } else {
131        return None;
132    }
133}
134
135#[wasm_bindgen]
136pub fn get_hsl(string: &str) -> Option<Hsl> {
137    lazy_static! {
138        static ref RE: Regex = Regex::new(r"^hsla?\(\s*([+-]?\d{0,3}(?:\.\d+)?)(?:deg)?\s*,?\s*([+-]?[\d\.]+)%\s*,?\s*([+-]?[\d\.]+)%\s*(?:[,/]\s*([+-]?\d*(?:\.\d+)?%?)\s*)?\)$").unwrap();
139    }
140    let groups = RE.captures(string)?;
141    let h: f32 = groups
142        .get(1)?
143        .as_str()
144        .parse::<f32>()
145        .ok()?
146        .rem_euclid(360.0);
147    let s: f32 = get_percentage(groups.get(2)?.as_str())?;
148    let l: f32 = get_percentage(groups.get(3)?.as_str())?;
149    let a: f32 = get_alpha(groups.get(4))?;
150    Some(Hsl { h, s, l, a })
151}
152
153#[wasm_bindgen]
154pub fn get_hwb(string: &str) -> Option<Hwb> {
155    lazy_static! {
156        static ref RE: Regex = Regex::new(r"^hwb\(\s*([+-]?\d{0,3}(?:\.\d+)?)(?:deg)?\s*,?\s*([+-]?[\d\.]+)%\s*,?\s*([+-]?[\d\.]+)%\s*(?:[,/]\s*([+-]?\d*(?:\.\d+)?%?)\s*)?\)$").unwrap();
157    }
158    let groups = RE.captures(string)?;
159    let h: f32 = groups
160        .get(1)?
161        .as_str()
162        .parse::<f32>()
163        .ok()?
164        .rem_euclid(360.0);
165    let w: f32 = get_percentage(groups.get(2)?.as_str())?;
166    let b: f32 = get_percentage(groups.get(3)?.as_str())?;
167    let a: f32 = get_alpha(groups.get(4))?;
168    Some(Hwb { h, w, b, a })
169}
170
171fn get_alpha(option: Option<Match>) -> Option<f32> {
172    match option {
173        Some(value) => parse_alpha(value.as_str()),
174        None => Some(1.0),
175    }
176}
177
178fn parse_alpha(string: &str) -> Option<f32> {
179    let len = string.len();
180    let last_char = string.chars().last().unwrap();
181    if last_char == '%' {
182        Some(get_percentage(&string[0..len - 1])? / 100.0)
183    } else {
184        parse_f32(&string, 0.0, 1.0)
185    }
186}
187
188fn get_percentage(string: &str) -> Option<f32> {
189    parse_f32(&string, 0.0, 100.0)
190}
191
192fn parse_f32(string: &str, min: f32, max: f32) -> Option<f32> {
193    Some(string.parse::<f32>().ok()?.max(min).min(max))
194}
195
196fn parse_u8(string: &str, min: u8, max: u8) -> Option<u8> {
197    Some(string.parse::<u8>().ok()?.max(min).min(max))
198}
199
200fn parse_u8_hex(string: &str, min: u8, max: u8) -> Option<u8> {
201    Some(u8::from_str_radix(string, 16).ok()?.max(min).min(max))
202}