use std::path::{Path, PathBuf};
use std::process::Command;
fn lib_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../lib-rustyfi")
}
fn run_with_big_stack(f: impl FnOnce() + Send + 'static) {
std::thread::Builder::new()
.stack_size(64 * 1024 * 1024)
.spawn(f)
.expect("spawn big-stack thread")
.join()
.expect("big-stack thread panicked (see assertion above)");
}
fn find_regular_ttf() -> Option<PathBuf> {
for family in ["DejaVuSerif", "DejaVuSans"] {
if let Ok(output) = Command::new("fc-match")
.args(["--format=%{file}", family])
.output()
{
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !path.is_empty() && Path::new(&path).is_file() && path.ends_with(".ttf") {
return Some(PathBuf::from(path));
}
}
}
}
for candidate in [
"/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/dejavu/DejaVuSans.ttf",
"/run/current-system/sw/share/fonts/truetype/DejaVuSans.ttf",
] {
if Path::new(candidate).is_file() {
return Some(PathBuf::from(candidate));
}
}
None
}
fn v006_only_lib_root() -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!(
"rustyfi-xver-capstone-v006-root-{}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create the 0.0.6-only lib root");
#[cfg(unix)]
std::os::unix::fs::symlink(lib_root().join("dist"), dir.join("dist"))
.expect("symlink the real dist/ into the 0.0.6-only lib root");
dir
}
#[test]
fn xver_capstone_renders_to_extractable_text() {
let font = match find_regular_ttf() {
Some(p) => p,
None => {
eprintln!("skipping xver capstone: no DejaVu TrueType font found");
return;
}
};
run_with_big_stack(move || {
let entry = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/xver-capstone.saty");
let program = rustyfi_loader::load(
&entry,
&rustyfi_loader::LoadOptions {
lib_root: Some(v006_only_lib_root()),
version: rustyfi_syntax::RustyfiVersion::V0_1,
..Default::default()
},
)
.expect(
"xver-capstone.saty + its real 0.0.6 list/option @require: targets + the local \
0.1 @import: helper must all load through one LoadOptions",
);
let saw_v006 = program.files[..program.files.len() - 1]
.iter()
.filter(|f| matches!(f.version, rustyfi_syntax::RustyfiVersion::V0_0))
.count();
assert_eq!(
saw_v006,
2,
"list.satyg + option.satyg should both be V0_0-tagged deps: {:?}",
program
.files
.iter()
.map(|f| (&f.path, f.version))
.collect::<Vec<_>>()
);
let store =
rustyfi_pdf::TtfFontStore::load(&font, None, None).expect("load DejaVu regular face");
let doc = rustyfi_lang::compile_document_v1(&program.files, &store).expect(
"the xver capstone must compile end-to-end: a real 0.0.6 list/option dependency \
spliced into a 0.1 whole-program compile, through real elaborate/typecheck/eval",
);
assert!(!doc.pages.is_empty(), "expected at least one page");
assert!(
doc.pages.iter().any(|p| !p.lines.is_empty()),
"expected at least one non-empty page"
);
let bytes = rustyfi_pdf::render_pdf_ttf(&doc.geometry, &doc.pages, &store, &doc.images)
.expect("PDF rendering must succeed");
assert!(bytes.starts_with(b"%PDF-"), "not a PDF header");
assert!(
bytes.windows(9).any(|w| w == b"FontFile2"),
"expected an embedded TrueType font (FontFile2) in the capstone PDF"
);
let tmp = std::env::temp_dir().join(format!(
"rustyfi-e2e-xver-capstone-{}.pdf",
std::process::id()
));
std::fs::write(&tmp, &bytes).unwrap();
let pdftotext = Command::new("pdftotext").arg(&tmp).arg("-").output();
match pdftotext {
Ok(out) if out.status.success() => {
let text = String::from_utf8_lossy(&out.stdout);
assert!(
text.contains("36"),
"pdftotext output missing \"36\" — the cross-version List/Option \
computation must have reached the rendered PDF:\n{text}"
);
for word in ["quick", "brown", "fox"] {
assert!(
text.contains(word),
"pdftotext output missing {word:?} — the capstone must render \
extractable Latin body text:\n{text}"
);
}
}
_ => eprintln!(
"pdftotext unavailable; the PDF-header + FontFile2-embed checks already passed"
),
}
let _ = std::fs::remove_file(&tmp);
});
}