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
}
#[test]
fn v01_stdjabook_capstone_renders_to_extractable_text() {
let font = match find_regular_ttf() {
Some(p) => p,
None => {
eprintln!("skipping v01 std-ja-book capstone: no DejaVu TrueType font found");
return;
}
};
run_with_big_stack(move || {
let entry =
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v01-stdja-book.saty");
let program = rustyfi_loader::load(
&entry,
&rustyfi_loader::LoadOptions {
lib_root: Some(lib_root().join("dist-v01").join("packages")),
version: rustyfi_syntax::RustyfiVersion::V0_1,
..Default::default()
},
)
.expect(
"std-ja-book.satyh + its full transitive @require: graph + v01-stdja-book.saty \
must load",
);
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 std-ja-book capstone must compile end-to-end: sealed module + records-in-\
type-position + optional-arg-rows increments 1/2/3a + FootnoteScheme.main, \
through real elaborate/typecheck/sealing/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-v01-stdja-book-{}.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("SATySFi in Rust"), "missing title:\n{text}");
assert!(
text.contains("The Vendoring Agents"),
"missing author:\n{text}"
);
assert!(
text.contains("1. Introduction"),
"missing section 1 title:\n{text}"
);
assert!(
text.contains("2. Conclusion"),
"missing section 2 title:\n{text}"
);
for word in ["quick", "brown", "fox"] {
assert!(
text.contains(word),
"pdftotext output missing {word:?} — the std-ja-book capstone must \
render extractable Latin body text:\n{text}"
);
}
assert!(
text.contains("*1"),
"missing footnote superscript marker:\n{text}"
);
assert!(
text.contains("A trailing footnote"),
"missing footnote body text:\n{text}"
);
assert!(text.contains('1'), "missing footer page number:\n{text}");
}
_ => eprintln!(
"pdftotext unavailable; the PDF-header + FontFile2-embed checks already passed"
),
}
let _ = std::fs::remove_file(&tmp);
});
}
#[test]
fn v01_stdjareport_capstone_renders_to_extractable_text() {
let font = match find_regular_ttf() {
Some(p) => p,
None => {
eprintln!("skipping v01 std-ja-report capstone: no DejaVu TrueType font found");
return;
}
};
run_with_big_stack(move || {
let entry =
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v01-stdja-report.saty");
let program = rustyfi_loader::load(
&entry,
&rustyfi_loader::LoadOptions {
lib_root: Some(lib_root().join("dist-v01").join("packages")),
version: rustyfi_syntax::RustyfiVersion::V0_1,
..Default::default()
},
)
.expect(
"std-ja-report.satyh + its full transitive @require: graph + \
v01-stdja-report.saty must load",
);
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 std-ja-report capstone must compile end-to-end: sealed module + \
page-break-multicolumn + hook-page-break-block + Ref.increment + \
FootnoteScheme.main, through real elaborate/typecheck/sealing/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-v01-stdja-report-{}.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("SATySFi in Rust"), "missing title:\n{text}");
assert!(
text.contains("The Vendoring Agents"),
"missing author:\n{text}"
);
assert!(
text.contains("1. Introduction"),
"missing chapter 1 title:\n{text}"
);
assert!(
text.contains("2. Conclusion"),
"missing chapter 2 title:\n{text}"
);
assert!(
text.contains("1.1. Background"),
"missing section 1.1 title:\n{text}"
);
for word in ["quick", "brown", "fox"] {
assert!(
text.contains(word),
"pdftotext output missing {word:?} — the std-ja-report capstone must \
render extractable Latin body text:\n{text}"
);
}
assert!(
text.contains("Definition"),
"missing Definition label:\n{text}"
);
assert!(text.contains("Theorem"), "missing Theorem label:\n{text}");
assert!(text.contains("Proof"), "missing Proof label:\n{text}");
assert!(
text.contains("*1"),
"missing footnote superscript marker:\n{text}"
);
assert!(
text.contains("A trailing footnote"),
"missing footnote body text:\n{text}"
);
assert!(text.contains('1'), "missing footer page number:\n{text}");
}
_ => eprintln!(
"pdftotext unavailable; the PDF-header + FontFile2-embed checks already passed"
),
}
let _ = std::fs::remove_file(&tmp);
});
}