Repository navigation
CheckLinearized: asking whether a file is what its front says
Linearized(b) has been here since v0.10.0 and answers the cheap question a reader asks before streaming: are the parameters at the front well formed? Of Table F.1's claims it checked exactly one against the file — /L against the byte count — because that is all a reader can afford, having fetched 1024 bytes and resolved nothing.
That makes it a weaker test than it looks, and a writer developed against it would pass without linearizing anything: /O, /E, /N and /T were read for type and never consulted, so a file with a correct /L and four invented integers was accepted. That is precisely what a first linearizer emits.
CheckLinearized(b) is the other question, for the other caller: not "may a reader trust the front of this file" but "is this file actually what the front says". It opens the document, so it costs what opening costs, and it is the judgement a writer needs on bytes it has just produced. Every parameter must point at what it claims to:
| checked against | |
|---|---|
/N |
the document's real page count |
/O |
the real first page, and that a reader will treat it as page one |
/H |
a real stream object, its extent matching exactly |
/T |
the main cross-reference section — either of the two meanings Table F.1 gives it |
/E |
the end of the last object the first-page cross-reference section lists |
/E, and why a second producer was needed
/E is the parameter a writer is most likely to get wrong, so leaving it ungated was the hole that let a writer be its own judge. It is now checked against the first-page cross-reference section — the one Annex F puts at the front and startxref points at, whose entries are the first page's objects.
The subtlety is that F.3.1 permits the primary hint stream on either side of the first page. qpdf writes it before (/H 550, /E 902); Ghostscript -dFastWebView writes it after, at /E exactly (/H 829, /E 829). A rule that simply took the highest-offset entry would therefore refuse every Ghostscript file. Both qpdf fixtures place it before and cannot tell the two rules apart, which is why a Ghostscript-produced file is committed beside them.
Values a 32-bit build cannot address are refused, not truncated
int(Integer) discards the high half where int is 32 bits, so a /H length of 2³²+127 read as 127 — the committed fixture's own hint length, a plausible value every later check then accepted. The same bytes were judged sound on one machine and refused on another, with nothing to say which. Every integer the dictionary offers now goes through one conversion that refuses what it cannot represent, /P included. The new error is ErrLinearizationMalformed.
Known limits, stated rather than implied
/Eis checked against the first-page cross-reference section, which is not the same as checking the section is right. A writer that omits objects page one genuinely needs produces an internally consistent file that passes. Ghostscript does this on a document with shared fonts, outlines and links:/H=/E= 1941 is accepted here, whileqpdf --checkcomputes5500..5501and reports page one as 9 objects where the hint table claims 4. Deciding what page one needs means walking its resources — the partition the hint tables describe — and that judgement is still outstanding.- The hint tables' contents are not decoded or compared with the file; only
/H's extent is checked. /Pis range-checked but nothing cross-checks its value.
One deliberate disagreement with qpdf
qpdf computes /E as a range (901..902 on the committed fixture), accepting the end with or without the line ending after endobj. This requires the end including it, which is what both measured producers write. A file carrying the lower value would pass qpdf --check and be refused here; none has been observed, and the alternative is a window that cannot tell a correct /E from one a byte short — the same argument that made the /H length an exact comparison.
Across the rest, there are 0 disagreements with qpdf over 42 generated files: 1, 2, 3, 5, 25 and 100 pages × plain, object streams, uncompressed streams, 256-bit encryption and --newline-before-endstream.
All limits are tracked in #40.