macroex_extras/
color.rs

1use macroex::{FromMacro, Either4, HexNumber, proc_macro2::{Span, Ident, TokenTree}, Error, bail, NumberList, Splat};
2
3fn hex(a: u8, span: Span) -> Result<u8, Error> {
4    Ok(match a {
5        b'0'..= b'9' => a - b'0',
6        b'a'..= b'z' => a - b'a' + 10,
7        b'A'..= b'Z' => a - b'A' + 10,
8        _ => bail!(span, "Not a valid hexadecial number.")
9    })
10}
11
12fn hex2(a: u8, b: u8, span: Span) -> Result<u8, Error> {
13    Ok((hex(a, span)? << 4) + hex(b, span)?)
14}
15
16
17fn parse_slice(lit: &[u8], span: Span) -> Result<[u8; 4], Error>{
18    Ok(match lit.len() {
19        3 => [
20            hex(lit[0], span)? * 17,
21            hex(lit[1], span)? * 17,
22            hex(lit[2], span)? * 17,
23            255
24        ],
25        4 => [
26            hex(lit[0], span)? * 17,
27            hex(lit[1], span)? * 17,
28            hex(lit[2], span)? * 17,
29            hex(lit[3], span)? * 17,
30        ],
31        6 => [
32            hex2(lit[0], lit[1], span)?,
33            hex2(lit[2], lit[3], span)?,
34            hex2(lit[4], lit[5], span)?,
35            255
36        ],
37        8 => [
38            hex2(lit[0], lit[1], span)?,
39            hex2(lit[2], lit[3], span)?,
40            hex2(lit[4], lit[5], span)?,
41            hex2(lit[6], lit[7], span)?,
42        ],
43        _ => bail!(span, "Invalid color syntax, must be of length 3, 4, 6 or 8."),
44    })
45}
46
47fn f2i(floats: [f32; 4]) -> [u8; 4] {
48    [
49        (floats[0] * 255.0) as u8,
50        (floats[1] * 255.0) as u8,
51        (floats[2] * 255.0) as u8,
52        (floats[3] * 255.0) as u8,
53    ]
54}
55
56fn smart_i2f(floats: [u8; 4]) -> [f32; 4] {
57    if floats.iter().all(|x| *x == 0 || *x == 1) {
58        [
59            floats[0] as f32,
60            floats[1] as f32,
61            floats[2] as f32,
62            floats[3] as f32,
63        ]
64    } else {
65        [
66            floats[0] as f32 / 255.0,
67            floats[1] as f32 / 255.0,
68            floats[2] as f32 / 255.0,
69            floats[3] as f32 / 255.0,
70        ]
71    }
72}
73
74fn i2f(floats: [u8; 4]) -> [f32; 4] {
75    [
76        floats[0] as f32 / 255.0,
77        floats[1] as f32 / 255.0,
78        floats[2] as f32 / 255.0,
79        floats[3] as f32 / 255.0,
80    ]
81}
82
83/// Parses an RGBA Color.
84///
85/// # Schema
86/// * Bracketed numbers: `[0.3, 0.72, 0.98]`, `[124, 54, 87, 255]`
87/// * Repeat syntax: `[0.3; 3]`, `[0.7; 4]`
88/// * Hex strings: `"AABBCC"`, `"AABBCCFF"`, `"#AABBCC"`, `"#AABBCCFF"`
89/// * Hex number literals: `0xAABBCC`, `0xAABBCCFF`
90/// * CSS color names: `Red`, `Blue`
91/// * TailwindCSS color names: `Red100`, `Sky400`
92///
93/// # Conversion
94/// Ints are in `0..=255`, floats are in `0.0..=1.0`.
95///
96/// When parsing to u8, if any value is a float, the color is considered in range `0.0..=1.0`
97///
98/// When parsing to float, if all values are ints and any of them is`>= 2`, the color is considered to be in range `0.0..=255.0`.
99#[derive(Debug, Default, PartialEq, Eq, Hash)]
100pub struct Rgba<T>(pub T);
101
102fn parse_color_name(name: &str, span: Span) -> Result<[u8; 4], Error>{
103    if let Some(num) = name.find(|x| ('0'..='9').contains(&x)) {
104        let (color, right) = name.split_at(num);
105        if let Ok(index) = right.parse() {
106            if let Some(color) = parse_color::parse_tailwind(color, index){
107                Ok(color)
108            } else {
109                bail!(span, "Invalid tailwind color {}-{}", color, index)
110            }
111        } else {
112            bail!(span, "Failed to parse color \"{}\"", name)
113        }
114    } else {
115        match parse_color::parse(&name){
116            Some(x) => Ok(x),
117            None => bail!(span, "Failed to parse color \"{}\"", name),
118        }
119    }
120}
121
122fn padi(arr: [u8; 3]) -> [u8; 4] {
123    [arr[0], arr[1], arr[2], 255]
124}
125
126fn padif(arr: [u8; 3]) -> [u8; 4] {
127    if arr.iter().all(|x| *x == 0 || *x == 1) {
128        [arr[0], arr[1], arr[2], 1]
129    } else {
130        [arr[0], arr[1], arr[2], 255]
131    }
132}
133
134fn padf(arr: [f32; 3]) -> [f32; 4] {
135    [arr[0], arr[1], arr[2], 1.0]
136}
137
138impl FromMacro for Rgba<[u8; 4]> {
139    fn from_one(tt: macroex::proc_macro2::TokenTree) -> Result<Self, macroex::Error> {
140        let span = tt.span();
141        match Either4::from_one(tt)? {
142            Either4::A(Splat(group)) => {
143                let tt = TokenTree::Group(group);
144                match Either4::from_one(tt)? {
145                    Either4::A(ints) => Ok(Self(padi(ints))),
146                    Either4::B(ints) => Ok(Self(ints)),
147                    Either4::C(NumberList(floats)) => Ok(Self(f2i(padf(floats)))),
148                    Either4::D(NumberList(floats)) => Ok(Self(f2i(floats))),
149                }
150            },
151            Either4::B(string) => {
152                let string: String = string;
153                if string.starts_with('#') {
154                    Ok(Self(parse_slice(&string.as_bytes()[1..], span)?))
155                } else {
156                    Ok(Self(parse_slice(&string.as_bytes(), span)?))
157                }
158            },
159            Either4::C(ident) => {
160                let ident: Ident = ident;
161                let name = ident.to_string();
162                Ok(Self(parse_color_name(&name, span)?))
163            },
164            Either4::D(HexNumber(_, hex)) => {
165                Ok(Self(parse_slice(&hex.as_bytes(), span)?))
166            },
167        }
168    }
169}
170
171
172impl FromMacro for Rgba<[f32; 4]> {
173    fn from_one(tt: macroex::proc_macro2::TokenTree) -> Result<Self, macroex::Error> {
174        let span = tt.span();
175        match Either4::from_one(tt)? {
176            Either4::A(Splat(group)) => {
177                let tt = TokenTree::Group(group);
178                match Either4::from_one(tt)? {
179                    Either4::A(ints) => Ok(Self(smart_i2f(padif(ints)))),
180                    Either4::B(ints) => Ok(Self(smart_i2f(ints))),
181                    Either4::C(NumberList(floats)) => Ok(Self(padf(floats))),
182                    Either4::D(NumberList(floats)) => Ok(Self(floats)),
183                }
184            },
185            Either4::B(string) => {
186                let string: String = string;
187                if string.starts_with('#') {
188                    Ok(Self(i2f(parse_slice(&string.as_bytes()[1..], span)?)))
189                } else {
190                    Ok(Self(i2f(parse_slice(&string.as_bytes(), span)?)))
191                }
192            },
193            Either4::C(ident) => {
194                let ident: Ident = ident;
195                let name = ident.to_string();
196                Ok(Self(i2f(parse_color_name(&name, span)?)))
197            },
198            Either4::D(HexNumber(_, hex)) => {
199                Ok(Self(i2f(parse_slice(&hex.as_bytes(), span)?)))
200            },
201        }
202    }
203}