Skip to main content

dioxus_google_fonts/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, ExprArray, ExprTuple, ExprAssign, Expr, ExprLit, Lit};
4
5#[proc_macro]
6pub fn google_fonts_url(input: TokenStream) -> TokenStream {
7    let input = parse_macro_input!(input as ExprArray);
8    let mut families = Vec::new();
9
10    for tuple in input.elems {
11        let Expr::Tuple(ExprTuple { elems, .. }) = tuple else {
12            panic!("Expected tuple: (\"Font Name\", wght = [...], ital = [...])");
13        };
14
15        let mut iter = elems.into_iter();
16        let name = match iter.next().expect("Font name required") {
17            Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) => s.value().replace(' ', "+"),
18            _ => panic!("Font name must be a string literal"),
19        };
20
21        let mut wghts = Vec::new();
22        let mut itals = Vec::new();
23
24        for arg in iter {
25            if let Expr::Assign(ExprAssign { left, right, .. }) = arg {
26                let attr = match *left {
27                    Expr::Path(p) => p.path.segments[0].ident.to_string(),
28                    _ => panic!("Expected identifier (wght or ital)"),
29                };
30
31                match attr.as_str() {
32                    "wght" => {
33                        if let Expr::Array(arr) = *right {
34                            for e in arr.elems {
35                                match e {
36                                    Expr::Lit(ExprLit { lit: Lit::Int(i), .. }) => {
37                                        wghts.push(i.base10_digits().to_string());
38                                    }
39                                    Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) => {
40                                        wghts.push(s.value());
41                                    }
42                                    _ => panic!("Invalid weight value"),
43                                }
44                            }
45                        }
46                    }
47                    "ital" => {
48                        if let Expr::Array(arr) = *right {
49                            for e in arr.elems {
50                                match e {
51                                    Expr::Tuple(tup) if tup.elems.len() == 2 => {
52                                        let ital_val = &tup.elems[0];
53                                        let wght_val = &tup.elems[1];
54                                        match (ital_val, wght_val) {
55                                            (
56                                                Expr::Lit(ExprLit { lit: Lit::Int(i1), .. }),
57                                                Expr::Lit(ExprLit { lit: Lit::Int(i2), .. }),
58                                            ) => {
59                                                itals.push(format!("{},{}", i1.base10_digits(), i2.base10_digits()));
60                                            }
61                                            _ => panic!("Invalid ital tuple"),
62                                        }
63                                    }
64                                    _ => panic!("Invalid ital value"),
65                                }
66                            }
67                        }
68                    }
69                    _ => panic!("Unknown attribute: {}", attr),
70                }
71            }
72        }
73
74        let style = if !itals.is_empty() {
75            format!("ital,wght@{}", itals.join(";"))
76        } else if !wghts.is_empty() {
77            format!("wght@{}", wghts.join(";"))
78        } else {
79            String::new()
80        };
81
82        let family = if style.is_empty() {
83            format!("family={}", name)
84        } else {
85            format!("family={}:{style}", name)
86        };
87
88        families.push(family);
89    }
90
91    let final_url = format!(
92        "https://fonts.googleapis.com/css2?{}&display=swap",
93        families.join("&")
94    );
95
96    TokenStream::from(quote! {
97        #final_url
98    })
99}
100
101#[proc_macro]
102pub fn google_fonts(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
103    let input2 = proc_macro2::TokenStream::from(input);
104
105    let output = quote! {
106        rsx! {
107            document::Link {
108                rel: "stylesheet",
109                href: {
110                    dioxus_google_fonts::google_fonts_url!(#input2)
111                }
112            }
113        }
114    };
115
116    output.into()
117}