Skip to content

fix(pdf): make the object serialization writable - #843

Merged
andiwand merged 1 commit into
mainfrom
feat/pdf-object-serialization
Sep 6, 2026
Merged

fix(pdf): make the object serialization writable#843
andiwand merged 1 commit into
mainfrom
feat/pdf-object-serialization

Conversation

@andiwand

@andiwand andiwand commented Sep 6, 2026

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Stacked on #842 (the design doc) — this is its Phase 0, the serialization correctness the writer needs before anything can be appended to a PDF.

pdf_object.cpp's to_stream already emitted PDF syntax rather than a debug dump, but nothing outside error messages ever consumed it, so four gaps went unnoticed:

before after
StandardString (a(b)c) — the standing // TODO escape (a\(b\)c)
Name /Odd Key /Odd#20Key
dictionary key written raw, unescaped goes through Name
Real {:.4g}612.3, 1e-05 612.345, 0.00001

The real formatting is the one that would have silently corrupted geometry: four significant digits turns 612.345 into 612.3, and g reaches for an exponent form that 7.3.3 has no syntax for at all. It now goes through util::number::to_string_significant, already the no-exponent, no-locale spelling the CSS and SVG writers use.

An unescaped name is not cosmetic either — a space in a dictionary key splits the dictionary in two on the way back in.

Also here

  • Transform2D::inversestd::optional, nullopt on a singular linear part. Undoing the page transform is how a viewport coordinate gets back to user space, which is the whole coordinate path for placing an annotation.
  • Container spacing tidied ([1 2], <</Type /Catalog >>) so writer output is diffable in assertions.

Tests

  • Escaping, real formatting and container output pinned per case.
  • A round-trip through ObjectParser — what to_stream writes, read_object reads back unchanged. That is the assertion that makes the serialization usable for writing rather than only for dumping.
  • inverse against a compound page-like transform, identity, and two singular matrices.

292 PDF tests plus the html/odr/File suites pass; clean under -Werror and clang-tidy.

No consumer-visible change — to_stream only ever reached exception text — so no changelog entry.

Base automatically changed from docs/pdf-annotation-design to main September 6, 2026 14:36
`to_stream` emitted PDF syntax already, but only ever fed error messages, so
four gaps went unnoticed: a literal string escaped nothing (the standing
`TODO`), a name escaped nothing, a dictionary key was written raw, and reals
went through `{:.4g}` — four significant digits, and an exponent form 7.3.3
has no syntax for. `612.345` came back out as `612.3` and `0.00001` as `1e-05`.

Reals now go through `util::number::to_string_significant`, which is already
the no-exponent, no-locale spelling the css and svg writers use. A round-trip
test through `ObjectParser` pins that what we write, we read back.

`Transform2D::inverse` comes along for the same reason: undoing the page
transform is how a viewport coordinate gets back to user space.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018e3PEzyU2oAFSzsEoWsSmz
@andiwand
andiwand force-pushed the feat/pdf-object-serialization branch from c951ac3 to c57b7d4 Compare September 6, 2026 14:42
@andiwand
andiwand merged commit 25baf98 into main Sep 6, 2026
23 checks passed
@andiwand
andiwand deleted the feat/pdf-object-serialization branch September 6, 2026 14:44
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