Skip to content

fix(pdf): read a literal string with nested parentheses - #711

Merged
andiwand merged 1 commit into
mainfrom
pdf-nested-parentheses-string
Aug 20, 2026
Merged

fix(pdf): read a literal string with nested parentheses#711
andiwand merged 1 commit into
mainfrom
pdf-nested-parentheses-string

Conversation

@andiwand

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

A public pdf printed from Firefox (https://opendocument.app/, cairo 1.18.4)
failed to render at all — map::at: key not found, thrown where
parse_document_impl reaches for a /Root that recovery never found.

The cause is one line of ISO 32000-1 7.3.4.2: balanced pairs of parentheses
within a string require no special treatment
. read_string ended the string at
the first ), so (cairo 1.18.4 (https://cairographics.org)) stopped mid-value
and the rest of the dictionary parsed as garbage. That dictionary is the /Info
of an object stream whose members include the document catalog — so the parse of
the catalog's own object stream threw, the xref recovery scan hit the same wall
looking for /Type /Catalog, and the retry ran into a trailer with no /Root.

read_string now tracks nesting depth and returns only on the ) closing the
outermost pair. An escaped parenthesis goes through the escape branch and never
touches the depth.

Also here: ViewStream gets the seeking its comment said it did not have.
FileParser::read_object_stream seeks to each member's absolute position, and
std::streambuf's default seekoff fails, so the seek was silently a no-op and
the members were read in order instead. That happens to be right for every file
we have, and wrong for any object stream whose members are not laid out as its
header lists them — nothing in the spec requires them to be.

What changes for a reader

Beyond the new file rendering, three pdfs in the private set get their document
metadata back. It was all-or-nothing (pdf/AGENTS.md), and a nested-parenthesis
producer string had been throwing the whole thing away:

  • basic_text.pdf, test_fail.pdfpdfTeX-1.40.14, whose /PTEX.Fullbanner
    nests parentheses
  • text_visibility.pdfcairo 1.13.1 (http://cairographics.org)

Tests

  • PdfObjectParser.standard_string_balanced_parentheses and ViewStream.seek
    / ViewStream.seek_out_of_range — new unit tests
  • pdf/opendocument-app-website.pdf added to the public input set, with its
    reference output; the three private meta.json files regenerated
  • Full suite green (942 passed, 8 skipped)

ISO 32000-1 7.3.4.2 lets a balanced pair of parentheses stand unescaped
inside a literal string, so the reader now tracks nesting depth and ends
the string only on the `)` closing the outermost pair. `cairo` and
`pdfTeX` write their `/Producer` and `/Creator` that way; those files
lost their document metadata to the best-effort catch in `pdf_file.cpp`,
and a catalog reached through such a string made the whole render fail.

Also give `ViewStream` the seeking it was documented not to have:
`FileParser::read_object_stream` addresses each member by absolute
position, and a silently failing `seekg` left it reading them in order —
correct only while the members are laid out as the header lists them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDs5aK3ZGSZsEvqUUwBBXU
@andiwand
andiwand force-pushed the pdf-nested-parentheses-string branch from acf8f36 to 39f2a0d Compare August 20, 2026 08:27
@andiwand
andiwand enabled auto-merge (squash) August 20, 2026 08:28
@andiwand
andiwand merged commit 6e6699c into main Aug 20, 2026
36 checks passed
@andiwand
andiwand deleted the pdf-nested-parentheses-string branch August 20, 2026 08:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant