Files
pdfcraft/xtask/src/main.rs
T
bmh129 a10906e991 M10: Recognize Text in the browser, read in Web Workers (ocr.ocr-web)
The web build downloads the OCR models the first time Recognize Text (or
Edit on a scanned page) needs them, installs the engine, and reads pages
in up to two Web Workers (apps/pdfcraft-ocr-worker) built with WebAssembly
SIMD, so the page keeps responding. The page prepares each page and sends
its image to an idle worker, which runs ocrs and sends the lines back.
Where no worker can start, the page reads with the installed engine.
Answers go back to the run that sent the page (ocr_route), so a single
run and a batch can overlap.

Recognize Text in Multiple Files on the web keeps main's skipped-files
bucket, low-confidence notes and cancel between files. Files chosen
before the models are in wait for them. A scan Edit finds while no engine
can read it waits for the models instead of counting as no scan.

Trunk builds the worker beside the app; `cargo xtask web-worker-link`
hashes its files and names it in index.html. The web zip ships the
models in models/.
2026-11-07 21:35:50 -06:00

102 lines
4.4 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 license_check;
mod macos_integration;
mod netcheck;
mod parity;
mod screenshots;
mod version;
mod web;
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, in one call", gates::wasm),
("deny", "Dependency licences, bans, sources and advisories (deny.toml; needs cargo-deny)", gates::deny),
(
"ci",
"fmt --check, clippy -D warnings, the CLI without default features, test, i18n-report, layers, wasm, license-check, deny, parity (stops at first failure)",
gates::ci,
),
("test", "Workspace tests: cargo nextest (cargo test when it is missing), then doctests", gates::test),
("i18n-report", "Print the strings each interface language lacks (counts and a few keys); never fails", gates::i18n_report),
("license-check", "Offline source licence documents, attribution and metadata", license_check::run),
("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),
(
"netcheck",
"Record which network-capable crates the batch crates can reach in Cargo.lock, and fail on any reachable pair not in xtask/baselines/network-crates.json",
netcheck::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),
(
"web-worker-link",
"Hash the web build's OCR worker files and name the worker in index.html (a Trunk post-build hook runs it)",
web::worker_link,
),
];
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");
}