dioxus-google-fonts 0.1.4

A declarative Google Fonts embedding for Dioxus
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ExprArray, ExprTuple, ExprAssign, Expr, ExprLit, Lit};

#[proc_macro]
pub fn google_fonts_url(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as ExprArray);
    let mut families = Vec::new();

    for tuple in input.elems {
        let Expr::Tuple(ExprTuple { elems, .. }) = tuple else {
            panic!("Expected tuple: (\"Font Name\", wght = [...], ital = [...])");
        };

        let mut iter = elems.into_iter();
        let name = match iter.next().expect("Font name required") {
            Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) => s.value().replace(' ', "+"),
            _ => panic!("Font name must be a string literal"),
        };

        let mut wghts = Vec::new();
        let mut itals = Vec::new();

        for arg in iter {
            if let Expr::Assign(ExprAssign { left, right, .. }) = arg {
                let attr = match *left {
                    Expr::Path(p) => p.path.segments[0].ident.to_string(),
                    _ => panic!("Expected identifier (wght or ital)"),
                };

                match attr.as_str() {
                    "wght" => {
                        if let Expr::Array(arr) = *right {
                            for e in arr.elems {
                                match e {
                                    Expr::Lit(ExprLit { lit: Lit::Int(i), .. }) => {
                                        wghts.push(i.base10_digits().to_string());
                                    }
                                    Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) => {
                                        wghts.push(s.value());
                                    }
                                    _ => panic!("Invalid weight value"),
                                }
                            }
                        }
                    }
                    "ital" => {
                        if let Expr::Array(arr) = *right {
                            for e in arr.elems {
                                match e {
                                    Expr::Tuple(tup) if tup.elems.len() == 2 => {
                                        let ital_val = &tup.elems[0];
                                        let wght_val = &tup.elems[1];
                                        match (ital_val, wght_val) {
                                            (
                                                Expr::Lit(ExprLit { lit: Lit::Int(i1), .. }),
                                                Expr::Lit(ExprLit { lit: Lit::Int(i2), .. }),
                                            ) => {
                                                itals.push(format!("{},{}", i1.base10_digits(), i2.base10_digits()));
                                            }
                                            _ => panic!("Invalid ital tuple"),
                                        }
                                    }
                                    _ => panic!("Invalid ital value"),
                                }
                            }
                        }
                    }
                    _ => panic!("Unknown attribute: {}", attr),
                }
            }
        }

        let style = if !itals.is_empty() {
            format!("ital,wght@{}", itals.join(";"))
        } else if !wghts.is_empty() {
            format!("wght@{}", wghts.join(";"))
        } else {
            String::new()
        };

        let family = if style.is_empty() {
            format!("family={}", name)
        } else {
            format!("family={}:{style}", name)
        };

        families.push(family);
    }

    let final_url = format!(
        "https://fonts.googleapis.com/css2?{}&display=swap",
        families.join("&")
    );

    TokenStream::from(quote! {
        #final_url
    })
}

#[proc_macro]
pub fn google_fonts(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input2 = proc_macro2::TokenStream::from(input);

    let output = quote! {
        rsx! {
            document::Link {
                rel: "stylesheet",
                href: {
                    dioxus_google_fonts::google_fonts_url!(#input2)
                }
            }
        }
    };

    output.into()
}