diff --git a/crates/fonts-build/src/embed.rs b/crates/fonts-build/src/embed.rs index a19cbedb..d8decac1 100644 --- a/crates/fonts-build/src/embed.rs +++ b/crates/fonts-build/src/embed.rs @@ -181,8 +181,9 @@ impl Embed { self.check_coverage(faces, dir, cargo); } } - let (table, faces) = match selected { - Ok(faces) => (table(&faces), faces.into_iter().map(|face| face.entry).collect()), + let tabled = selected.and_then(|faces| Ok((table(&faces)?, faces.into_iter().map(|face| face.entry).collect()))); + let (table, faces) = match tabled { + Ok(tabled) => tabled, Err(e) if required => { write(&out, EMPTY_TABLE)?; return Err(e); @@ -400,9 +401,12 @@ pub(crate) struct Env { pub out_dir: Option, } -fn table(faces: &[Face]) -> String { +/// The generated source; `include_bytes!` takes a string, so a face under a path that isn't UTF-8 +/// is an error rather than a lossy path the consumer's build can't find. +fn table(faces: &[Face]) -> Result { let mut src = String::from("// Generated by craft-fonts-build. Do not edit.\n&[\n"); for Face { entry, path, index, .. } in faces { + let file = path.to_str().ok_or_else(|| Error::NonUtf8Path { path: path.clone() })?; let scripts: Vec = entry.scripts.iter().map(|s| format!("{s:?}")).collect(); let _ = writeln!( src, @@ -411,11 +415,11 @@ fn table(faces: &[Face]) -> String { entry.style, scripts.join(", "), index, - path.display().to_string(), + file, ); } src.push_str("]\n"); - src + Ok(src) } fn write(path: &Path, contents: &str) -> Result<(), Error> { @@ -446,6 +450,8 @@ pub enum Error { Subset { path: PathBuf, reason: String }, /// [`Embed::documents`] names families the manifest lacks; `known` lists the manifest's. UnknownFamilies { manifest: PathBuf, families: Vec, known: Vec }, + /// A face to embed has a path that isn't UTF-8, which `include_bytes!` can't name. + NonUtf8Path { path: PathBuf }, } impl fmt::Display for Error { @@ -477,6 +483,7 @@ impl fmt::Display for Error { families.join(", "), known.join(", ") ), + Self::NonUtf8Path { path } => write!(f, "cannot embed {}: include_bytes! needs a UTF-8 path", path.display()), } } } diff --git a/crates/fonts-build/src/tests.rs b/crates/fonts-build/src/tests.rs index b7661399..03a04413 100644 --- a/crates/fonts-build/src/tests.rs +++ b/crates/fonts-build/src/tests.rs @@ -115,6 +115,20 @@ fn required_fails_without_a_usable_checkout() { assert!(matches!(run(Embed::new(), Some(&scratch.0), Some("1")).result, Err(Error::NoFaces { .. }))); } +#[cfg(unix)] +#[test] +fn rejects_a_checkout_under_a_non_utf8_path() { + use std::os::unix::ffi::OsStrExt; + let scratch = Scratch::new("non-utf8"); + let dir = scratch.0.join(std::ffi::OsStr::from_bytes(b"craft-fonts-\xff")); + std::fs::create_dir_all(dir.join("fonts")).unwrap(); + std::fs::copy(fixture().join("fonts/sans-arabic/SansArabic.ttf"), dir.join("fonts/a.ttf")).unwrap(); + std::fs::write(dir.join("fonts/manifest.txt"), "A | Regular | fonts/a.ttf | Arab | OFL-1.1 | OFL.txt | 00 | src\n").unwrap(); + let r = run(Embed::new(), Some(&dir), Some("1")); + assert!(matches!(r.result, Err(Error::NonUtf8Path { .. })), "{:?}", r.result); + assert_eq!(r.table.trim(), "&[]"); +} + #[test] fn embeds_the_selected_scripts() { let r = run(Embed::new(), Some(&fixture()), Some("1"));