veraPDF run date: 2026-08-22, against the 1.7.5 release build. This small maintenance release changes live annotation rotation behavior, mouse-wheel navigation, shortcuts, and localization, without changing the PDF serializer. The standard open/save pipeline was nevertheless run fresh across the complete corpus because every KillerPDF release must independently meet the same zero-regression bar. The run reproduces every established count exactly: 2,236 successful resaves, 671 refusals matching the SKIP rows one for one, 63 improvements, and the same single documented PDF/A-4 header case as the only flagged saved file. The qpdf sweep also reproduces its table exactly: 2,032 clean both sides, 195 improved, 9 kept preexisting warnings, 0 worsened.
Question under test: does saving a PDF through KillerPDF degrade its standards conformance? Every file in a 2,907-file public corpus was validated, resaved through KillerPDF's standard open/save pipeline, and validated again.
Result: Zero conformance regressions across every file KillerPDF will save, with one documented engine limitation (PDF/A-4's PDF 2.0 header). 63 files came out more conformant than they went in.
| Tool | Version | Role |
|---|---|---|
| veraPDF | 1.30.2 | PDF/A + PDF/UA validation (the industry reference validator) |
| qpdf | 12.3.2 | Structural check (--check exit codes) |
| KillerPDF | 1.7.5 | --batch-resave through the standard open/save pipeline |
| Compare-VeraPDF.ps1 | this folder | Diffs the two veraPDF reports file by file |
| QpdfSweep.ps1 | this folder | Structural before/after sweep (qpdf --check exit codes) |
2,907 PDFs from the public conformance suites: the veraPDF test corpus (PDF/A-1, PDF/A-2, PDF/A-4, PDF/UA-1, PDF/UA-2), the Isartor PDF/A-1b test suite, and the TWG test files. These are deliberately hostile files: most are constructed to violate exactly one clause of a standard, so any structural damage a resave introduces shows up as a new failed rule.
- Validate the pristine corpus:
verapdf --recurse --format json <corpus> > baseline.json - Resave every file through KillerPDF:
KillerPDF.exe --batch-resave <corpus> <resaved> --log resave.csv - Validate the resaved tree the same way into
after.json Compare-VeraPDF.ps1matches files by relative path and flags any file that fails a rule after the resave that it did not fail before- qpdf sweep:
qpdf --checkon original and resave of all 2,236 saved files; flag any file whose exit code worsened
| Outcome | Files |
|---|---|
| Corpus total | 2,907 |
| Resaved OK | 2,236 |
| Skipped (refused, source untouched) | 671 |
| Resave failures | 0 |
| Validation outcome unchanged | 2,172 |
| Improved (noncompliant before, fully compliant after) | 59 |
| Improved (fails fewer rules than before) | 4 |
| Regressed | 1 (the documented PDF/A-4 header case below) |
The 671 skips are encrypted files and files damaged beyond parsing. KillerPDF refuses to
resave what it cannot fully read rather than risk writing a damaged file; each one is a SKIP
row in resave.csv, and all 671 files absent from the after-report cross-check exactly
against those SKIP rows. No file went missing for any other reason.
The 63 improvements are a side effect, not a goal: many corpus files carry deliberately malformed structure (bad trailers, broken xref, wrong stream lengths), and rewriting the file through a clean serializer repairs that class of defect.
ISO 19005-4 (PDF/A-4) is built on PDF 2.0 and requires a %PDF-2.0 header. KillerPDF's write
engine serializes PDF 1.7, so the single PDF/A-4 corpus file gains ISO 19005-4:2020 clause
6.1.3 tests 4 and 5 after a resave. This is a version-marker limitation, not structural
damage: qpdf reports the resaved file clean. PDF 2.0 serialization is future work; KillerPDF
does not claim PDF/A-4 output.
qpdf --check on the original and the resave of all 2,236 saved files:
| Exit code before -> after | Files |
|---|---|
| 0 -> 0 (clean both sides) | 2,032 |
| 3 -> 0 (warnings before, clean after) | 195 |
| 3 -> 3 (kept preexisting warnings) | 9 |
| Worsened | 0 |
No file's structural health got worse; 195 files with qpdf warnings came out clean.
The write engine is PdfSharpCore 1.3.67 (MIT), vendored under third_party/PdfSharpCore/
with six patches, each marked KillerPDF patch in the source:
- No Producer/Creator stamping into an imported document's Info dictionary. PDF/A (ISO 19005-1 clause 6.7.3) requires the Info dictionary to stay equivalent to the XMP metadata; silently rewriting Producer broke that on every save.
- No /ModDate rewrite at open. Same clause: the reader stamped a new modification date into every document the moment it was opened for modification.
- No transparency /Group injected into pages. The writer force-added
/Group << /S /Transparency >>to every page; PDF/A-1 (clause 6.4) forbids transparency. - Stream /Length always matches the spec's byte count (clause 6.1.7), including
zero-length streams, which were serialized with no EOL between
streamandendstream. - Debug verbose file layout removed. Debug builds padded object tokens with extra spacing that violates the object syntax rules (clause 6.1.8).
- Booleans written as the PDF keywords
true/false. .NET'sBoolean.ToString()leaked into indirect boolean objects asTrue, which is not a valid PDF token (ISO 32000-1 clause 7.3.2). This broke/MarkInfo /Markedin PDF/UA files.
On top of the library patches, every save runs three scrubs in KillerPDF itself:
- Dangling /Outlines removal - reading
doc.Outlinesplants an empty outline dictionary that becomes a dangling reference (the 1.6.3 corruption bug). - Degenerate /CropBox removal - reading page boxes planted
[0 0 0 0]boxes that Adobe rejects as out-of-range page dimensions (the other 1.6.3 corruption bug). - Dead signature values stripped - a digital signature's digest must cover the entire
file, so any resave invalidates it. Leaving the stale
/Vand/Perms /DocMDPentries in place fails strict validation; the save now removes the dead values and keeps the empty signature fields.
Everything needed ships in this folder or is a free download (veraPDF, qpdf, the public corpora). On a tree containing the corpus:
verapdf --recurse --format json C:\pdf-corpus > baseline.json
Start-Process -Wait KillerPDF.exe -ArgumentList '--batch-resave','C:\pdf-corpus','C:\pdf-corpus-resaved','--log','resave.csv'
verapdf --recurse --format json C:\pdf-corpus-resaved > after.json
.\Compare-VeraPDF.ps1 -Baseline baseline.json -After after.json `
-BaselineRoot C:\pdf-corpus -AfterRoot C:\pdf-corpus-resaved -CsvOut compare.csv
.\QpdfSweep.ps1 -Corpus C:\pdf-corpus -Resaved C:\pdf-corpus-resaved `
-ResaveLog resave.csv -CsvOut qpdf-results.csv
The resave step must be Start-Process -Wait (or otherwise blocked on): KillerPDF.exe is
a GUI-subsystem binary, so a bare invocation returns immediately and the after-scan then
validates a half-written tree - every not-yet-written file shows up as MISSING_AFTER (this
burned the 1.7.0 run, twice).
The compare script counts every MISSING_AFTER as a regression by design, so a run with skips
exits 1 even when clean. The release bar is: every MISSING_AFTER row cross-checks against a
SKIP row in resave.csv (encrypted/unparseable files KillerPDF refuses to touch), and the
only rule-level change is the documented PDF/A-4 header case. Anything beyond that is a real
regression.