Skip to content

fix(docx): write a page zone's picture in its line, and stand each part on its own baseline - #872

Open
DemchaAV wants to merge 3 commits into
2.5-devfrom
fix/docx-write-zone-image
Open

DemchaAV wants to merge 3 commits into
2.5-devfrom
fix/docx-write-zone-image

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Why

A page zone is written as one line of a Word header or footer. An ImageNode in it — a header's logo — was not written: warnUnsupportedZoneNode named it DROPPED, page zone content. Word holds a logo in a header as an inline picture on the header's line.

Writing it alone moves the text beside it. Word stands an inline picture on its line's baseline, so the logo, as the line's tallest part, places the line by its foot. The page sets the text beside a logo from the logo's top, or its middle. On the logo's foot that text stood 9 to 18pt low, where before it had stood on the page's baseline.

What changed

  • A zone's picture is written in its line (appendZonePart → writeZonePicture). It is an inline picture at the size the page draws it, through addFittedPicture, which the body's writeImage now shares (its own commit, the body's output unchanged):

    • fitted inside its box where it is contained;
    • the box's size, cropped to it, where it covers it (applyCoverCrop);
    • its link its run's (newRun(para, linkTarget));
    • its description empty rather than POI's file name, through describe, shared with writeInlinePicture.
  • The picture is a part of the line's placement (zonePlacement, pictureOnThePage). It stands on its drawn foot, read off the layout's zone fragment (DocxLayoutMetrics.zonePictures) with the drawn rectangle DocxClipInk.drawn already computes for a contained picture. As the tallest part it places the line by that foot, and the line is tall enough to hold it: four fifths of it above the baseline.

  • In a zone of one line, each one-line part Word sets as the page does stands on its own baseline (ZonePlacement.raised, written in writeZoneLine):

    • raised or lowered from the line's baseline by its runs' w:position, through raiseRunsFrom. That is the loop seatInTheLine already used, now shared, so it also reaches a run inside an internal link;
    • in whole half points: the nearest, or the next one towards the line's baseline where the nearest would pass the line's edge, so a part is set within half a point;
    • the line grows to hold what a raised part reaches above it;
    • below, an exact line holds a fifth of itself, five times what it would grow by. A text is lowered only as far as that, or as far as the tallest part's letters reach; a picture, whose foot is ink, only as far as the line holds. One set lower stands on Word's baseline and is counted, as before;
    • this covers the text beside a logo, a smaller text beside a larger one, a page field, and a picture that is not the tallest part;
    • where the line stops at the page's edge, its tallest part is raised back onto the page's baseline. Only text the page sets past the edge itself stays off it;
    • a zone's paragraph reads none of the lines the body lays the same node out in (writingAZoneLine): it is not seated by them (seatInTheLine), and its pictures are not placed by its body line. A node used in the body and a zone is raised as the zone's line places it alone.
  • reportZoneLine reads a picture as a part:

    • where it starts or ends against Word's place, with its drawn width in the run of widths before the next part;
    • its foot against the baseline Word sets it on, a raised part's own;
    • a lone picture is measured.

    A picture's own losses are named (zonePictureLost): its transform, its outline entry, its anchor's bookmark.

  • DocxZoneParts.readAlike compares a picture's box (samePictureBox): its stated sizes, fit and insets. Where its own proportions set what is drawn — a side left unstated, or a picture contained in its box — it also needs the same picture, the same file or bytes. A zone whose first page draws another box or picture there is written, and where it stands is named not measured.

  • Anything else in a zone — a shape, a barcode, a container — is still DROPPED, page zone content; the message now lists pictures among what a zone is written from.

Verification

  • ./mvnw -B -ntp install -pl :graph-compose-render-docx → BUILD SUCCESS, 1267 tests, 0 failures, 1 skipped.
  • New DocxZonePictureTest (24):
    • a header's logo: its size, its foot where the layout draws it to 0.1pt, the line holding it, no description, no note; a footer's: its foot, no note;
    • a contained logo in a taller box, whose drawn foot stands 8pt above the box's;
    • text beside a logo, the row aligned to the top and to the middle, raised to the page's baseline within half a point;
    • text in the column right after a logo's, starting where Word sets it after the picture's width; split evenly, it is counted;
    • a logo before a page number against the right margin, ending where Word sets it;
    • a page number beside a footer's logo, every one of its field's five runs raised alike;
    • text set 6pt above its logo, growing the line to 37.5pt;
    • a logo shorter than the 30pt text beside it, raised to where the page draws it;
    • a logo drawn 20pt in from the margin, and one padded 4pt: "its picture stands off", the padded one's foot where the layout draws it;
    • a text set 4.3pt below its logo's foot, lowered 4pt, the half point towards the baseline the line holds;
    • a logo set at a 30pt text's foot, past what the line holds below: not lowered, counted;
    • a paragraph — text and a centred picture — used in the body and in a zone, raised as far as one used in the zone alone;
    • contained and covered sizes, the cover cropped;
    • a linked logo inside its hyperlink, to its address;
    • a transform, an anchor and an outline entry named;
    • a logo written into the first page's header and the ordinary one, the text beside it raised alike in each;
    • framed zones — a cover's header and the running one, a cover's footer and the running one — each logo's foot where the page draws it;
    • a logo built at another width, of another picture where its height is its own, or of another picture contained in a stated box, for its first page: written, not measured;
    • one of the same bytes built afresh for each page, its height its own: measured;
    • with no layout, the size it states, on Word's own line.
  • DocxZoneLineTest:
    • a smaller part beside a larger one raised to the page's baseline;
    • an 18pt and an 80pt title against the top edge raised back onto the page's baseline;
    • text the page sets past the edge named;
    • no run raised in a zone of more lines than one, a smaller part after the break among them.
  • DocxZoneReportTest: parts on baselines of their own, a page field among them, no longer counted. A part set lower than the line holds is still counted.
  • DocxReportedLossesTest: a shape in a zone is still page zone content, named once though written into two headers, and per section; a picture is not named.
  • Word 16 and LibreOffice, a 48 by 24pt logo in a 48pt header and footer — alone, beside 8pt text set from its top and its middle, after 18pt text, and beside a page number:
    • each logo stands where the page draws it, to 0.1pt, in both editors;
    • in Word, each text's baseline (span origin) stands within half a point of the page's: 15.96/15.74, 24.00/24.04, 22.92/22.92, 572.52/572.05;
    • before, neither editor showed the logo.
  • 31 sabotages, each reverting a single decision in the change, are each caught by a test. They cover:
    • the picture written, placed, sized, cropped, linked and described;
    • every step of raising a part, and its rounding;
    • what the line holds, a picture's foot apart;
    • a zone's parts kept off the body's lines;
    • how a picture is read and named, and the readAlike comparison, the contained case included.
  • Core doc guards: 166/0. qa doc and DOCX guards, DocxPageZoneTest among them: 50/0.
  • DOCX fidelity corpus: no document has a page zone. All 62 are byte-identical with 2.5-dev, its body pictures written through the shared addFittedPicture included. Report notes: 954, unchanged.

Known limits

  • LibreOffice raises a run about a seventh further than w:position says: 8pt text raised 18pt stood 2.3pt high. It does not raise a page field at all, so a page number beside a taller logo stands on the logo's foot there. It also stands every picture on the line's baseline, so a picture that is not the line's tallest part is not raised there. Word, the reference editor, sets each where the page does.
  • A part lowered further than the line holds below stands on Word's baseline, counted in the note. Growing an exact line five points for each point lowered would carry the zone's line far past its text.
  • One node instance used twice in a zone's row is read once by the report, which then counts both places against one. The page sets it twice, and the note over-counts rather than misses.

Lane: render-docx (DOCX semantic backend) — no public API change.

@DemchaAV
DemchaAV force-pushed the fix/docx-write-zone-image branch from d0fca92 to 7ab2307 Compare October 8, 2026 05:02
@DemchaAV DemchaAV changed the title fix(docx): write a page zone's picture in its line, where the page draws it fix(docx): write a page zone's picture in its line, and stand each part on its own baseline Oct 8, 2026
Base automatically changed from fix/docx-write-row-paint to 2.5-dev October 8, 2026 07:32
…aws it

A page zone is written as one line of a Word header or footer, and an
ImageNode in it was not written: DROPPED, page zone content. It is now an
inline picture in the line, at the size the page draws it - contained in
its box or cropped to cover it - its link its run's.

Word stands an inline picture on its line's baseline, so a picture that is
the line's tallest part places the line by its foot, read off the layout's
zone fragment, and the line is tall enough to hold it.

Each one-line part the page sets on a baseline of its own - the text beside
a logo, set from the logo's top, a smaller text beside a larger one, a page
field - is raised or lowered to it by its runs' w:position, through the run
loop seatInTheLine already used, now shared. The line grows to hold what is
raised; a part is lowered only as far as the fifth of the line below the
baseline holds, and one set lower stays counted. A line stopped at the
page's edge raises its tallest part back onto the page's baseline.

The report reads a picture as a part of the line and names what it loses:
its transform, its outline entry and its anchor's bookmark. readAlike
compares a picture's box, and the picture itself where a side is its own.
The body's writeImage and a page zone's picture fit a picture in its box, crop a covering one to it, and add it to a run the same way: addFittedPicture does it once for both. A zone's picture and an inline picture clear the file name POI describes a picture by through one describe. The body writes what it wrote before.
…keep zone parts off the body's lines

A picture contained in a stated box is drawn as its own proportions fit
it there, so a zone whose first page draws another picture in that box
was read as alike, its line placed by the other picture's foot, and
nothing named. readAlike now needs the same picture for a contained one,
as for one whose side is left to its proportions.

A zone's paragraph no longer reads the lines the body lays the same node
out in: it is not seated by them, its pictures are not placed by them,
and it is raised as the zone's line places it alone. A node used in the
body and in a zone was raised twice, its note gone.

A part's raise is written in whole half points - the nearest, or the
next one towards the line's baseline where the nearest would pass the
line's edge - and checked against what is written. A picture is lowered
only as far as the line holds below its baseline: its foot is ink.
@DemchaAV
DemchaAV force-pushed the fix/docx-write-zone-image branch from 7ab2307 to 359217f Compare October 8, 2026 07:38
@DemchaAV
DemchaAV marked this pull request as ready for review October 8, 2026 07:38

This branch has not been deployed

No deployments
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