Skip to content

fix(docx): name what of a paragraph's pull up nothing gives back - #940

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-pull-below-zero
Oct 22, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-pull-below-zero

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Why

Word has no space above a paragraph below zero. When a paragraph's, a page reference's or a rule's top
edge is below zero, standsIntoTheSpaceAbove takes it:

  • out of the space owed above it;
  • for a paragraph, also off the foot of the one line written above it (takeFromTheLineAbove) and off
    the top of its own line (riseInsideItsOwnLine).

What none of them gave was dropped with nothing in the report. The field ledger held it as a known gap.

The corpus has a top below zero in 10 documents. Line-by-line drift between the engine's PDF and Word's
PDF shows one visible loss: TealPulse's 4 contact pairs. Each is pulled 4.32pt up in a centred row, a
2.16pt lift on the page, and its line holds an icon, so it cannot rise inside it. In Word the pairs stood
1.2pt lower. The other 9 documents' lines stand within 0.75pt of the page's around those blocks.

Writing the pull as space below the pairs was tried, since that shifts a centred paragraph as much. The
pairs rose 0.8pt, but the row grew past the page's and the page under it stood 2.2 to 2.8pt lower in
Word. So the remainder is named, not written.

The corpus's one bottom below zero, Luma's closing banner, is the last block of its document. Nothing
follows it, and Word draws the same number of pages, so nothing is lost there.

What changed

  • DocxSemanticBackend.standsIntoTheSpaceAbove names what is left of the block's own pull, under the
    subject space above (nameAPullNotGivenBack):
    • It is worked out after every gift: min(−edge, −(carried + pending)).

      • An edge of the container round the block, carried down, is the container's.
      • A border below the block above is named apart, by the existing tail note.
      • The steps that take the pull are unchanged, so the DOCX is too.
    • In the flow, the existing wording: "it stands Xpt lower than the page sets it, and what follows
      with it: the page sets it Xpt over the block above it, and Word sets no block over another".

    • In a table cell: "…it stands up to that much lower than the page sets it, and where its cell
      sets its row's height, the row is that much taller". The page sets the cell's content that much
      shorter, and how far that moves the block depends on the cell's alignment and on which cell sets
      the row.

    • Not named (spaceAboveWrittenElsewhere), since the space Word writes there is reckoned
      elsewhere:

      • the first block of a band's layer or of a row of columns' later layer, which resumes at the page's
        distance from what is above it;
      • a block opening a page in the flow: a section's first, one the layout moves to a new page, or one
        after a page break;
      • a side of an overlay's line pair, which takes its pull out of the space above alone
        (writeLinePair).

      The ledger keeps those as a narrowed gap, PULL_UNRECKONED.

  • Ledger: ParagraphNode, PageReferenceNode and LineNode (as a rule) margin and padding move
    from the broad TOP_PULL gap to the narrowed PULL_UNRECKONED one, with the rest reported
    (PULLED_UP). TOP_PULL stays a gap for the other blocks. The value ledger's DocumentInsets.top
    adds the narrowed gap and the named rest to its TOP_PULL gap.
  • DocxPanelTailTest.aParagraphPulledUpStandsBelowNoBorder keeps its point: a pull is no panel's
    border. It no longer expects no space above note at all. It now pins the two pulls nothing gives
    back: the flow one, and the cell one at 4pt, neither naming a border.
  • Docs: the recipe's paragraph on a negative top edge, the capability matrix's paragraph row, and
    the CHANGELOG (Public API).

Verification

  • DocxTopEdgePullTest (new, 14 tests). The paragraphs run to two lines or more, so no line gives
    anything unless a test means it to.
    • Named:
      • a paragraph pulled 6pt up out of a 2pt gap: 4pt, in the flow;
      • a rule pulled 3pt up: 3pt;
      • a paragraph opening its centred row's cell: 4pt, in the cell's wording;
      • the same in a row opening a page after a page break, and in a panel kept whole that moves to the
        next page;
      • a paragraph in a section pulled 3pt more: its own 2pt only;
      • 10pt up out of a 6pt gap under a stroked card: 4pt, the border not counted.
    • Not named:
      • a pull the space above gives back;
      • one the one line above gives back;
      • one a one-line paragraph rises inside its own line by;
      • a band's later layer;
      • the document's first block;
      • a block after a page break;
      • an edge at zero or above.
  • Fail-closed: eleven sabotages, each run alone and each caught:
    • the note never added;
    • what the line above gives not counted;
    • what its own line gives not counted;
    • a container's edge named on its block;
    • a cell's block named as in the flow;
    • the threshold raised to 5pt;
    • a border below the block above counted in;
    • a band's resumed layer reckoned;
    • the document's first block reckoned;
    • a block after a page break reckoned;
    • a cell on a later page taken for a page opening.
  • Corpus: all 62 DOCX byte-identical before and after. Notes go from 1274 to 1279: the 4 TealPulse
    pairs at 4.32pt, and Obsidian's wordmark at 0.1pt.
  • ./mvnw -B -ntp clean verify -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing,:graph-compose-qa,:graph-compose-coverage -am
    → BUILD SUCCESS (render-docx 2014 tests, qa 1828). The JDK 17 checks pass: the knowledge pack, @Beta
    stability doc, claims and routes, and core javadoc with 0 warnings.

Lane: shared DOCX backend (render-docx report). No public API change; the DOCX bytes do not change.

Word has no space above a paragraph below zero. A paragraph's, a page
reference's or a rule's top edge below zero comes out of the space owed
above it, and a paragraph's off the foot of the one line above it and
the top of its own line; what none of them gave was dropped without a
note.

standsIntoTheSpaceAbove now names the block's own pull left after every
gift (space above), a container's edge carried down and a border below
the block above aside: in the flow the block and what follows stand
that much lower, in a table cell up to that much, its row as much
taller where the cell sets it. The first block of a resumed layer and a
block opening a page in the flow are not reckoned here yet, and stay a
narrowed gap in the ledger. riseInsideItsOwnLine returns what it took.
@DemchaAV
DemchaAV marked this pull request as draft October 22, 2026 09:12
A block after a page break opens its page with nothing above it there:
its pull is no longer named as set over a block above. A side of an
overlay's line pair takes its pull out of the space above alone and is
not named; the ledgers, the recipe, the matrix and the CHANGELOG say so,
and the value ledger's top inset carries the narrowed gap too.
riseInsideItsOwnLine keeps its void shape, the remainder being read
from the space owed.
@DemchaAV
DemchaAV marked this pull request as ready for review October 22, 2026 09:27
@DemchaAV
DemchaAV merged commit 20667b0 into 2.5-dev Oct 22, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-pull-below-zero branch October 22, 2026 10:08
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.

2 participants