Skip to content

fix(docx): bookmark a layer stack's column, so a page reference to it is a field - #967

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-bookmark-a-stack-column
Oct 26, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-bookmark-a-stack-column

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Why

A layer stack whose layers stand side by side — a two-column CV, a sidebar beside a body — is written to Word as a one-row table, each layer's content in its column's cell (writeLayerColumns). The layer itself is never written as a block, so its anchor got no bookmark:

Measured in Word 16 (fields updated, as F9 on the whole text) and LibreOffice 24.2, on an anchored column on page 2 and on the same file with a page put in ahead of it, so a live field must move to 3: both editors read 2 in both files and carried no link, for every column shape tried.

What changed

  • writeLayerColumns puts the anchor's bookmark round what each layer writes, through the same bookmarkAround a section or container in the flow uses: from the first paragraph it writes — a table's first cell where it opens with one — to the end of its last. A later layer of a column is marked from its own first paragraph, the paragraph closing a table above included where it writes its first line there.
  • A layer's block written in an earlier layer's stand-in — a subtitle drawn in a later layer for the reading order, which DocxLayerColumns.Moves writes in the strip above it — is written before the rest of its layer. writeInPlaceOf now records what each such block writes (WrittenInAStandIn), taking its place in the record before it is written, so a stack inside the block that writes in its own stand-ins is recorded after it, in the file's order. The layer's bookmark opens at that block. Only what earlier layers wrote counts: a stack inside the layer writing in its own stand-ins is inside what the layer writes.
  • writtenBy is the piece of writePlacedNode that collects what a block wrote in the body or the cell being filled, now used by writePlacedNode, the column writer and writeInPlaceOf; markOrName, its marking tail, is shared by the first two. The column drops a blank-anchor check that could never apply.
  • Page references and links need nothing new: settleThePageReferences already names each field after the bookmark its anchor was written under, and a link already names the bookmark by its anchor.
  • A column that writes no paragraph — drawings alone — has no bookmark still. It is named as a block that writes no paragraph (nameTheBlocksNoBookmarkMarks), in its own column note where it has one, and a page reference to it stays its number, as before.
  • The note "written as a column of its layer stack; its anchor has no bookmark…" is gone, with the matching phrases in the node and value ledgers.
  • Docs: the recipe's links and anchors row and its drawing limits, the capability matrix's internal links row, the CHANGELOG.

Measured

Each case on page 2 of a 400×500pt page, then the same DOCX with a page put in at its start (the stored field still reads 2). Each cell: what the reference reads in the file as written / in the shifted one, and where the link goes.

Case Word 16, before Word 16, after LibreOffice 24.2, before LibreOffice 24.2, after
left column, right column, container column 2 / 2, no link 2 / 3, link to the page 2 / 2, no link 2 / 3, link to the page
later layer of a column, column opening with a table 2 / 2, no link 2 / 3, link 2 / 2, no link 2 / 3, link
column of a stack in a panel 2 / 2, no link 2 / 3, link 2 / 2, no link 2 / 3, link
table of contents' entry to a column 2 / 2, no link 2 / 3, link 2 / 2, no link 2 / 3, link
layer whose subtitle is written in the strip above, with and without a body line — 2 / 3, link — 2 / 3, link
  • In the last case the bookmark opens at the subtitle in the strip's panel cell. With a body line it closes at that line, after the panel, and Word widens such a bookmark to the strip's whole row, so it starts at the name, where the layer starts on the page; without one it closes at the subtitle.
  • A stack composed in a table's cell is written as a band, not as columns, and was already bookmarked: 2 / 3 before and after.
  • No document of the template corpus, and no template, declares an anchor on anything. The corpus holds no bookmark, page reference or internal link.

Verification

  • New DocxLayerColumnBookmarkTest (16 tests):
    • a left section and a right container column are each bookmarked from their cell's first paragraph to its last, in their own cell, with no note about their anchor;
    • a link and a page reference to a column name its bookmark (PAGEREF left \h reading 2); so does a table of contents' entry;
    • a later layer of a column is marked from its own first paragraph, after text and after a table;
    • a column opening with a table is marked from its own table's first cell, inside the column's cell; one ending with a table is marked to the cell's closing paragraph; a column of a stack in a panel is marked in the columns' table inside the panel's cell;
    • a layer whose subtitle an earlier layer's stand-in holds is marked from the subtitle, in the strip, to its body line, or to the subtitle alone where that is all it writes, with a live reference;
    • a column holding a stack whose own subtitle moves into that stack's strip is marked from the column's own first line;
    • a layer whose block in the stand-in is a strip of its own, writing its subtitle in its own strip, is marked from where that block starts;
    • a column anchored a. whose first paragraph is anchored a gets a_2, and the reference reads that;
    • an unanchored column writes no bookmark;
    • a column of drawings alone has no bookmark, its reference reads 1 as text with its note, and the column is named — in its own column note where it has one.
  • Changed: DocxPageReferenceTest (a reference to a column is a field), DocxCarriedWithoutReportTest (no note for an anchored column), the node and value ledgers.
  • Sabotages: 9 sabotages each undo one part of the change — the column neither marked nor named, what it wrote read off the body instead of its cell, the paragraph closing a table above not counted, only a column's first layer marked, what an earlier layer's stand-in holds left out, a nested stack's own stand-ins counted as written before the column, a block in a stand-in recorded only once written, after what it holds, a block that writes nothing left unnamed or named in a note of its own — and every one fails a test.
  • Template corpus (67 DOCX) against 2.5-dev: all 67 byte-identical; report notes 1491 → 1491.
  • clean verify of the reactor as CI runs it → BUILD SUCCESS (render-docx 2272, qa 1835); the four knowledge checks and core javadoc (0 warnings) pass; examples 93.

Lane: shared-engine (render-docx semantic DOCX backend), no public API change.

… is a field

A layer stack written as columns writes each layer's content into its
column's cell without dispatching the layer as a block, so a section's or
container's anchor there got no bookmark: a link to it named a bookmark the
file did not hold, and a page reference to it was written as a fixed number.

writeLayerColumns now puts the anchor's bookmark round what each layer
writes, from its first paragraph to its last, as writePlacedNode does for a
block in the flow. A block of the layer written in an earlier layer's
stand-in is recorded by writeInPlaceOf and opens the bookmark there. A
column that writes no paragraph is named as such a block is, and a page
reference to it stays its number.

Measured in Word 16 (fields updated) and LibreOffice 24.2: a reference to a
column on page 2 read 2 before and after a page was put in ahead of it, and
the link went nowhere; now both read 3 there and the link lands on the
column's page. The template corpus declares no anchors and is unchanged.
writeInPlaceOf recorded what a block wrote in a stand-in once the block was
written, so a stack inside it that writes in its own stand-ins was recorded
first, and the column's bookmark opened at that stack's subtitle instead of
where the block starts. The block's place in the record is now taken before
it is written, which keeps the record in the file's order.

The recipe's limit on an anchor whose first block moved now names a
container inside the later layer: the layer's own anchor, where the stack is
written as columns, is marked from the moved block.
@DemchaAV
DemchaAV marked this pull request as ready for review October 26, 2026 09:23
@DemchaAV
DemchaAV merged commit daa68f5 into 2.5-dev Oct 26, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-bookmark-a-stack-column branch October 26, 2026 10:16
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.

1 participant