04 — Features by importance (F-001…), for a multi-platform, multi-language, web-capable library
Ranking criteria, in order: (1) what blocks credibility (proof, robustness, performance);
(2) what blocks adoption (reachability from every language and platform, drop-in shims);
(3) what nobody else covers (universal standards, national profiles, industry conventions);
(4) what makes a product (viewers, servers, integrations); (5) what makes an institution
(ecosystem, standardisation, licence). Each feature names its spec, oracle and exit
criterion. Status comes from 01-baseline.md; lanes update it.
T0 — Credibility (Phase 0; nothing else holds without it)
| F | Feature | Spec | Oracle | Exit criterion | Status |
|---|---|---|---|---|---|
| F-001 | Text extraction aligned on the majority of three oracles (16 named gaps: Type 3 without ToUnicode, CID CFF ToUnicode, double-painted text, underlay/overlay lines, hyphenation, columns) ; references with wrong generation = null (§7.3.10) ; /Count bounds leaves (§7.7.3.2) |
ISO 32000-2 §9.10, §7.3.10, §7.7.3 | pdftotext, mutool, pdf.js (oracle_pdfjs.rs, external_corpora.rs, oracle_text.rs) |
ratios ≥0.9 rise on all three, 0 regression | ⏸ lane AC (commit a6b7d93 + 4 uncommitted files) |
| F-002 | Document modifier (§6.3.3): page insert/delete/reorder/rotate, merge/split, box edits, annotation add/remove, form fill/flatten, metadata/outline/attachment/page-label edits; incremental by default, full rewrite on demand; unknown content preserved; signatures intact under incremental update | ISO 32000-2 §6.3.3, §7.5.6, §12.7 | qpdf --pages/--rotate/--split-pages/--flatten-annotations, pdfcpu, mutool merge, pdftk, pyHanko validate; qpdf --json before/after on 200 files |
conformance-processors, prepress-boxes implemented |
⏸ lane AE (~/wt/ae: edit.rs ×2, edit_oracle.rs) |
| F-003 | PDF/A: 0 disagreement with veraPDF on its corpora 1b/2b/3b/4/4e/4f + Isartor; real corpus 3b/4 passed; every residual disagreement classified (apdf / veraPDF / broken file) with the veraPDF source line cited | ISO 19005-1..4 (previews), veraPDF profiles + sources | veraPDF 1.30.2, fontTools as third witness on font rules | pdfa-2/3/4 implemented or residuals named |
⏸ lane AF (4 worktrees) |
| F-004 | Performance harness: criterion benches per crate + tests/tools/bench_corpus.sh on ~50 representative files (open/text/render) vs qpdf, pdftotext, mutool, pdftoppm, pdf.js, with wall time and max RSS; profile the worst 20 ratios; optimise every layer (codec, filter, parse/xref, font, content, render, write); targets ≤2× qpdf open, ≤2× pdftotext, ≤3× mutool render, or the gap explained |
— | /usr/bin/time -v, perf, criterion |
docs/PERFORMANCE.md, ADR-benchmarks, before/after per hot spot, oracle tests unchanged |
⏸ lane AG (~/wt-AG scripts, ~/bench-AG) |
| F-005 | Coverage-guided fuzzing: 23 targets (lexer, object, xref, each filter incl. DCT/JBIG2/JPX/Brotli bridges, sfnt/CFF/Type1/CMap, content interpreter, Document::load, standard security handler, XMP XML, XFA, check_a, render page), ASan on parse/filter/font, ≥30 min each, ≤8 workers total; crashes minimised, deduplicated, fixed with deterministic regressions; structural limit tests (nesting, cycles, huge numbers) |
ISO 32000-1 Annex C limits | libFuzzer, cargo-fuzz, UNSAFE-DOCS seeds | docs/FUZZING.md, docs/LIMITES.md (our limits vs Annex C vs qpdf/mupdf/pdf.js), 0 open crash |
⏸ lane AH (~/fuzz-AH seeds) |
| F-006 | FDF/XFDF export/import (fields + annotations), /AF complete (owners: XObject, marked content, DPart, page, annotation, StructElem; CLI exposure; /EP encrypted payload), PDF Declarations read/write, 11 PDF/X Nutshell gaps in check/x |
§12.7.8, XFDF 3.0 + XSD, AN002, PDF Declarations, 15930 previews | pdftk, xmllint, qpdf --json, pdf.js, veraPDF 3b 6.8, Ghent 55/55 | rows fdf, xfdf, pdf-declarations, associated-files, pdfx-* updated |
⏸ lane AI |
| F-007 | Tree hygiene: cargo fmt in one commit when no lane is active; prove all green after; dead worktrees and cargo targets removed; upstream branches merged |
— | prove all |
green gate at the new HEAD | 📋 P0-a/P0-b |
| F-008 | Truncated DCT painted to the decoded prefix (poppler/mupdf do), CFF charstring index built once (perf), streaming inflate for images/object streams | T.81, RFC 1951 | pdftoppm, mutool, CPython zlib | PSNR on bug_603518; memory on huge flate images |
📋 (AG/AC scope) |
T1 — Reachability (Phase 1)
| F | Feature | Spec / tooling | Oracle / proof | Exit criterion |
|---|---|---|---|---|
| F-010 | Stable C ABI apdf-capi: opaque handles, error codes + French messages, library-owned buffers with explicit free, thread-safety contract, semver'd apdf.h via cbindgen, pkg-config file |
cbindgen (ADR-bindings) | fresh C project (cmocka tests) that opens/renders/validates; abi-checker diff between versions |
one ABI for all bindings |
| F-011 | Node & Bun package @asklera/pdf via napi-rs: prebuilt binaries per platform in optionalDependencies, TypeScript types, ESM+CJS, streams for large outputs, async by default (worker threads) |
napi-rs (ADR) | fresh Node 22 and Bun projects; pdf.js-equivalent smoke (pages/text/render/forms) | npm publish dry-run + local registry |
| F-012 | WASM @asklera/pdf-wasm: wasm32 via wasm-bindgen for browsers (Web Workers, HTTP range streaming of linearised files, banded rendering under the 4 GB limit), WASI 0.2 component for Deno/Cloudflare/Fastly/wasmtime; size budget ≤4 MB gzipped without fonts/profiles (loaded on demand) |
wasm-bindgen, wit-bindgen (ADR) | browser smoke page rendering the corpus sample; wasmtime run of the WASI component |
size and parity tables |
| F-013 | Python asklera-pdf (PyO3/maturin): wheels manylinux2014 x86_64/aarch64, macOS universal, Windows; API familiar to pypdf/pikepdf users; NumPy export of bitmaps |
maturin (ADR) | pypdf/pikepdf test snippets ported; pypdfium2-style render check | PyPI-ready wheels consumed by a fresh venv |
| F-014 | JVM (JNI or UniFFI Kotlin; Maven Central), .NET (UniFFI/csbindgen; NuGet), Swift (UniFFI; SwiftPM; iOS/macOS), Kotlin/Android (AAR), Go (cgo), Ruby (magnus), PHP (ext-php-rs), Dart/Flutter (flutter_rust_bridge), Elixir (rustler), C++ header over the C ABI | UniFFI and friends (ADR-bindings) | one fresh project per language | matrix in CI |
| F-015 | Drop-in shims: pdfium FPDF_* subset (document/page/render/text/annot/form) as a shared library with the same symbol names; poppler-glib-shaped API for GNOME/KDE viewers; CLI aliases with compatible flags for pdftotext, pdftoppm, pdfinfo, pdfimages, pdffonts, pdfsig, qpdf (common verbs), pdftk verbs, cpdf parity table; docs/MIGRATION.md |
pdfium public headers, poppler-glib docs | pypdfium2 test suite pointed at our .so; poppler-utils golden outputs on the corpus |
shim passes the upstream smoke tests |
| F-016 | Documentation site (clause-by-clause "MDN of PDF", using our tests as examples), per-crate CHANGELOG, semver policy, CI matrix (Linux glibc/musl, macOS x86/arm, Windows, iOS, Android, wasm32, WASI), reproducible builds | mdBook/Docusaurus (ADR if a crate) | link checker, doctests | site built from HEAD |
| F-017 | System packages: Homebrew tap, AUR, Debian/Ubuntu (apdf-cli), winget, Chocolatey, Docker Hub image, nix flake |
— | install smoke on each | packages published to our own channels (crates.io only after D-1) |
T2 — Differentiation: inspector, linter, forensics (Phase 2)
| F | Feature | Spec | Oracle | Exit criterion |
|---|---|---|---|---|
| F-020 | Inspector: object graph (shared and cyclic references), views — physical (offsets, generations, xref, trailer), logical (catalog → pages → resources → content), tagged structure, content operators aligned to rendered pixels, fonts, colour, security, signatures, DPart, XFA, 3D, /AF; revision history (each incremental update as a commit: added/replaced/freed objects, what each signature covers) |
§7.5, §7.5.6, §12.8 | qpdf --json, pdfresurrect, mutool show, pdfsig | apdf tree, apdf revisions with negative controls |
| F-021 | Multi-standard linter: every ledger row + PDF/A/UA/X/VT/E/raster/Declarations/EA-PDF + industry profiles (eCTD PDF spec, court filing, Factur-X, ZATCA, gov presets), severity, clause cited, oracle proof linked, suggested fix; outputs JSON, SARIF 2.1, HTML, PDF; differential mode (since revision n) | SARIF 2.1.0 (OASIS) | sarif-multitool validate, GitHub code scanning upload, veraPDF/callas as cross-checks |
apdf lint --profile <p> --sarif |
| F-022 | Query language over the graph (apdf query): selection, projection, aggregation, CSV/JSON export; usable for custom lint rules |
jq-like grammar (ours) | qpdf --json equivalence tests | 50 documented queries |
| F-023 | QDF mode (normalised text-editable file, offsets recomputed on rewrite) and apdf json compatible with qpdf --json=2 |
qpdf QDF/JSON docs | qpdf --qdf, fix-qdf |
round-trip byte-identical where qpdf is |
| F-024 | Diff: structural (objects), textual (words), visual (pixels per page, change boxes), revision diff; HTML side-by-side | — | Draftable/pdftk/pdftoppm as witnesses | apdf diff on 100 pairs |
| F-025 | Forensic & sanitisation: JS/actions extracted (never executed), orphan/hidden objects, hidden layers, white/off-page text, attachments, xref/revision inconsistencies, YARA-compatible indicators; apdf scrub (hidden data, metadata, MIP labels awareness) and apdf scrub --rebuild = CDR by construction (regenerate the file from the validated model); hardening report replaying PDFex, shadow attacks, signature wrapping, JBIG2/JPX decoder attacks |
NSA sanitisation guidance, NCSC, pdf-insecurity.org PoCs | peepdf/pdfid witnesses; published PoCs must fail against us | docs/HARDENING.md with each class and its test |
| F-026 | GUI (egui, then WASM): tree + render + linter side by side, object ↔ pixel highlighting, live oracles if installed | — | manual + screenshots + WASM smoke | desktop and web builds |
| F-027 | VS Code extension (inspector + linter), GitHub Action apdf-lint (SARIF upload), pre-commit hook, browser extension (MV3) |
— | Action run on this repo | published to marketplaces we control |
T3 — Creation and trust (Phase 3)
| F | Feature | Spec | Oracle | Exit criterion |
|---|---|---|---|---|
| F-030 | Document builder: shaped text (OpenType shaping — ADR: engine choice), hyphenation (ADR), UAX #14/#29, vertical CJK, ruby, tables, lists, images, vector canvas, SVG → PDF vector, Markdown → PDF, automatic tagging (PDF/UA-2 and PDF/A-4 by default), variable-font instancing, colour-font flattening | 14496-22, UAX #9/#14/#29, MathML | HarfBuzz hb-shape, LuaTeX/pdfTeX output, veraPDF UA-2/A-4, epubcheck for reflow round-trip |
builder output passes UA-2 and A-4 with 0 findings |
| F-031 | E-invoicing hybrids: Factur-X/ZUGFeRD 2.3 (all conformance levels), XRechnung hybrid, ZATCA phase-2 PDF/A-3 + TLV QR, Thai e-Tax PDF/A-3, PDF representations with mandated QR/barcodes (CFDI, DANFE, DIAN, SII PDF417, IRN JWT, ATCUD, KSeF, VERI*FACTU, TicketBAI, myDATA), EN 16931 UBL/CII generation | EN 16931, Factur-X 1.0.07, ZATCA SDK, national specs | Mustang, KoSIT, ZATCA SDK, SAT/SEFAZ/DIAN validators, Peppol Schematron | each profile: generate → validate 100 % with the national validator; negative controls |
| F-032 | C2PA 2.4 manifests in PDF: read, verify (trust list), write with data.hash exclusions compatible with PAdES |
C2PA 2.4 §PDF | c2patool |
round-trip verified by c2patool |
| F-033 | Post-quantum PAdES: ML-DSA (RFC 9882), SLH-DSA, composite ML-DSA+ECDSA/RSA (LAMPS drafts), policy selection (--policy cnsa2|sogis|nist), via RUSSL (measure russl-ml-dsa/russl-slh-dsa availability first) |
RFC 9882, FIPS 204/205, ETSI TR 119 619 (draft) | OpenSSL 3.5, Bouncy Castle, liboqs; pyHanko when it supports it | first PQ/hybrid-signed PDF validated by two independent stacks |
| F-034 | Trust & remote signing: external-signer trait (hash → signature) with adapters PKCS#11, CNG, CryptoTokenKit, SKF (GM/T 0016), cloud KMS (AWS/Azure/GCP/Vault), CSC API 2.2, Aadhaar eSign, SingPass; trust stores EU LOTL/TSL (TS 119 612), AATL, ICP-Brasil, CCA India, JPKI, national lists; DSS as validation oracle; signature policies (PAdES-BR, Part 11 manifestation, e-Apostille, court filing); visible signature designer with verification QR | ETSI EN 319 142/122/102, TS 119 612, CSC 2.2, DOC-ICP-15, 21 CFR 11 | DSS, pyHanko, SoftHSM2, OpenSC/pkcs11-tool, ITI verifier (BR), CSC sandboxes |
each adapter proven against a real or sandbox signer; DSS says VALID |
| F-035 | Forms: designer (AcroForm, static XFA), XFA rendering (SAP ADS/AEM/IRS/cerfa), JavaScript field subset in an optional sandbox (calculate/format/validate, AFDate/AFNumber…), cerfa/IRS/HMRC/Elster pre-fill catalogues | §12.7, XFA 3.3, Acrobat JS API (subset) | pdfium XFA build, Acrobat (manual), pdf.js getFieldObjects |
100 real government forms fill and render |
| F-036 | Smartcard/token layer (generic-first): PC/SC transport, PKCS#15/IAS-ECC/EN 14890 discovery, profile modules PIV/CAC, IAS-ECC (CPS = config), EstEID, BELPIC, DNIe, CNS/CIE, JPKI, OpenPGP card, health-card family (HBA/SMC-B, UZI, TS-CNS, NHS); PKCS#11 universal path first; CNG/CTK/SKF adapters | ISO 7816-4/-15, SP 800-73-5, IAS-ECC 1.0.1, PKCS#11 3.1, GM/T 0016 | OpenSC (pkcs15-tool, pkcs11-tool), SoftHSM2, YubiKey PIV, NIST PIV test cards, vendor test cards |
sign a PAdES with ≥5 card families through the same trait |
| F-037 | TS 32004 write (/AttachedToSig) and /ISO_ 32001/32002 declarations (upstream russl-pades), EA-PDF export/import, PDF/raster read/write, PDF Declarations write, ASiC-E/S containers wrapping PDFs (EN 319 162), XAdES for XML payloads |
TS 32004, EA-PDF 1.0, PDF/raster 1.0, EN 319 162 | pyHanko, EA-PDF validator, libdigidocpp, DSS | rows ts-32004, ea-pdf, pdfraster implemented |
T4 — Verticals and industry conventions (Phase 4)
| F | Feature | Spec | Oracle | Exit criterion |
|---|---|---|---|---|
| F-040 | Health: PDF/A-3 + CDA/FHIR /AF, IHE XDS-SD wrapping, DICOM Encapsulated PDF/CDA wrap/unwrap, national e-health profile configs (FR/DE/AT/CH/BE/NL/IT/ES/UK/US/AU/IN), eCTD PDF profile, 21 CFR Part 11 manifestation |
DICOM PS3.3/3.10, CDA R2, FHIR R5, ICH M2/M8, FDA PDF spec | dcm4che, HL7 validators, IHE Gazelle, FDA validation criteria | profiles pass their official validators |
| F-041 | Prepress: PDF/X preflight all levels (texts pending D-2), imposition, RIP-grade separations bit-exact vs tiffsep, trapping, TAC/ink coverage, spot/CxF, screening, JDF/XJDF read, VDP generator (data → DPart), AFP/XPS → PDF importers, PDF → PCL/PS 📋 |
ISO 15930/12647/16612, CIP4, GWG, ECMA-388, MO:DCA | gs, Ghent, Altona (🧭), callas trial, Compart samples, mupdf xpstopdf | Ghent 55/55, Cal Poly 12/12, imposition vs Preps samples |
| F-042 | Archival: JHOVE/DROID/Tika/VITAM oracles, PDF/A conversion assistant (fonts, colour, transparency, JS, encryption, attachments), evidence records (RFC 4998/6283, TR-ESOR), SIP packaging (SEDA 2.x, E-ARK CSIP, BagIt, METS/PREMIS), national profiles (NF Z42-013/020, TR-03138, GoBD, BS 10008, SInCRO, ENI, 電子帳簿保存法, NOM-151) | as listed | JHOVE, VITAM, E-ARK commons-ip, TR-ESOR suite |
1 M-file batch conversion run reported |
| F-043 | Accessibility: autotag from detected layout, guided remediation in the tree, reading-order simulator, WCAG/EN 301 549/Section 508 mapping + VPAT/ACR generator, PDF → EPUB/HTML reflow, BRF/braille export, DAISY, MathML speech hooks | 14289-2, WCAG 2.2, EN 301 549, ADA Title II, ACR 2.5 | veraPDF, PAC 2024, epubcheck, DAISY Pipeline |
Reference Suite stays 16/16; autotag output passes UA-2 |
| F-044 | Structured extraction: tables, figures + captions, blocks, Markdown/JSON with provenance (page, bbox, structure path), measured on OmniDocBench/DocLayNet/PubTabNet | — | benchmarks, Docling/Marker as baselines | published scores vs baselines |
| F-045 | Legal/defence: Bates, redaction apply with destruction report, e-discovery load files, classification/CUI/STANAG 4774-4778 marking and binding engine, court-filing profiles, e-Apostille, notarial profiles, audit-trail evidence PDFs | as listed | Relativity import, STANAG 4778 samples, HCCH samples | profiles validated on real samples |
| F-046 | MRC compression (segmentation → JBIG2 mask + JPX layers), JBIG2 generic/symbol encoder, size/quality vs Ghostscript/qpdf/LuraTech | T.88, T.800 | jbig2dec/OpenJPEG decode our output; size tables | ≥ LuraTech-class ratios on scan corpora, decodable by all viewers |
| F-047 | Barcodes and visible digital seals: encode (vector) and decode (raster) QR/PDF417/DataMatrix/Aztec/Code128/GS1/EAN; verify 2D-Doc/CEV, ICAO VDS/VDS-NC, EU DCC, SMART Health Cards, Aadhaar QR, ZATCA TLV, IRN JWT, Swiss QR-bill, EPC QR, IATA BCBP, AAMVA PDF417; GeoPDF read/write (GDAL oracle) | ISO/IEC 18004/15438/16022/24778/15417, ICAO 9303-13, ISO/IEC 22376, ANTS 2D-Doc | ZXing/zbar, ANTS test kit, ICAO vectors, GDAL | each profile: 100 % on official test sets |
T5 — Products (Phase 5)
| F | Feature | Oracle / proof |
|---|---|---|
| F-050 | Viewers: WASM (browser: render, search, outlines, annotations, forms, client-side signing via WebCrypto/CSC, progressive loading) and native (egui: reading, reflow, dark mode, measurement/layers for AEC, smartcard signing, OS TTS via reading order) | pdf.js/mupdf.js parity tables; accessibility of the viewer itself (WCAG) |
| F-051 | apdf-server: REST (OpenAPI 3.1), Docker/Kubernetes, queue + webhooks, CMIS/SharePoint/Box connectors; operations: validate, convert to PDF/A, sign, timestamp, merge, extract, render, lint (SARIF) | contract tests; Gotenberg/Stirling parity |
| F-052 | SaaS pdf.asklera.fr (consumer funnel: merge/split/compress/convert/sign/validate) |
uptime + conversion metrics |
| F-053 | OS/desktop hooks: CUPS filters (apdftoraster → PWG raster, apdftopdf), print-to-PDF/A driver, Windows IFilter, QuickLook/Spotlight, thumbnailers, Evince/Okular backends, Office/LibreOffice add-ins, Moodle plugin |
cups-filters tests, Windows Search index check |
| F-054 | OCR (🧭 bind Tesseract under ADR or build) with invisible text layer and MRC pipeline | OCRmyPDF parity |
| F-055 | HTML+CSS paged media subset → PDF on apdf-layout (🧭 D-9) |
WeasyPrint/Prince reference renders |
T6 — Ecosystem and standardisation (Phase 6; start once T0 is green)
| F | Feature | Proof |
|---|---|---|
| F-060 | PDF Observatory: public, reproducible dashboard of measured deviations in poppler, mupdf, pdfium, pdf.js, qpdf, veraPDF, Ghostscript (file, clause, method) — never accusatory, always replayable | published methodology + scripts |
| F-061 | Open conformance suite ("PDF Platform Tests"): our synthetic fixtures and negative controls (never third-party corpora), runnable by any engine; "apdf-verified" badge | suite runs on ≥3 external engines |
| F-062 | Registered developer extension prefix (ASKL) via the ISO 32000 registration authority; extensions published in the PDF Association registry (e.g. PQ signature metadata, Zstandard filter proposal, typed /AF MIME registry) |
registry entries |
| F-063 | Memberships and standardisation: PDF Association, AFNOR mirror committee of ISO/TC 171/SC 2, ETSI ESI liaison, CIP4, HL7 (as needed); errata contributions (Arlington, PRC WD, TS 32005 vs veraPDF, JBIG2 vectors); interop workshops; proposal of a TS on PQ signatures in PDF | submitted issues/tickets, workshop reports |
| F-064 | SafeDocs campaign: 8 M files read without panic, feature-usage statistics published; UNSAFE-DOCS for robustness | report with counts |
| F-065 | Continuous fuzzing infrastructure (OSS-Fuzz-style, internal), "break apdf" programme when distribution allows | dashboard |
| F-066 | 🧭 Distribution model (D-1) | — |
| F-067 | Registries presence: crates.io (after D-1), npm, PyPI, Maven Central, NuGet, SwiftPM, Go, Packagist, pub.dev, Hex, Homebrew, AUR, Debian, winget, Docker Hub | packages resolvable |