Repository navigation
fix(docx): split a body table at the row a page with other margins starts - #952
Merged
Merged
Conversation
…arts - A Word section starts only between blocks, so a page the layout started among a table's rows was named and Word set it with the margins before it: WorkspaceInvoice, its line items run onto three pages, set 58 words of its third page on its second. - The table is written as two at the row the page starts with (DocxTableSplit), the second part opening with copies of its header rows, which the layout repeats on each page, and the hairline between the parts ends the section. Measured in Word and LibreOffice: every page holds the lines the layout sets on it. - Not split, and named on the table as before, where a cell spans rows across that row, only the header rows stand above it, a header row holds a bookmark or a drawing, a header row or a moving row holds a page reference, which the export settles in the paragraph it wrote it in, or the rows above leave no room for the hairline. - A table split at two rows is split at the later one first, and a block named before a written section is named again after it.
DemchaAV
marked this pull request as draft
October 24, 2026 06:15
…s split plainly - A numbered list item in a header row is copied into the second part under the same list, where Word numbers it on from the original: the page's "1." would read "2." there. A table whose header row holds one is not split, as one holding a bookmark or a drawing is not. - The split's note says the table is split there, the part after it opening with its header rows: "written as two" read wrong on a table split twice. - Tests: a split before a page break into a third set of margins ends two sections; a table of a later section of a document of several is split in it; a numbered header row keeps the table whole. - CHANGELOG: the margins in force rather than the page before's, the drift of a table without header rows as measured (0.4 to 0.8pt), what stays the same in MerchantInvoice and PaymentsInvoice, and Obsidian's one note.
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 DOCX export writes a page whose top and bottom margins differ as a Word section, and a Word section starts only between blocks of the body. So a page that the layout started among a table's rows was named, and Word set it with the margins in force.
An invoice's line items are where that happens.
WorkspaceInvoicesets another bottom margin from page 2. With its line items run onto three pages, Word and LibreOffice both set 58 words of its third page on its second.What changed
DocxTableSplit(new, package-private)rowStartingfinds the row a page starts with, from the page the layout placed each row on.splitwrites the rows from that row on as a table of their own, right after the first part. The second part opens with copies of the table's header rows: the layout repeats them at the top of each page, as Word repeats a table's leading header rows. A paragraph goes between the two parts.insertTable). The passes after the section read those lists — the hidden cell closing marks, the one-row table keeps.DocxPageMarginSectionsDocxTableSplitfor the row. The paragraph between the two parts ends the section, as a hairline. It needs room on the page above, as the hairline after a table does.Obsidian's line items sit in a painted panel, which waits on the panel being written in pieces where the layout breaks it.
Verification
DocxTableSplitTest(10 new tests):getTables()andgetBodyElements()in order; a table with no header rows splits into two parts of its own rows;DocxPageMarginSectionsTest(9 new, 2 rewritten):WorkspaceInvoice: 58 words of page 3 set on page 2 before; now every word is on its page, within 0.5pt in both editors.MerchantInvoiceandPaymentsInvoiceare split too. Both editors set every word on the same page as before, the layout's, and at the same height.ObsidianInvoiceis unchanged and named../mvnw -B -ntp clean verify -pl <core, render-pdf, render-docx, render-pptx, templates, testing, qa, coverage> -am→ BUILD SUCCESS: core 823, render-pdf 339, render-docx 2128, testing 5, render-pptx 142, templates 127, qa 1830../mvnw -f examples/pom.xml test→ BUILD SUCCESS, 93 tests.Lane: shared-engine (render-docx). No public API change.