rustreqwestrust-iced

Loading fonts at runtime in Rust


I'm working on a Rust desktop application that utilizes the Iced GUI. I use to load a custom font to use for Iced widgets:

// fonts module:
use iced::Font;

pub const NOTO_SANS_REGULAR: Font = Font::External {
    name: "noto-sans-regular",
    bytes: include_bytes!("../../../resources/fonts/noto-sans-regular.ttf"),
};

pub const NOTO_SANS_BOLD: Font = Font::External {
    name: "noto-sans-bold",
    bytes: include_bytes!("../../../resources/fonts/noto-sans-bold.ttf"),
};

// usage:
fonts::NOTO_SANS_REGULAR

But now I'm trying to load those fonts at runtime, so they won't be bundled into the executable and increase its size. Here is what I came up with:

// fonts module:
use once_cell::sync::OnceCell;
use iced::Font;
use tokio::runtime::Runtime;

pub static NOTO_SANS_REGULAR: OnceCell<Font> = OnceCell::new();
pub static NOTO_SANS_BOLD: OnceCell<Font> = OnceCell::new();

async fn download_font(name: &'static str, url: &str) -> Result<Font, reqwest::Error> {
    let response = reqwest::get(url).await?;
    let bytes = response.bytes().await?;
    let font_data = Box::new(bytes);
    let font = Font::External {
        name: name,
        bytes: &*Box::leak(font_data)
    };
    Ok(font)
}

pub fn load_fonts() {
    let runtime = Runtime::new().unwrap();

    runtime.spawn(async move {
        let noto_sans_regular = download_font("noto-sans-regular", "https://raw.githubusercontent.com/googlefonts/noto-fonts/main/unhinted/ttf/NotoSans/NotoSans-Regular.ttf");
        let noto_sans_bold = download_font("noto-sans-bold", "https://raw.githubusercontent.com/googlefonts/noto-fonts/main/unhinted/ttf/NotoSans/NotoSans-Bold.ttf");

        // Await all font downloads and store them in the OnceCell instances
        if let Ok(noto_sans_regular) = noto_sans_regular.await {
            NOTO_SANS_REGULAR.set(noto_sans_regular).ok();
        }
        if let Ok(noto_sans_bold) = noto_sans_bold.await {
            NOTO_SANS_BOLD.set(noto_sans_bold).ok();
        }
    });
}

// usage:
fonts::NOTO_SANS_REGULAR.get().unwrap_or(&Font::Default).clone()

The problem is that I receive DNS errors on the GET requests there, therefore I'm not sure whether the entire thing would work.

Failed to download font: reqwest::Error { kind: Request, url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("raw.githubusercontent.com")), port: None, path: "/googlefonts/noto-fonts/main/unhinted/ttf/NotoSans/NotoSans-Regular.ttf", query: None, fragment: None }, source: hyper::Error(Connect, ConnectError("dns error", Custom { kind: Interrupted, error: JoinError::Cancelled(Id(8)) })) }


Solution

  • If all you do is create a default Runtime and wait till it ran the Future to completion that's exactly what the tokio::main macro does, so you could use it instead:

    #[tokio::main]
    pub async fn load_fonts() {
        let noto_sans_regular = download_font("noto-sans-regular", "https://raw.githubusercontent.com/googlefonts/noto-fonts/main/unhinted/ttf/NotoSans/NotoSans-Regular.ttf");
        let noto_sans_bold = download_font("noto-sans-bold", "https://raw.githubusercontent.com/googlefonts/noto-fonts/main/unhinted/ttf/NotoSans/NotoSans-Bold.ttf");
    
        // Await all font downloads and store them in the OnceCell instances
        if let Ok(noto_sans_regular) = noto_sans_regular.await {
            NOTO_SANS_REGULAR.set(noto_sans_regular).ok();
        }
        if let Ok(noto_sans_bold) = noto_sans_bold.await {
            NOTO_SANS_BOLD.set(noto_sans_bold).ok();
        }
    }