BeanNestTools

PDF internals research

Reading PDF Objects with a Byte Scan Instead of the Cross-Reference Table

A PDF has a cross-reference table that is supposed to make object lookup fast. Why would an analyzer ignore it, and what does it find instead?

the BeanNest analyzer script counts PDF object markers by scanning raw bytes instead of using the cross-reference table as its lookup index. The four generated fixtures give that scan a known expected answer, so this guide can explain what the method verifies on the corpus and what it does not establish about damaged or more complex PDFs.

Reviewed 2026-09-20 by BeanNest Studio

The scan looks for object markers in raw bytes

For each fixture, the analyzer records object number, generation, and byte position from the object-header pattern it finds in the file. This path does not consult xref offsets to decide which markers to count.

The controlled corpus gives the scan a known answer

The three unpadded fixtures were generated with five objects and the padded fixture with forty-five. The analysis artifact reports exactly those counts: 5, 5, 5, and 45. Agreement with the constructed fixture is the validation this corpus provides.

Ignoring xref is a design choice, not damaged-file proof

Because the scan does not depend on xref lookup, its object count is independent of the xref table for these fixtures. The corpus contains no deliberately truncated, stale, repaired, or otherwise damaged xref case, so this guide does not claim that such a file is handled correctly.

Raw scanning can overcount bytes inside a stream

A stream payload is arbitrary data and can contain bytes that resemble an object header. The current scan does not first exclude every stream payload while searching, so a coincidental marker could be counted as an object. The generated corpus does not contain that collision.

Incremental-update behavior is also unmeasured here

The four fixtures do not contain incremental revisions with older and newer bodies for the same logical object. A raw byte scan could see more than one body in such a file, so the current object count should not be presented as an authoritative live-object count for an untested incremental document.

What you can see in a file yourself

A classic PDF writes each object as a number, a generation and the word obj, for example 4 0 obj, and closes it with endobj. Near the end of the file the cross-reference table starts with the word xref, and startxref gives the byte offset where that table begins. The four fixtures are all version 1.4 files, so they use this classic layout. Newer files can store the table as a compressed cross-reference stream and keep many objects inside object streams; a raw scan for obj markers sees fewer markers than such a file has logical objects, and the corpus does not measure that case.

The counts in this corpus, side by side

Three fixtures carry five objects and the padded fixture carries forty-five, the difference being the forty extra objects that were added on purpose. The scan reports exactly those numbers, which is what a controlled fixture can prove: the scan finds the structure that was built. It does not prove the scan would agree with a PDF reader on a file that was edited, repaired or produced by another writer.

What this means when you compare PDF sizes

An object count is a clue about structure, not a verdict about size. Forty-five objects explain a larger file than five in this pair, yet a file with few objects and a few large streams can still be much bigger. Use the object count together with the stream share, and say which method produced each number.

A hybrid parser would answer a different question

A stricter future implementation could compare xref-addressed objects with additional raw markers and report disagreements as anomalies. That would combine an indexed logical view with byte-level evidence. The current analyzer deliberately stops at the simpler scan needed for this controlled size corpus.

Scope of this guide

This research explains the byte-level object-location approach used by the BeanNest analyzer script and the reasoning behind it. It is not a general PDF specification guide and does not claim to handle every malformed file.

Method and evidence

We generated four single-page PDFs with BeanNest's own writer, then parsed each file's raw byte structure with BeanNest's own analyzer. Both tools are dependency-free and deterministic, so the same commands reproduce the same numbers on any machine. For this guide we ran the byte-level object scan over all four corpus files and checked that the reported object counts match how the corpus was constructed, which is what demonstrates the scan finds real structure.

Environment: Node.js on a desktop workstation. No PDF library, no network access, and no third-party file was used. The corpus is produced from text BeanNest owns.

Captured: 2026-09-20. Re-review trigger: Re-review when the analyzer's scanning logic changes or when corpus files gain content that could contain literal object markers.

Evidence files: kept in BeanNest's source repository, not published on this site.

  • docs/research-evidence/pdf/analysis-2026-09-20.json
  • docs/research-evidence/pdf/README.md

What this guide does not claim

  • claims that this analyzer replaces a full PDF parser
  • assertions about handling every malformed PDF
  • statements about encrypted or linearised PDF structure
  • security claims about opening untrusted PDFs

Try the tool this guide explains