feat(api): GraphCompose.documents() — multi-section documents in one PDF#238
Merged
Conversation
A document had a single page geometry: one page size, one margin, one numbering scheme for the whole file. Assembling a book — a full-bleed cover of one size in front of a margined, page-numbered body of another — meant rendering separate documents and merging them with external PDFBox, which re-parses each part and is awkward to keep navigable. GraphCompose.documents() concatenates several independent DocumentSession sections — each with its own page size, margins, fonts, and footer numbering — into one PDF inside the engine, with no external merge. Each section's pages are appended at a growing page offset; anchors, internal links, and the bookmark outline resolve across section boundaries against the combined document, so a link on the cover can jump to a chapter in the body. Each section's footer counts from its own first page, so roman front-matter can precede an arabic-numbered body. Document-level metadata and protection are taken from the first section that declares them. Single-section output is unchanged: the existing renderers delegate to a windowed overload with a zero page offset, and the link/anchor/bookmark page indices are rebased only when a section starts past page one. The full suite passes with no changed visual baselines. Tests: MultiSectionDocumentTest renders a cover + body and asserts each section keeps its page size, a cover link resolves to the body's global page, a bookmark and external link in a later section rebase to their global pages, a duplicate anchor resolves to the last section, and each section is numbered from its own first page. Example: MultiSectionExample (landscape cover + portrait, page-numbered body in one PDF).
…he correct page A gradient- or pattern-stroked path, SVG, or text layer in a section after the first registered its shading pattern against the section-local page index of the combined document, so the pattern landed on an earlier section's page resources while the stroke drew on the section's own page. The stroke then referenced a pattern absent from its own page dictionary and dropped from the output. Resolve the page through the render environment's page-offset mapping at that site and at the debug node-label MediaBox lookup, which shared the flaw. Single-section rendering is unaffected (offset zero). Also mark the low-level multi-section assembly seam — PdfFixedLayoutBackend.Section, renderSections, and writeSections — @beta so its shape can still change, and move the example and tests onto the canonical DocumentHeaderFooter / DocumentMetadata APIs instead of the deprecated PDF-options forms. Tests: a gradient-stroked path in a later section registers its pattern on its own page (red before the fix); an internal link whose source is on a third section lands on its global page; a footer can skip its section's own first page; document metadata is taken from the first section that sets it; and close() is idempotent, rejects further rendering, and buildPdf() without a default file throws.
|
|
||
| private static int patternCount(PDDocument doc, int zeroBasedPage) throws IOException { | ||
| int count = 0; | ||
| for (COSName ignored : doc.getPage(zeroBasedPage).getResources().getPatternNames()) { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A document has a single page geometry — one page size, one margin, one numbering scheme for the whole file. Assembling a book (a full-bleed cover of one size in front of a margined, page-numbered body of another) meant rendering separate documents and stitching them with external PDFBox
PDFMergerUtility, which re-parses each part and is awkward to keep navigable. This is the payoff of the book-template groundwork (pageIndex#231,line().fill()#234,columns()#235,addPageReference#236,addTableOfContents#237).What changed
GraphCompose.documents()→MultiSectionDocumentBuilder→MultiSectionDocument. Concatenates several independentDocumentSessionsections — each with its own page size, margins, fonts, and footer numbering — into one PDF inside the engine, no external merge.{page}/{pages}counts from that section's own first page (roman front-matter can precede an arabic body). Document-level metadata and protection are taken from the first section that declares them.PdfRenderEnvironmentgains a page-index offset applied at the four document-relative recording sites (anchor / link source / bookmark / external-link page); the header/footer and watermark renderers gain a windowed overload[base, base+count);PdfFixedLayoutBackendgains thewriteSectionsorchestration over an extractedrenderGraph.Lane: canonical API (
GraphCompose.documents,MultiSectionDocument) + shared-engine (backend assembly, windowed chrome). Purely additive → japicmp-safe.Verification
./mvnw test -pl .— green (1555 tests), zero changed visual baselines → single-section output is byte-identical (the renderers delegate to the windowed overload at offset 0; page indices rebase only when a section starts past page one).MultiSectionDocumentTest(7): each section keeps its page size; a cover link resolves to the body's global page; a bookmark and an external link authored in a later section rebase to their global pages; a duplicate anchor resolves to the last section; each section is numbered from its own first page ("Page 1 of 2", not"Page 2 of 3").MultiSectionExample+ committed preview: a landscape cover (440×300) precedes a portrait, page-numbered body (300×440) in one PDF.Render
Mixed orientation in one document — landscape cover, then portrait body pages footed
1 / 3,2 / 3,3 / 3(section-local), with the cover's call-to-action linking into the body.This removes the cover-plus-body multi-session + external-merge workaround: a real book is now one
DocumentSessionassembly.