dioxus_google_fonts/
lib.rs1use 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}