Repository navigation
fix(docx): hold an auto column's editor slack in its cells' margins - #944
Merged
Merged
Conversation
- A table column sized to its content gives its text a point of slack so an editor's face keeps the page's line breaks. It widened the column, moving every rule and fill right of it, and the table's right end, a point right of the page's. It now comes out of the column's text cells' margins, on the side their text does not lean on, and the column keeps the page's width. - A cell spanning more than one such column shares its margin among them. A composed cell keeps its margins; what a column's cells do not hold widens it and is named (right end). - An indented table is indented by the margin its first cell keeps, which both editors measure the indent to. - Measured on Windows: Word stood the rules right of the auto column 0.94 to 1.06pt right of the page's, now within 0.12pt; LibreOffice 0.66 to 1.06pt, now with the tables' other rules. No line breaks change in either editor. - Tests: margins by alignment, direction, span, header and thin padding, a composed cell widening its column with the note, an indented table's tblInd; cell padding tests expect the right margin less the slack.
DemchaAV
marked this pull request as draft
October 22, 2026 20:22
…slack - A side of a text cell gives an auto column's slack only past the inner half of the cell's rule, as wide as Word draws it: taken further, a line an editor's face sets wider would run onto the rule. What it cannot give widens the column, named (right end). - Re-render the Word export example's DOCX preview: its quarterly table's three auto columns are a point narrower each, the point in their cells' right margins. - Tests: a 1pt padding beside a 1pt rule gives half a point; a cell spanning two rows gives the point in the merge's covered position; the thin-padding tests are unruled.
DemchaAV
marked this pull request as ready for review
October 22, 2026 20:39
DemchaAV
added a commit
that referenced
this pull request
Oct 28, 2026
A cold pass over the note's numbers against the measurements found three wrong: - 75 commits between #899 and #974; `git rev-list --count` says 74, which is also what the per-commit attribution sums to. - "229 of them standing a point nearer the page": 229 lines moved across, but one of them stands further (`proposal-editorial`'s "Signature:", -0.01 -> +0.11pt), and only the 220 of #944 moved by about a point - the other nine moved 0.02 to 0.76pt. - "Word 16.0.20430 and 16.0.20527 measure this corpus identically": the A/B that shows it was run on the corpus as 6683be9 exported it, 62 documents. Today's corpus is 71 and carries least line heights, per-page margin sections and panel cuts that did not exist then, none of which the older build was ever measured on. The sentence now says which corpus it is about.
DemchaAV
added a commit
that referenced
this pull request
Oct 28, 2026
…975) * test(qa): rewrite the Word fidelity baseline on Word 16.0.20527 The baseline was shot on Word 16.0.20430, and the Word the gate is run with is 16.0.20527. It sets 1388 of the corpus's 5165 lines at another height and 229 at another place across, 1609 rows in all, reaching 0.48pt further from the page against the 0.51pt at which the gate fails a line: the Word gate passed with 0.03pt to spare on cv-serif_headline, cv-panel and invoice-obsidian. Nine documents already carried rows shot on 16.0.20527, so the file held two builds at once. word-windows.tsv and word-windows-lines.tsv now hold what Word 16.0.20527 measures across all 71 documents, under one header. CONTRIBUTING.md says, where it tells a contributor to run the Word gate, that the baseline's header records the build it was shot on and that a failure on another build is re-shot in a change of its own. Nothing else changes: no Java, and no resource the export reads, so the corpus exports byte for byte as before (71 documents, 1499 report notes). The file holds its own control group: the nine documents whose rows were already shot on 16.0.20527 move 0 lines of 883, while the other 62 move 1609 of 4282. The move is Word's 1/600in grid - 1385 of the 1388 lines whose drift down changes move by an exact multiple of 0.12pt, and 227 of the 229 that move across do too. The other five move a whole number of steps plus 0.02 to 0.03pt. All 5165 lines are found in both measurements, none lost and none new; past 0.05pt, 632 stand nearer the page and 553 further, and across all but one of the 229 move nearer, four invoices' figures by about a point. Of the 47 rows that change, 21 stand nearer, 21 further and 5 the same, no median by more than 0.13pt. Acceptance stays at 63 of 71, the same eight documents short of it. Three full runs - export, a fresh Word conversion of all 71 documents, measure - wrote byte-identical measurements, the baseline is that measurement byte for byte, and the third holds a fresh Word run to the rewritten files. * docs(contributing): say why a stale fidelity baseline goes unnoticed The note added with the baseline rewrite said a Word build sets lines a little differently on its own, and that a gate failure on another build is re-shot in a change of its own. Measurement says otherwise: the corpus exported from 6683be9, the commit whose run last took all 62 rows on Word 16.0.20430, and converted by Word 16.0.20527 measures byte-identical to that commit's baseline - 62 document rows and 4282 line rows, 0 changes. The two builds read this corpus the same, so the note pointed a contributor at the wrong cause. What actually leaves a baseline stale is the gate's own shape: it fails a line only once it stands more than 0.5pt further from the page, so a change that moves documents nearer, or less than that further, passes and the rows stay as they were. The note now says that, with what it cost between #899 and #974, and keeps the build in one clause as the thing it rarely is. * docs(contributing): correct three figures in the fidelity gate note A cold pass over the note's numbers against the measurements found three wrong: - 75 commits between #899 and #974; `git rev-list --count` says 74, which is also what the per-commit attribution sums to. - "229 of them standing a point nearer the page": 229 lines moved across, but one of them stands further (`proposal-editorial`'s "Signature:", -0.01 -> +0.11pt), and only the 220 of #944 moved by about a point - the other nine moved 0.02 to 0.76pt. - "Word 16.0.20430 and 16.0.20527 measure this corpus identically": the A/B that shows it was run on the corpus as 6683be9 exported it, 62 documents. Today's corpus is 71 and carries least line heights, per-page margin sections and panel cuts that did not exist then, none of which the older build was ever measured on. The sentence now says which corpus it is about.
DemchaAV
added a commit
that referenced
this pull request
Oct 29, 2026
… a line moved (#976) * test(qa): hold the DOCX fidelity corpus to its baseline whichever way a line moved The fidelity gate failed a document only when it moved away from the page: a line more than 0.5pt further than its baseline held it, a line no longer found, or a page count further from the engine's. Measured on the gate as it stood, everything else passed in silence — a line 1.10pt nearer the page, one 5pt nearer, a page count arriving on the engine's own, a line newly found, a line more drawn on the page, and `+3.00 -> -3.00pt`, which is six points of movement that a rule comparing which of two drifts is further from the page cannot see at all, since both stand three points away. Silence there is not tidiness. A move nearer the page that nothing writes down leaves the further value in the baseline, so a later change may walk the line back to it and spend the 0.5pt on top; held that way, the Word baseline went stale in 1609 of its 5165 rows over 74 commits without one red run, 227 of them standing as much as a point nearer the page across after #944 and #948, until three documents stood 0.48pt further and the next change to touch them would have failed for drift it did not cause. So each measurement is now held as a step from where the baseline puts it: `FidelityBaseline.divergences` fails a line whose drift moves more than 0.5pt, down or across, nearer the page or further; a document the editor sets on another number of pages, or that the engine draws on another number of pages or lines; a document whose set of found lines is not the set the baseline holds; and a line the baseline holds a place across for that is measured without one. Each failure names the step and its difference, so its direction is in the message, and the remedy is in the assertion: a change that moves documents, nearer the page included, rewrites the baseline in the same commit, and that diff is the record. The slack stays one number for both directions, because it is rounding and rounding is the same size whichever way a line rounds; the row aggregates are deliberately held to nothing, since they follow from the lines, which are held one by one. The export does not change — the 71-document corpus exports byte for byte as before — and the Word baseline needs no rewrite: Word 16.0.20527 measures all 71 documents and 5165 line rows exactly as word-windows.tsv holds them, not a row differing. DocxFidelityGateTest covers each rule in both directions, 32 tests from 27, including that a step of exactly half a point is rounding for every drift from 0 to 5pt either way. Thirteen sabotages — every rule removed, and every rule that has a direction made one-sided again — were each caught by those tests. libreoffice-windows.tsv is left as it stands and named as stale instead. Neither LibreOffice baseline has been shot whole since #898, and the local run is red already under the one-sided rule: 2 documents, 8 lines no longer found, 2 page counts and 65 lines more than half a point further, 8 of them proposal-indigo's, 10.25pt lower than it holds them on the LibreOffice build the baseline names. Rewriting it here would bury a 10pt export move in a baseline diff; it wants attributing to the commit that moved it first, as the Word baseline's drift was attributed before #975 rewrote it. * test(qa): shoot the Linux LibreOffice baseline whole, and close the gate's own NaN hole The first run of the two-sided gate went red on the Linux LibreOffice baseline: 8 documents, 230 lines. Measured against the old predicate the same run is green, and of the 230 steps past the slack every one stands nearer the engine's PDF and not one further, with no line lost or gained and no page or line count moved, on the build the file names. Not shot whole since #898 and patched since only where the one-sided gate went red, the baseline was stale on 1778 of its 5149 rows purely in improvements that rule had swallowed. It is re-shot from that run, and its note records the run and the reckoning; the note explaining where cv-teal_pulse's email line stands is carried over, and three notes that only marked which run a document's rows came from are dropped, since a whole re-shoot makes them untrue. libreoffice-windows.tsv is the opposite case and is left alone, marked stale in the file rather than only in CONTRIBUTING: nothing in CI runs it, and it fails even one-sidedly on 2 documents — 8 lines no longer found, 2 page counts, 65 lines more than half a point further, 8 of them proposal-indigo's at 10.25pt on the build it names. A move further from the page wants attributing to the commit that made it before the file is shot again. Three holes the gate had, found by reading it against its own thesis: - A baseline row whose drift or place across was not a number was read, and held its line to nothing: every comparison against NaN is false, so a line 500pt from where the baseline put it produced no divergence. These files are patched by hand, so one mistyped cell was enough. Such a row is now refused, which is the mirror of the rule this change added for a measurement carrying no place across. - A rewrite dropped every hand-written note the file carried, and a rewrite is now the ordinary answer to a document moving. FidelityBaseline.write keeps them under the new header, for the author to prune. - The page's own line count was not held, so a line added to the page with the same ones found passed in silence. It is held beside the pages the engine draws. The drift figures the javadoc, the changelog and CONTRIBUTING quote were wrong and are recounted from the committed rows: of the Word baseline's 1609 stale rows, 1388 moved down the page — 717 nearer it, 635 further, 36 crossing it to stand the same distance on the other side — and 229 moved across, 228 of them nearer by as much as 1.10pt. 228 rows had moved more than the slack, 226 across and 2 down, and no row's distance from the page had grown by more than 0.41pt, which is why a rule reading distances stayed quiet. CONTRIBUTING also gains the two engine-side rules it did not list, and both it and the javadoc now say that the slack is read afresh against each rewrite and so bounds no total. 34 tests from 27, and 17 sabotages — every rule removed, every rule that has a direction made one-sided again, the non-finite refusals removed and note-keeping dropped — each caught by DocxFidelityGateTest. * test(qa): count the drift the gate's own reckoning quotes, and fail closed on both axes The figure the javadoc, the changelog and the pull request quoted for how far the stale rows had grown — 0.41pt — was the largest growth among the rows whose step passed the slack, not among the rows that moved, and it contradicted the next clause of its own sentence. Recounted from the committed rows: no row's distance from the page grew by more than 0.48pt, 21 rows of cv-serif_headline, cv-panel and invoice-obsidian by that much and the rest by 0.36pt or less, and those three documents are the ones that ended 0.48pt further. The argument is the same and now agrees with itself: 0.48 against half a point of slack is why a rule reading distances stayed quiet. Three things the gate still let through: - A measurement carrying no number for its drift down passed every rule, since Math.round(NaN) is 0 and the step against it compares false. The across axis had that guard; the down axis did not, so the mirror the last change claimed was one-sided. Both axes fail closed now. - A baseline row's median and p90 were read unchecked, and a median that is not a number reads as passing every limit it is put to. They are refused as the drift already is. - Keeping a file's notes did not reach the path the contributor is told to take: a measured run writes its own file, which carries no notes, and a run on a machine the baseline cannot be shot on is copied over the baseline from there. write takes the notes from the baseline, so the copy carries them. Where a file's header stands is now its shape rather than a guess. The header cannot be told from a note by its text — both are a # row of prose — so a note written above it could not be kept without dropping the header in its place and leaving the file naming a build it was never shot on. notesOf takes the first line to be the header and refuses a file that does not open with one. CONTRIBUTING no longer says neither LibreOffice baseline has been shot whole since #898, which the Linux one now has, and it, the changelog and the job comment name the engine's two rules in their lists. 37 tests from 27, and 20 sabotages — each rule removed, each rule that has a direction made one-sided, both NaN guards reopened, every non-finite refusal removed, note-keeping dropped from either file and the header's place made a guess — every one caught by DocxFidelityGateTest.
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 table column sized to its content (
DocumentTableColumn.auto()) is exactly as wide as its widest unwrapped cell. An editor setting that cell in its own face can wrap it, so the export gives each auto column's text a point of slack (withEditorSlack). The slack widened the column. Every rule and fill right of it, the text of the columns after it, and the table's right end stood a point right of the page's. The report did not say so.Measured against the page on Windows:
What changed
slackTheMarginsHold,givingTheSlack):leanOf, the cell's anchor read with its direction). Its text keeps the room the slack gave it, so the line breaks are the same. The column keeps the page's width.measureAtWordsSize). With the margin taken, it would run past the cell's edge, where Word cuts it off.ruleTwips). Taken further, a line an editor's face sets wider would run onto the rule: a cell padded 1pt beside a 1pt rule gives half a point.right endnote (applyTableWidth): a table whose auto columns are widened by a tenth of a point or more, in all, is named. Its right end, and what stands right of them, is up to that much further right. It is named whether or not the table paints there, since the text of the columns after a widened one moves with it. In the corpus, these are Orange Ops's aside headings: one-column tables with no padding, a composed heading over a filled accent rule. Word draws their rules 0.98 to 1.05pt longer than the page.applyTableWidthreturns what each column's cells' margins hold, and the row loop writes each cell's padding less its share.indentTable) is indented by its first cell's written left margin, as before, now less what that cell gives. Word and LibreOffice measure the indent to that cell's text. Measured in a section padded 20pt, with the first auto column set right, centred and left: the left rule stood 0.06pt right of the page's in Word and on it in LibreOffice, in all three.assets/readme/examples/word-export-companion.docxis re-rendered.WordExportExample's quarterly table has three auto columns. Its grid and its cells' right margins are a point narrower each, and nothing else in the document changes.DocumentTableColumn.type,DocumentTableColumn.Type.AUTOandTableNode.columnsname the margins and theright endnote;DocumentTableStyle.paddingsays a text cell's padding in an auto column is written less the slack.Verification
DocxEditorSlackTest(+12 table tests, two rewritten):w:tblIndis the inset and the margin its set-right first cell keeps.DocxCellPaddingTest:SLACK). Their vertical margins are unchanged.CommittedAssetDriftTest(examples) passes with the re-rendered DOCX, and failed without it.right end../mvnw -B -ntp clean verify -pl <core, render-pdf, render-docx, render-pptx, templates, testing, qa, coverage> -am→ BUILD SUCCESS: core 821, render-pdf 339, render-docx 2062, testing 5, render-pptx 142, templates 127, qa 1828. The JDK 17 knowledge checks pass, and core javadoc has 0 warnings../mvnw -f examples/pom.xml test→ BUILD SUCCESS, 93 tests.Lane: shared-engine (render-docx): no public API change.