1#[macro_use]
2extern crate lazy_static;
3
4use regex::{Match, Regex};
5use wasm_bindgen::prelude::*;
6use wee_alloc::WeeAlloc;
7
8#[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}