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 6 => {
35 let rgb = pair_to_f32(&hex_only);
36 quote! {
37 #crate_::Rgba::new(#rgb 1.0)
38 }
39 }
40 8 => {
42 let rgba = pair_to_f32(&hex_only);
43 quote! {
44 #crate_::Rgba::new(#rgba)
45 }
46 }
47 3 => {
49 let rgb = single_to_f32(&hex_only);
50 quote! {
51 #crate_::Rgba::new(#rgb 1.0)
52 }
53 }
54 4 => {
56 let rgba = single_to_f32(&hex_only);
57 quote! {
58 #crate_::Rgba::new(#rgba)
59 }
60 }
61 _ => {
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}