zng_color_proc_macros/
hex_color.rs

1use proc_macro2::{Span, TokenStream};
2use syn::{LitInt, Path, Token, parse::Parse, parse_macro_input};
3
4struct Input {
5    crate_: Path,
6    #[expect(unused)]
7    comma: Token![,],
8    hex: TokenStream,
9}
10impl Parse for Input {
11    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
12        Ok(Input {
13            crate_: input.parse()?,
14            comma: input.parse()?,
15            hex: input.parse()?,
16        })
17    }
18}
19
20pub fn expand(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
21    let Input { crate_, hex, .. } = parse_macro_input!(input as Input);
22
23    let input_str: String = hex.to_string();
24
25    let hex_only = input_str
26        .trim()
27        .trim_start_matches('#')
28        .trim_start_matches("0x")
29        .trim_start()
30        .replace('_', "");
31
32    match hex_only.len() {
33        // RRGGBB
34        6 => {
35            let rgb = pair_to_f32(&hex_only);
36            quote! {
37                #crate_::Rgba::new(#rgb 1.0)
38            }
39        }
40        // RRGGBBAA
41        8 => {
42            let rgba = pair_to_f32(&hex_only);
43            quote! {
44                #crate_::Rgba::new(#rgba)
45            }
46        }
47        // RGB
48        3 => {
49            let rgb = single_to_f32(&hex_only);
50            quote! {
51                #crate_::Rgba::new(#rgb 1.0)
52            }
53        }
54        // RGBA
55        4 => {
56            let rgba = single_to_f32(&hex_only);
57            quote! {
58                #crate_::Rgba::new(#rgba)
59            }
60        }
61        // error
62        _ => {
63            quote! {
64                compile_error!("expected [#|0x]RRGGBB[AA] or [#|0x]RGB[A] color hexadecimal");
65            }
66        }
67    }
68    .into()
69}
70
71fn pair_to_f32(s: &str) -> TokenStream {
72    let mut r = TokenStream::new();
73    for i in 0..s.len() / 2 {
74        let i = i * 2;
75        let lit = LitInt::new(&format!("0x{}", &s[i..i + 2]), Span::call_site());
76        r.extend(quote! { #lit as f32 / 255.0, })
77    }
78    r
79}
80
81fn single_to_f32(s: &str) -> TokenStream {
82    s.chars()
83        .map(|c| {
84            let lit = LitInt::new(&format!("0x{c}{c}"), Span::call_site());
85            quote! { #lit as f32 / 255.0, }
86        })
87        .collect()
88}