mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-27 03:27:21 -06:00
Investigating the worst file of the first 250 turned the headline finding around. bug1552113.pdf scores 84%, and the cause is Poppler: it paints the link's `/Border [0 0 112]` as a 112-unit band across a 250x50 page, hiding the two lines of text. The fixture is named "this text should be visible", and in our rendering it is. The oracle is not ground truth, and the top of its worst list needs a person to look at both images before it is read as a bug of ours. That led to a plausible change that the measurement rejected. Poppler synthesises appearances for annotations that have none, where we draw only what the document carries -- `/AP`-less link borders are already recorded as not covered under view.render-annotation-appearances -- so hiding annotations looked like the way to measure page content alone. It is worse: it then scores Poppler for every appearance we do draw. Over the same 250 files, shown gives mean 2.16%, p90 5.35% and 25 files within 0.1%; hidden gives 2.18%, 5.55% and 22, with annotation-tx3.pdf jumping to 45.64% and annotation-tx2.pdf to 18.79%. The two biases nearly cancel. So the default is unchanged and `--hide-annotations` is an opt-in for explaining a single file, with the numbers written down so the experiment is not repeated. Verified that the default reproduces the merged baseline exactly and that the flag moves it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 lines
3.6 KiB
Rust
82 lines
3.6 KiB
Rust
//! Project automation for PdfCraft, run via `cargo xtask <command>`.
|
|
|
|
use std::process::ExitCode;
|
|
|
|
mod assets;
|
|
mod corpus;
|
|
mod demo_pdf;
|
|
mod fuzz;
|
|
mod gates;
|
|
mod layers;
|
|
mod macos_integration;
|
|
mod parity;
|
|
mod screenshots;
|
|
mod version;
|
|
|
|
type Command = fn(&[String]) -> anyhow::Result<()>;
|
|
|
|
/// Every subcommand: name, one-line summary, entry point.
|
|
const COMMANDS: &[(&str, &str, Command)] = &[
|
|
("version", "Print the workspace version, or `version set X.Y.Z[-pre]` to change it and refresh Cargo.lock", version_cmd),
|
|
("layers", "Enforce the crate dependency layering (plan/architecture.md §3)", gates::layers),
|
|
("wasm", "cargo check --target wasm32-unknown-unknown for every crate below L8", gates::wasm),
|
|
("deny", "Dependency licences, bans, sources and advisories (deny.toml; needs cargo-deny)", gates::deny),
|
|
("ci", "fmt --check, clippy -D warnings, test, layers, wasm, assets, deny, parity (stops at first failure)", gates::ci),
|
|
("assets", "Enforce the asset policy (AGENTS.md §1) against ATTRIBUTION.toml; --write regenerates ATTRIBUTION.md", assets::run),
|
|
("corpus", "Fetch the pinned pdf.js test corpus into corpus/ (git-ignored) and verify it; --update-pin re-records the pin", gates::corpus),
|
|
("check", "Robustness sweep over corpus/ with pdfcraft-cli; fails on crashes or regressions vs xtask/baselines", gates::check),
|
|
("fuzz", "Mutation fuzzing of open/render/edit/save in child processes; findings in fuzz-out/ (--time 300)", fuzz::run),
|
|
("parity", "Validate parity/acrobat-features.toml against the registry, tools and tests; report progress (--json, --partial)", parity::run),
|
|
(
|
|
"macos-integration",
|
|
"Report macOS bundle, Quick Look, Spotlight and PDF integration diagnostics without mutating system state",
|
|
macos_integration::run,
|
|
),
|
|
("text-oracle", "Compare text extraction with pdftotext over corpus/ (word F1; target median ≥ 0.97)", gates::text_oracle),
|
|
(
|
|
"render-oracle",
|
|
"Compare page rendering with Poppler over corpus/ (pixel disagreement rate; --dir, --dpi, --limit, --tolerance, --hide-annotations)",
|
|
gates::render_oracle,
|
|
),
|
|
("screenshots", "Regenerate the README screenshots in docs/images/ and their ATTRIBUTION entries", screenshots::run),
|
|
("models", "Fetch the OCR models (ATTRIBUTION.toml kind = \"model\") into assets/models/, verified by SHA-256", assets::models),
|
|
("demo-pdf", "Build dist/demo/pdfcraft-showcase.pdf (needs Google Chrome or Chromium)", demo_pdf::run),
|
|
];
|
|
|
|
fn version_cmd(args: &[String]) -> anyhow::Result<()> {
|
|
let args: Vec<&str> = args.iter().map(String::as_str).collect();
|
|
version::run(&gates::root(), &args).map_err(anyhow::Error::msg)
|
|
}
|
|
|
|
fn main() -> ExitCode {
|
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
|
let Some(name) = args.first() else {
|
|
print_help();
|
|
return ExitCode::FAILURE;
|
|
};
|
|
if matches!(name.as_str(), "help" | "-h" | "--help") {
|
|
print_help();
|
|
return ExitCode::SUCCESS;
|
|
}
|
|
let Some((_, _, command)) = COMMANDS.iter().find(|(n, _, _)| n == name) else {
|
|
eprintln!("xtask: unknown command `{name}`\n");
|
|
print_help();
|
|
return ExitCode::FAILURE;
|
|
};
|
|
match command(&args[1..]) {
|
|
Ok(()) => ExitCode::SUCCESS,
|
|
Err(err) => {
|
|
eprintln!("xtask {name}: error: {err:#}");
|
|
ExitCode::FAILURE
|
|
}
|
|
}
|
|
}
|
|
|
|
fn print_help() {
|
|
eprintln!("Usage: cargo xtask <command> [args]\n\nCommands:");
|
|
for (name, about, _) in COMMANDS {
|
|
eprintln!(" {name:<12} {about}");
|
|
}
|
|
eprintln!(" {:<12} Show this list", "help");
|
|
}
|