Skip to content

fix(docx): name in the report what a list's items lose - #861

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-report-list-losses
Oct 6, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-report-list-losses

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Why

DocxExportReport promises to name every loss. For a list it named none, while the Word file lost these:

  • Alignment. A centred or right-aligned list is written flush left.

  • lineSpacing. The gap between a wrapped item's lines is written only by the layout's own lines for that item. Where the layout's items are not the list's own — an item run onto the next page is laid out as two, a list composed in a table cell has no fragments of its own — no item gets it.

  • continuationIndent. The page sets it before every wrapped line of a list whose markers are not drawn before them: a hidden marker, or a tree of items flattened into its labels. The file never writes it.

  • The marker column and markerGap. An item at the stated column — 9pt in, 6pt more a level — stands off the page's column. So does a hangingIndent item a space past its marker or two spaces a level in. Every item stands off its column in:

    • a Word list that nests;
    • a list whose gap does not clear its marker;
    • a tree of items;
    • a list the layout did not place.

    A list of paragraphs that nests only markerless rich items is the exception: its items stand where the page sets them.

  • A row of a marker alone. A hangingIndent list draws a blank item whose marker is visible as a row of its own; the export writes no paragraph for a blank item.

DocxNodeFieldLedgerTest listed the five list fields as gaps, and items as written.

What changed

  • writeList reports what its items lose through listLost, as one ListNode note: "written as a Word list", "written as a paragraph per item" or, for a list of blank items, "writes no paragraph", then:
    • "its items are written flush left, where the page sets them centred" / "right-aligned";
    • "its lineSpacing is not written between a wrapped item's lines", where the layout's items are not matched to the list's and one of them wraps. The page lays out a row for each item the export writes and each marker it draws alone; more pieces than that are an item run onto the next page, which wraps even when it was split one line per page;
    • "its continuationIndent is not written before a wrapped item's lines", in a list the page sets it in — a markerless list, or a tree of items without hangingIndent — where an item wraps;
    • "N of its M items stand at a stated column — 9pt in, 6pt more a level — or a space past their marker or two spaces a level in, not where the page sets them";
    • "1 row the page draws as a marker alone, for a blank item, is not written" (or "N rows").
  • Where wrapping cannot be read, the phrase says so. A list composed in a cell, or exported with no layout, cannot be checked for wrapping. Its lineSpacing phrase (in a cell) and its continuationIndent phrase end "whether an item wraps is not measured".
  • The items at their column are counted by the paths that write them, failing closed.
    • writeListItems and writeNestedItem return how many items stand where the page sets them. The note names the rest.
    • Each branch must assign atItsColumn before the item is counted, so a new branch that says nothing does not compile, and an item a path forgets counts as off its column.
    • Which items count as at their column:
      • a Word list's item, at the column the layout measured;
      • an item writeRichListLine set where the page does — tabbed to the layout's column, at the layout's own place for a nested item, or at the edge with no marker — which it now returns;
      • an item written as text, where it holds the letters the page sets: always without hangingIndent, and with it only a top-level item with no marker (standsAsItsLetters).
  • The no-layout note covers lineSpacing. With no layout behind the export, the section's measured geometry note now says "line heights, the space between lines and auto column widths are the editor's". That also covers a paragraph's lineSpacing, which without a layout is put between no lines.
  • Ledger.
    • A list's align, lineSpacing, continuationIndent, hangingIndent and markerGap move from a gap to REPORTED.
    • Its items move from WRITTEN to REPORTED for a blank item drawn as a marker alone.
    • 7 node-field gaps remain.
  • Docs. The recipe's "What a list becomes" and its lineSpacing paragraph, the capability matrix's list row, render-docx/README.md and the CHANGELOG say what the note names. The README no longer says every hanging-indent list loses its markerGap.

Verification

  • ./mvnw -B -ntp install -pl :graph-compose-render-docx → BUILD SUCCESS: 1071 tests, 0 failures, 1 skipped (the property-gated fidelity probe).
  • DocxListReportTest is new, 8 tests.
    • Named:
      • a centred list and a right-aligned list;
      • lineSpacing:
        • with an item run onto the next page;
        • with a two-line item split one line per page;
        • in a list composed in a table cell — the whole note pinned, unmeasured suffix and stated column included;
      • continuationIndent:
        • on a markerless list and on a tree of items whose item wraps;
        • with no layout, unmeasured;
      • items counted off their column:
        • a nesting hangingIndent list (3 of 3);
        • markerGap(0), with a text marker and with a drawn disc;
        • a tree of items;
        • mixed markers with hangingIndent;
        • rich items nesting one with a marker;
      • a blank item drawn as a marker alone, beside written items and alone;
      • with no layout: the list's count and the section note's new wording.
    • Not named:
      • a left-aligned list;
      • lineSpacing:
        • on one page;
        • for a marker drawn alone, which is no item run onto the next page;
      • continuationIndent:
        • where no item wraps;
        • where a bullet stands before the wrapped lines;
        • on a hangingIndent list;
      • a flat hangingIndent list at the page's column, with a text marker and with a disc tabbed to it;
      • a plain bullet list;
      • a name with its description set under it;
      • mixed markers without hangingIndent;
      • a markerless rich item;
      • a list whose items are all blank and draw nothing;
      • a blank item without hangingIndent, or with no marker.
  • The tests fail without the code they cover. Each of these was broken in turn, and each made the tests that cover it fail:
    • the note;
    • the alignment;
    • the matched check;
    • the unmeasured suffix;
    • an unread list counted as one that may wrap;
    • a split item counted as one that wraps;
    • a marker drawn alone counted as a split;
    • which lists set a continuationIndent: the hangingIndent guard, the tree of items;
    • the wrap check;
    • text items:
      • counted on a list without hangingIndent;
      • left uncounted with it;
    • a numbered item at a stated column, flat and nested;
    • a rich item always and never at the page's column;
    • the drawn marker's tab;
    • a markerless rich item;
    • the empty list;
    • the marker-alone rows, and their guards (hangingIndent, a visible marker);
    • the no-layout wording.
  • The DOCX bytes do not change. The 62 corpus documents exported deterministically are byte-identical to the export before the change: DocxFidelityCorpusTest -Dgraphcompose.docxFidelity=export, SHA-256 per file, 0 of 62 differ.
  • Across the corpus the report names one list: SlateOrange's certifications, a bullet list composed in a table cell, written at the stated column.
  • Documentation guards:
    • -pl :graph-compose-core -Dtest='com.demcha.documentation.**' → 166 tests, 0 failures;
    • qa documentation guards plus DocxPageZoneTest, DocxTransparentWrapperTest, TimelineRailAcrossBackendsTest and RtlAcrossBackendsTest → 50 tests, 0 failures.
  • The full reactor gate was not run; no public signature, POM or workflow file changed.

Known limits

  • A stated column is counted without being measured against the page's. A nesting list whose marker and gap come to 9pt exactly would be named, though its items stand where the page sets them.
  • A drawn marker with no image data is counted under "a space past their marker". Its item actually stands at the list's edge.
  • A blank nested item with no marker is written. The page draws nothing for it, and the export writes an empty item. It adds a line in Word and loses nothing, so it is not named.

Lane: render-docx backend (report only, no change to what is written) plus tests and docs.

A list's note now names its alignment (items written flush left), its
lineSpacing where the layout's items are not its own and one wraps, its
continuationIndent where an item of a list the page sets it in wraps, and
how many items stand at a stated column, a space past their marker or two
spaces a level in rather than where the page sets them. With no layout the
section's note names the space between lines. The DOCX bytes do not change.
…ame a marker drawn alone

The writers return how many items stand where the page sets them, each
branch assigning it, so an item a path forgets counts as off its column.
A hangingIndent list's blank item, which the page draws as its marker
alone and the export does not write, is named, and is no longer taken
for an item run onto the next page. A continuationIndent whose lines
cannot be read is named as unmeasured. The README and the recipe's
lineSpacing paragraph say what the list note names.
@DemchaAV
DemchaAV merged commit a7725ce into 2.5-dev Oct 6, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-report-list-losses branch October 6, 2026 14:19
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