feat(docx): export a multi-section document as Word sections - #726
Merged
Merged
Conversation
MultiSectionDocument rendered only to PDF. It gains export(backend), toDocxBytes, writeDocx and buildDocx, and the semantic SPI gains SemanticBackend.exportSections and the SemanticSection record, all @beta. The default exports one section and refuses several. DocxSemanticBackend writes a Word section per section with its own page size, orientation, margins, header and footer. Page numbers restart at 1 and a zone's total is SECTIONPAGES; a section without a zone gets an empty header or footer instead of the inherited one; styles, fonts and bookmark names are shared. A page total's placeholder is the laid-out page count, which LibreOffice shows as it does not update SECTIONPAGES. One section exports byte-identically to its session.
A section that adds no body element - an empty session, or one of nodes the export drops - found the previous section's closing paragraph as its last and overwrote that section's properties, so three sections came out as two. A paragraph that already closes a section is no longer reused; an added carrier is one point tall. An empty header or footer standing in for an inherited one is also one point tall at distance 0, so it cannot push a narrow-margin body down. The header/footer policy is created only when a section has zones, as before. The docs say page-zone header and footer, which is what maps; the missing-layout note names its section.
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
MultiSectionDocumentis how a document gets more than one page geometry: a cover in one size, a body in another, a footer on one part and not the other. It rendered only to PDF (and to PPTX when the sizes match), and had no DOCX export at all. The semantic SPI had nowhere to put one:SemanticBackendcarriedexport(graph, context)for a single document and nothing like the fixed-layoutrenderSections.What changed
Semantic SPI (core, all
@Beta)SemanticSection(graph, context)— one section handed to a semantic backend: its authored graph and the context it would be exported with on its own. The resolved layout is included when the backend asks for one.SemanticBackend.exportSections(List<SemanticSection>)— a default method, so existing backends keep compiling.export.UnsupportedOperationExceptionnaming the backend. Silently concatenating the sections would give the whole document the first section's page, which is exactly what a multi-section document exists to avoid.IllegalArgumentException.MultiSectionDocumentgains:export(SemanticBackend);toDocxBytes(),writeDocx(OutputStream)andbuildDocx(Path), resolved throughSemanticBackendProviderslike the session's own DOCX methods. The export is produced in full before it is written, andbuildDocxgoes throughAtomicFileOutput.DocumentRenderingFacade.semanticSectionbuilds the graph, context and layout once. The single-documentexportand the new package-privateDocumentSession.toSemanticSectionboth use it, so a section gets exactly what the session would get.DocxSemanticBackendStructure.
exportandexportSectionsshare one writer.w:sectPrto its last paragraph, which is where Word ends a section. The bodysectPrbelongs to the last section.DocumentPageZone) describe, through anXWPFHeaderFooterPolicybound to that section'ssectPr. The textDocumentHeaderFooterslots and the watermark are not written, as for a single document.Where Word would do otherwise, it is told what the PDF does.
w:pgNumType w:start="1"on every section, and a page zone's total becomesSECTIONPAGESinstead ofNUMPAGES. The PDF counts each section from its own first page.Shared across the document.
Page-total placeholder. A page total now shows the laid-out page count (
DocxLayoutMetrics.pageCount) before an editor updates the field, instead of1. LibreOffice does not updateSECTIONPAGES: with the old placeholder the body's second page read "2 of 1".What changes for a single document.
exportfor one document goes through the same writer, and one section exports byte-identically to its session (asserted by a test).pageTotal().word-export-companion.docxpreview has no page total and did not move (CommittedAssetDriftTestgreen).sectPr.Docs and knowledge pack
docs/recipes/docx-export.mdhas a new "Several sections in one document" section.n/ato ✅, and its reading note is updated.docs/capabilities.md, therender-docx/README.mdparagraph that said multi-section export runs through PDF only, theapi-stability.mdExperimental entry, and CHANGELOG.extract-api --checkcheck-stability-doccheck-claims --checkcheck-routesVerification
In an editor. A cover (300×400), a landscape body (500×300, footer "Body · page · total", 30 lines) and a back page (300×400) were exported and converted with LibreOffice. Pages:
The body breaks at the same line (18) as the engine's PDF of the same document.
Gate. Full reactor gate (
clean verifyover core, render-pdf/docx/pptx, templates, testing, qa, coverage) → BUILD SUCCESS. Examples after install: 93 green. Corejavadoc:javadocis clean.New tests.
DocxMultiSectionTest(14):pgNumTypestart 1 and aSECTIONPAGEStotal;buildDocxandwriteDocxwrite the same two-section document;SemanticBackendSectionsTest(3):Sabotaging each of these fails the matching test:
Known limit. The same anchor name in two sections writes two bookmarks with one name. The PDF keeps the last one and warns; the DOCX export does not warn yet.
Lane: canonical (semantic export SPI +
MultiSectionDocument) with shared-engine (render-docx). New public API is@Betaand named inapi-stability.md. The only change to existing API is a default method added toSemanticBackend, which is binary- and source-compatible.