Skip to content

fix(pdf): reach deterministic output from the multi-section path, harden the /ID#364

Merged
DemchaAV merged 1 commit into
2.0-devfrom
fix/deterministic-review-findings
Jul 11, 2026
Merged

fix(pdf): reach deterministic output from the multi-section path, harden the /ID#364
DemchaAV merged 1 commit into
2.0-devfrom
fix/deterministic-review-findings

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

Review follow-ups to the reproducible-output option (#363):

  1. MultiSectionDocument builds its own default backend internally, so a user could not
    pass PdfFixedLayoutBackend.builder().deterministic(true).build() to the section path —
    the internal wiring existed, but the public GraphCompose.documents() surface had no way
    to reach it.
  2. The /ID seed covered only five info fields joined with spaces: keywords and custom
    metadata did not participate, and shifted field boundaries collided
    (title="A B", author="" vs title="A", author="B").
  3. deterministic(Instant) kept sub-second precision: PDF dates serialize to whole seconds,
    so a sub-second instant pinned a second-precision CreationDate while the /ID was derived
    from the full-precision value.

What

  • MultiSectionDocument.toPdfBytes(FixedLayoutRenderer) and
    writePdf(FixedLayoutRenderer, OutputStream) — the multi-section counterpart of
    DocumentSession.render(backend); the existing no-arg methods delegate to them with the
    built-in default, so current callers are unchanged. PDF-specific configuration stays out of
    the core API (the parameter is the existing FixedLayoutRenderer seam).
  • PdfDeterminismWriter.documentId now canonicalizes the complete info dictionary —
    every entry participates, as sorted, length-prefixed key/value frames — hashed with
    SHA-256, truncated to the 16 bytes a PDF /ID carries.
  • Builder.deterministic(Instant) truncates to whole seconds up front, so the serialized
    dates and the /ID derive from the same value.

Tests

  • PdfDeterministicOutputTest (6): + multi-section byte-identity through the public
    GraphCompose.documents() path, + sub-second instant renders byte-identically to its
    whole-second truncation.
  • PdfDeterminismWriterTest (6): + field-boundary collision stays distinct, + keywords and
    custom metadata fields participate in the /ID.
  • ./mvnw -f aggregator/pom.xml verify -pl :graph-compose-qa -am — full compile + javadoc
    gate + the qa visual/parity suite (643): green.

The /ID scheme change is safe pre-release: deterministic(...) is @Beta and unpublished.

…den the /ID

Three follow-ups to the deterministic() backend option:

- MultiSectionDocument hard-wired its own default backend, so a configured
  PdfFixedLayoutBackend could not reach the section path through the public
  API. Add toPdfBytes(FixedLayoutRenderer) and
  writePdf(FixedLayoutRenderer, OutputStream) overloads — the multi-section
  counterpart of DocumentSession.render(backend); the no-arg methods delegate
  to them with the built-in default.
- The /ID seed only covered five info fields joined with spaces, so keywords
  and custom metadata were ignored and shifted field boundaries could collide
  ("A B"+"" vs "A"+"B"). Derive it from the complete info dictionary as
  sorted, length-prefixed key/value frames hashed with SHA-256 (first 16
  bytes).
- deterministic(Instant) now truncates to whole seconds up front — PDF dates
  carry second precision, so a sub-second instant previously pinned a
  second-precision date while the /ID used the full-precision value.

New coverage: multi-section byte-identity through GraphCompose.documents(),
sub-second truncation equivalence, field-boundary collision and
keywords/custom-field participation in the /ID.
@DemchaAV DemchaAV merged commit 4de541c into 2.0-dev Jul 11, 2026
12 checks passed
@DemchaAV DemchaAV deleted the fix/deterministic-review-findings branch July 11, 2026 10:14
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