//! Embeds the optional craft-fonts build input (`CRAFT_FONTS_DIR`): both `fonts/manifest.txt` and the //! opt-in Latin presentation fonts in `fonts/latin-manifest.txt` (Inter, Roboto, …). Unset or missing, //! `CRAFT_FONTS` is empty and the app uses system fonts. Recipe: craft-fonts `docs/integration.md`. //! Noto Sans CJK only with `FIGCRAFT_EMBED_CJK` set (the web build's only way to show CJK UI text). use std::fmt::Write as _; use std::path::PathBuf; fn main() { println!("cargo::rerun-if-env-changed=CRAFT_FONTS_DIR"); println!("cargo::rerun-if-env-changed=CRAFT_FONTS_REQUIRED"); println!("cargo::rerun-if-env-changed=FIGCRAFT_EMBED_CJK"); let mut src = String::from("pub static CRAFT_FONTS: &[CraftFont] = &[\n"); if let Some(dir) = std::env::var_os("CRAFT_FONTS_DIR").map(PathBuf::from) { let mut ok = false; for manifest in ["fonts/latin-manifest.txt", "fonts/manifest.txt"] { match craft_fonts(&dir, manifest) { Ok(entries) => { ok = true; src.push_str(&entries); } Err(e) if std::env::var_os("CRAFT_FONTS_REQUIRED").is_some() => { println!("cargo::error=CRAFT_FONTS_DIR={}: {e}", dir.display()); } Err(e) => println!("cargo::warning=craft-fonts {manifest} skipped: CRAFT_FONTS_DIR={}: {e}", dir.display()), } } if !ok { println!("cargo::warning=building without craft-fonts (once it is there, `cargo clean -p figcraft-text` makes the next build use it)"); } } src.push_str("];\n"); let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("craft_fonts.rs"); if let Err(e) = std::fs::write(&out, src) { println!("cargo::error=writing {}: {e}", out.display()); } } /// Rebuild when `manifest` changes or, while it is missing, when it is added to its folder. /// /// Cargo takes a watched path that doesn't exist as changed on every build, and then rebuilds this /// crate and every crate that depends on it each time. So a missing manifest is never watched /// itself: its folder is, when there is one (a craft-fonts checkout without the Latin manifest). /// With no craft-fonts at all there is nothing to watch, so the build can't notice it arriving: /// the warning in `main` says how to pick it up. fn watch(manifest: &std::path::Path) { let watched = if manifest.exists() { Some(manifest) } else { manifest.parent().filter(|folder| folder.is_dir()) }; if let Some(path) = watched { println!("cargo::rerun-if-changed={}", path.display()); } } fn craft_fonts(dir: &std::path::Path, manifest_rel: &str) -> Result { let manifest = dir.join(manifest_rel); watch(&manifest); let text = std::fs::read_to_string(&manifest).map_err(|e| format!("{}: {e}", manifest.display()))?; let wasm = std::env::var("CARGO_CFG_TARGET_ARCH").is_ok_and(|a| a == "wasm32"); let mut out = String::new(); for line in text.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#')) { let f: Vec<&str> = line.split(" | ").map(str::trim).collect(); let [family, style, file, scripts, ..] = f.as_slice() else { return Err(format!("malformed manifest line: {line}")); }; // CJK fonts are large; the desktop app finds system CJK fonts instead unless asked. let cjk = scripts.contains("Hans"); if cjk && std::env::var_os("FIGCRAFT_EMBED_CJK").is_none() { continue; } // Web builds have a size budget: Inter, plus Noto Sans CJK (every CJK script, for CJK UI // text) when asked. if wasm && *family != "Inter" && !cjk { continue; } let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?; println!("cargo::rerun-if-changed={}", path.display()); let _ = writeln!(out, " CraftFont {{ family: {family:?}, style: {style:?}, bytes: include_bytes!({:?}) }},", path.display().to_string()); } Ok(out) }