Skip to content

fix(docx): name in the report where a page zone's parts stand and what its paragraphs lose - #863

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

DemchaAV merged 3 commits into
2.5-devfrom
fix/docx-report-zone-losses

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Why

A page zone (session.chrome().zone(...)) is written as one line of a Word header or footer (writeZoneLine).

  • Word sets the line by its own rule. The parts go one after another from the page's left margin. Those after the first spacer stand against its right margin, at the line's one right tab. A part after a second spacer goes to no tab the line holds. Word gives the line one baseline, its tallest part's, and stands the line at the zone's edge.
  • The page sets each part by another rule. It uses the zone's padding, a row's columns and gap, a paragraph's alignment and each part's sides, each part on a baseline of its own.
  • So these moved in Word with no note:
    • a centred footer;
    • a zone padded in from the margins;
    • a row's columns and gap;
    • a page number set in by its sides;
    • a smaller part beside a larger one, set on the larger one's baseline.
  • A zone paragraph lost these too, also with no note:
    • its right-to-left direction (the line is written left to right);
    • its prefix's letters;
    • the size an auto-sized one is fitted to;
    • its outline entry.

DocxNodeFieldLedgerTest listed a page field's align, padding and margin as gaps, and the zones option as a gap for a zone paragraph's alignment, spacing, direction, prefix, fitted size and outline entry, and a row's columns, gap and padding.

What changed

  • reportZoneLine names what the zone's line loses, in a page zone note: "a footer written as one line of Word's footer; …". It runs where the zone is placed, so it fires once per zone in a section, however many of Word's headers or footers the zone is written into.

  • The writer and the report share the line. Both take the parts from zoneParts(content) and the right tab from zoneRightTab(). A test pins the written footer to one right tab at the right margin.

  • The parts that stand off where the page sets them are counted. The phrases:

    • "its text stands off where the page sets it", for a zone of one part;
    • "N of its M parts stand off where the page sets them";
    • "where U of its M parts stand is not measured".
  • How a part is tested.

    • Where the page sets each text part is read from the layout's zone fragments (DocxLayoutMetrics.zoneText), on the first page the zone is drawn on. The export builds the zone's content again, so it finds its nodes by the paths the layout gives a tree compiled on its own (pathsWithin).
    • A part's line start, end and baseline come from the geometry the PDF draws with (ParagraphLineGeometry). The baseline is seated as the PDF seats it (ParagraphSeating).
    • Word's baseline is the tallest part's: its descent above the lowest foot of the parts in a footer, its ascent below the highest head in a header.
    • Word's start for a part on the left side is the left margin plus the widths of the parts before it, at Word's size. Word's end for a part on the right side is the right margin less the widths of the parts after it.
    • A part's width counts only where Word keeps it: one line, with no prefix, at the size the file holds. Past a part whose width it does not keep, and past one the layout does not show, the parts on that side are not measured across the line.
    • A part is counted off where any of these holds:
      • it comes after a second spacer;
      • it is laid out over more than one line, since the zone's line holds none of what keeps a body paragraph's breaks where the page sets them;
      • a prefix stands before it on the left side, the prefix being unwritten (on the right side its end is where Word ends it);
      • its baseline is more than ZONE_PLACE_CLEARANCE (1.5pt) off Word's;
      • its start, or against the right margin its end, is more than 1.5pt off Word's.
    • The clearance is a point and a half because a page field's box is a point wider than its number, and Word sets a size to the half point.
    • Where no part's place is told, the note says "whether its text stands where the page sets it is not measured". That covers no layout, and a zone whose nodes the page names or nests otherwise than the file. The file builds the zone for no page in particular, which reads as the first and the last.
  • A zone paragraph's own losses are named (zoneParagraphLost), each as "a paragraph's …", since a zone holds several:

    • its right-to-left text;
    • its prefix's letters;
    • its fitted size, read from the zone's own lines;
    • its outline entry.
  • autoSizeLost, fittedSize and laysOutAPrefix take the paragraph's lines rather than reading them by the body's paths, so a zone paragraph is measured from the zone's fragments.

    • The body reads a paragraph's lines only where it has an autoSize or a prefix, as before.
    • autoSizeLost takes whose text it names.
  • Ledger.

    • A page field's padding and margin move from a gap to REPORTED: at its sides, and above and below beside other parts. A lone field's room above and below falls in the zones gap.
    • Its align moves to INERT: the page sets a field in a box a point wider than its number, which its alignment moves it no further within.
    • The zones gap narrows to the line's height and the room its parts hold above and below their text, which Word sets its own way, and a zone paragraph's anchor, which gets no bookmark.
    • 1 node-field gap remains (CanvasLayerNode.height).
  • Docs. These say what the note names:

    • the capability matrix's page-zone row;
    • the recipe's page-zone section and its Paragraphs row;
    • render-docx/README.md;
    • the CHANGELOG.

    paragraphLost's Javadoc, the README and the CHANGELOG's paragraph entry no longer say a zone's paragraphs are not named.

Verification

  • ./mvnw -B -ntp install -pl :graph-compose-render-docx → BUILD SUCCESS: 1091 tests, 0 failures, 1 skipped (the property-gated fidelity probe).
  • DocxZoneReportTest is new, with 11 tests.
    • Named:
      • a centred footer;
      • a zone padded in from the left;
      • a header paragraph set in by its margin;
      • a page number set in by its margin;
      • a row's second part at its column, with no spacer;
      • a part after a spacer held off the next by the row's gap;
      • a middle part between two spacers, and the part after the second;
      • smaller parts beside a larger one: last in a footer, first in a footer, first in a header;
      • a smaller part set lower, which stands the line at the footer's foot so both parts move;
      • a part seated off its baseline (verticalAlign);
      • a footer line the page wraps;
      • parts beside an auto-sized one, off its baseline;
      • a zone paragraph's right-to-left text, prefix letters, fitted size and outline entry;
      • not measured:
        • past a prefixed part on the left;
        • before a prefixed part on the right;
        • a zone whose nodes the page nests otherwise;
        • with no layout;
      • a zone written into two of Word's footers, named once.
    • Not named:
      • a line set from the left margin;
      • a page number alone, from the left or aligned right;
      • a row of a notice, a spacer and a page number;
      • a row of a spacer and a page number;
      • parts packed at either end of a row.
    • The written footer holds one right tab at the right margin.
  • The tests fail without the code they cover. 29 sabotages were run, each breaking one thing, and each made the tests that cover it fail:
    • the report call, the writer's tab, the right margin and the missing left margin;
    • the start comparison, the end comparison and the clearance;
    • a second spacer read as the first;
    • the widths of the parts before and after, and the widths Word keeps;
    • Word's baseline: the tallest part, the zone's edge, the seating;
    • the line count;
    • the prefix, and the prefix read on the right side;
    • the not-measured phrases, and a missing part counted as off;
    • each of the paragraph's own phrases, and all of them;
    • reading the zone's lines;
    • the zone's paths;
    • the count's agreement.
  • 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. No corpus document has a page zone, and the report's notes across the corpus stay at 950.
  • Documentation guards:
    • core: -pl :graph-compose-core -Dtest='com.demcha.documentation.**' → 166 tests, 0 failures;
    • qa: the 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

  • Word's widths are the page's lines at Word's half-point size. A page field is measured by the number the page draws on the first page the zone is on, which is what Word's field reads there.
  • The zone is read on the first page it is drawn on. A zone whose text differs from page to page, its nodes the same, is measured against that page's text.
  • Word's baseline is the page's metrics for the tallest part. Word sets the line at its own height, which the zones gap records.
  • Identical losses of two paragraphs are named once.
  • The line's height and the room above and below its parts, and a zone paragraph's anchor, are not named yet; the zones option keeps them as its gap. A lone page field padded above or below stands that far off in Word, unnamed.

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

…t its paragraphs lose

A page zone is written as one Word line: its parts one after another
from the left margin, and those after the first spacer against the right
margin. The page sets each by the zone's padding, a row's columns and gap
and the part's own alignment and sides. A page zone note now counts the
parts that stand off where the page sets them, read from the layout's
zone fragments, says where that is not measured, and names a zone
paragraph's direction, prefix letters, fitted size and outline entry.
The written bytes do not change.
… it with the writer

Word gives a zone's line one baseline, its tallest part's, standing at the
zone's edge: the smaller parts move onto it, so they are the ones counted.
A part known to stand off is counted before its place across the line is
asked, and a prefix on the right side, whose end Word keeps, does not
count. The writer and the report take the parts and the right tab from
one place, and a page field's alignment, which moves it within a point,
is inert. The not-measured phrase names the zone's parts, and a zone
paragraph's own losses are named as a paragraph's.
…s number

The page a zone's fragment is drawn on is on the fragment; parsing it out of the path could overflow. The zone report test sets a row's spacing through the call that is not deprecated.
@DemchaAV
DemchaAV merged commit 4564bfd into 2.5-dev Oct 6, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-report-zone-losses branch October 6, 2026 19:10
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