Measure inline-image payload sizes across a PDF corpus with reproducible JSON results. This research tool was motivated by the pypdf maintainers' request for corpus statistics in pypdf #4196. It does not add or recommend a hard image limit to pypdf.
Python 3.10 or newer:
python -m pip install .
pdf-inline-audit path/to/pdfs --output report.jsonMultiple files or directories are accepted. Directory scanning is recursive and
case-insensitive for .pdf; duplicate resolved input paths are scanned once.
No PDFs, extracted pixels, text, credentials or document metadata are uploaded.
There is no network client in this tool. Review filenames in the report before
sharing it: relative directory names may still identify your documents.
pdf-inline-audit corpus --max-file-mib 50 --max-pages 2000 \
--max-images 10000 --timeout 20 --output report.jsonExit status 0 means every selected PDF was scanned successfully. 1 means at
least one file is partial, skipped, encrypted, errored or timed out; valid results
are still saved. Invalid command-line arguments return 2.
Use a fresh output filename: the tool never replaces an existing output file and rejects an output path that aliases an input PDF.
| Field | Meaning |
|---|---|
parsed_encoded_payload_bytes |
Length of the payload returned by pypdf's inline-image content parser after the containing stream has been decompressed. |
estimated_packed_sample_bytes |
Metadata-derived sample storage estimate using width, height, bits per component and known component counts, including byte padding on each row. |
invocations |
Number of times this definition is encountered through page content and invoked Form XObjects. |
sha256 |
Hash of the input PDF, for identifying the exact corpus version. |
The first field is not an exact original PDF byte span. pypdf's extraction
helpers may discard or retain some delimiter whitespace. The second field is
not measured decoded pixels, not an extracted PNG/JPEG file size, and not
allocated image memory. Device and Indexed color spaces can be resolved directly
or by name in the current page/Form /Resources dictionary. Indexed images use
one component per sample, regardless of the palette's base space. Other custom
spaces (including ICCBased, Separation and DeviceN), missing names, and cyclic
aliases remain explicitly unknown. Lookup is bounded to 32 steps and never
reads image pixels or decodes profiles. See PDF 32000-1, sections 7.8.3 and 8.9.7
(Adobe reference).
DCT payloads are never treated as decoded rasters. PDF 32000-1 sections 7.4.9
and 8.9.7 prohibit JPXDecode and JBIG2Decode for inline images; JPX image XObjects
are outside this tool's inline-image measurements. The tool is not a PDF
conformance validator.
Forms are traversed through actual Do instructions, including nested Forms.
An indirect Form reused three times contributes one definition and three
invocations. A Form's own resources take precedence over inherited page
resources. If reuse under different inherited resources produces different
estimates, the definition's estimate becomes unknown with reason
context_dependent_color_space; invocation counting still includes every use.
Cycles, excessive nesting/invocations, page limits and image limits
produce explicit partial results. Uninvoked resource entries are not scanned.
Top-level content is keyed per page; a content stream shared between pages is
therefore measured per page. This distinction is recorded here for corpus users.
Annotation appearances, tiling patterns and Type3 glyph programs are excluded;
this is not an exhaustive scan of all possible executable streams in a PDF.
The summary includes nearest-rank percentiles and counts strictly above 4096 and
8192 bytes. These are exploratory thresholds, not conformance verdicts or a
proposal for parser policy. Partial-file measurements are included in summaries;
always inspect file_status_counts, per-file errors and coverage before drawing
conclusions.
Each PDF runs in a separate worker with a configurable wall-time limit. This protects the batch from a hung parser; it is not an OS sandbox or a hard memory limit. Only analyze PDFs you are permitted to process in an appropriate environment.
The initial corpus consists of the 71 PDFs under resources/ at upstream pypdf
commit cf2dabb9bd14aebbb04e924d0c0e5737a938b97f (15,340,486 bytes).
The upstream sample-files submodule and remote test fixtures are not included.
PDF fixtures are not redistributed by this project.
A second smoke corpus is the 34 PDFs in
py-pdf/sample-files at commit
818dc013ad1f537198e9fcfae8a6b0dffe25ffa3. Its separate report is in examples/:
32 readable files, one encrypted file and one parser error. Both smoke corpora
contain the same small ReportLab inline-image example (51 parser-retained payload
bytes; 768 estimated packed sample bytes). These are overlapping, test-oriented
samples, not two independent observations or a representative size survey.
They cannot establish a safe size threshold. A broader corpus is still needed.
git clone https://github.com/py-pdf/pypdf.git pypdf-source
git -C pypdf-source checkout cf2dabb9bd14aebbb04e924d0c0e5737a938b97f
pdf-inline-audit pypdf-source/resources --output resources-report.json
git clone https://github.com/py-pdf/sample-files.git sample-files
git -C sample-files checkout 818dc013ad1f537198e9fcfae8a6b0dffe25ffa3
pdf-inline-audit sample-files --output sample-files-report.jsonThe dependency is pinned to pypdf 6.21.1 because the content-parser representation used by this tool is version-sensitive. Reassess the tests before upgrading it.
research/inline-regressions.json records a
separate, deliberately inline-heavy selection of eight public upstream regression
fixtures (11,475,722 downloaded bytes), with original URLs, SHA256 values,
per-file results and reproduction instructions. PDF bytes are not redistributed.
Seven files completed; one deliberately malformed DCT fixture is partial. The collector found 15,453 inline definitions with parsed payloads from 1 to 3,996 bytes. Two DCT payloads of 3,056 and 2,079 bytes have estimated packed sample sizes of 56,394 and 15,183 bytes respectively. Encoded and estimated sample limits therefore answer different questions. The estimates remain metadata-derived.
The historical report was produced by version 0.1.0, with 1,684 unresolved
sample estimates. Version 0.2.0 resolves all 1,684 through named Indexed color
spaces in plan-excerpt.pdf. The comparison in
research/resource-color-spaces.json
uses the same eight PDF hashes, statuses and 15,453 definitions; the encoded
payload distribution is unchanged. Reproduce it with --max-images 20000
(the default 10,000 limit truncates the largest file). No additional independent
sample is claimed.
One document contributes 13,591 definitions and dominates pooled percentiles. The JPX control contains an XObject rather than an inline image. This convenience sample does not justify a universal hard limit or estimate ecosystem prevalence. No upstream review or acceptance is claimed.
research/qpdf-inline-regressions.json
adds three existing QPDF regression PDFs at commit
4eba95899886e851cc41d76886483b347612f2a8. The manifest records pinned source
links, Git blob and SHA256 hashes, and QPDF's Apache-2.0 project license and
notice links. No PDF bytes are redistributed. These are deliberately chosen
upstream test inputs, including a hand-crafted large-image case, rather than a
representative real-world survey. Their hashes do not overlap the earlier eight.
All three files completed: three pages, 11 inline definitions and no unknown sample estimates. Independent runs of published tool version 0.2.0 on Windows CPython 3.12 and 3.13 produced identical reports. One A85+Flate inline payload retains 2,134,990 bytes, compared with 2,430,000 metadata-estimated packed sample bytes. The same file has a DCT payload of 1,841 bytes with an 8,232-byte sample estimate. Neither estimate is a decoded-pixel or memory measurement.
The large retained payload exceeds the earlier eight-file maximum of 3,996 bytes. That earlier maximum cannot be generalized into a parser size limit. The containing stream is compressed, so a parser-retained payload can also exceed the whole PDF's on-disk size. This selection adds scale, named-resource and nested-Form evidence, without adding a new filter family or establishing prevalence, conformance, or a safe threshold. Reports remain separate rather than pooling correlated regression inputs into a purported population survey.
To reproduce, download the three manifest URLs into a directory named
fixtures, verify their recorded hashes, and run:
pdf-inline-audit fixtures --max-images 20000 --timeout 30 --output qpdf-report.jsonTests use only the standard library and the pinned runtime dependency:
python -m unittest discover -s tests -vSynthetic PDF fixtures test parser-backed payload measurements, filter arrays, long-form keys, false-positive text strings, bit-packed rows, image masks, repeated Forms, recursion, indirect named color spaces, Form resource precedence, context-dependent reuse, cyclic aliases, unknown color spaces, corruption, encryption, scan limits, timeouts and the Windows-compatible spawn-based CLI.
The original tool is MIT licensed. pypdf is an external BSD-3-Clause dependency; its source and test PDFs remain under their upstream terms. This project is not affiliated with or endorsed by pypdf or Anthropic.
Implementation and tests were developed with OpenAI Codex assistance. No exact model identifier is recorded here. Test results and the corpus report are local evidence, not upstream review, acceptance, independent adoption, or eligibility for any sponsorship program.
Before sharing a contribution with pypdf, read its AI policy. Its maintainers permit coding assistance, but require contributors to understand and describe their own work, write maintainer communications themselves, and disclose AI assistance. This repository does not automate outreach or PR creation.