Skip to content

fix(docx): end and turn a drawing's outline as the page strokes it - #901

Merged
DemchaAV merged 1 commit into
2.5-devfrom
fix/docx-drawing-joins-caps
Oct 14, 2026
Merged

DemchaAV merged 1 commit into
2.5-devfrom
fix/docx-drawing-joins-caps

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

A drawn shape's outline was written as <a:ln w> with no cap and no join element. Word 16 and LibreOffice draw that with flat ends and round corners, while the page ends a stroke butt and mitres it unless the node says otherwise. Measured on one-drawing pages, editor ink against the page's:

  • a rectangle's 10pt frame came out with rounded corners;
  • a five-point star's points stood 4pt short, a triangle's apex 13pt short, and a 6pt V's point 5.9 to 25.1pt short as its angle narrows;
  • a path's or a line's round or square caps ended flat, 5pt short at each end of a 10pt stroke.

Only paths said so in the report. Rectangles, polygons and shape-container outlines lost their corners in silence.

What changed

  • DocxDrawings.Shape carries the outline's lineCap and lineJoin, BUTT and MITER where nothing else is given. DocxDrawings.of takes them from a path's and a line's payload (strokedWith). Every other outline keeps the page's own BUTT/MITER: rectangle, rounded rectangle, ellipse, polygon, shape-container outline and side border, none of which the PDF handlers stroke otherwise.
  • outline(Shape) writes them: cap="flat|rnd|sq", then <a:miter lim="1000000"/>, <a:round/> or <a:bevel/> after the fill.
    • The limit is the page's: PDF's default of ten, which render-pdf never changes.
    • An outline with nothing to stroke stays <a:ln><a:noFill/></a:ln>.
    • Badges (holding/inFront) and the drawings a table's cells hold go through the same of and outline.
  • Measured with every variant on the same pages:
    • Word draws each cap and join where the page does, and ignores lim. With lim 2, 5, 8, 10 and 50 alike it mitred ratios up to 9.5 and bevelled 10.5 and over, as the page does.
    • LibreOffice ignores lim too. It writes its own PDF miter limit of 3.86 (30°) and bevels a sharper corner, such as the triangle's 21° apex. That behaviour belongs to the editor; the recipe and the capability matrix say so.
  • The report stops naming what is now written:
    • pathStyleLost names only a gradient paint and a dash pattern; hasOpenEnds and joinsSegments go.
    • A drawn line's "its caps are not written, so it ends flat" goes. A line written as a paragraph border still names its caps.
    • The ledger now has PathNode.lineCap/lineJoin WRITTEN, and LineNode.lineCap REPORTED for a rule only.

Verification

  • Probe pages after the change. The same 17 probe pages (Vees at miter ratios 3–12, bevel and round joins, butt/round/square caps on a path and a line, a rectangle, a triangle, a star) were exported, converted by Word 16 and LibreOffice, and their ink measured against the page.
    • Word: all 17 within 0.12pt of the page.
    • LibreOffice: also within 0.12pt, except the joins sharper than 30°.
  • Corpus. 29 of 62 DOCX change, and only in document.xml. With the new cap attributes and join elements stripped, each is byte-identical to 2.5-dev.
    • 208 outlines: 146 flat, 60 round and 2 square caps; 148 mitred and 60 round joins.
    • Reports go from 1340 notes to 1332. The 8 notes on paths in table cells, which named only their caps, are gone, and 39 other path notes lose their caps clause.
  • LibreOffice fidelity. The run on Windows (-Dgraphcompose.docxFidelity=libreoffice) measured the same lines as libreoffice-windows.tsv, byte for byte. A line's position does not depend on an outline.
  • Committed preview. assets/readme/examples/word-export-companion.docx is re-rendered: its one outline gains its cap and join, so CommittedAssetDriftTest passes.
  • Tests and sabotage.
    • New tests in DocxDrawingsTest assert the written outline exactly for a round/bevel path, a square/round path, a round-capped line, a stroked rectangle, a polygon container and an unstroked ellipse, plus a rectangular badge and a path composed in a table cell.
    • DocxCarriedWithoutReportTest pins the notes that no longer name caps and joins, and the rule's that still does.
    • 11 sabotages, each undoing one part (join, cap, limit, the path's or line's ends, the copy, the notes), are each caught.
  • Gate. ./mvnw -B -ntp clean verify → BUILD SUCCESS (qa 1820).

Lane: render-docx — DrawingML outline of drawn shapes; report notes follow

A drawn shape's outline was written as <a:ln w> with no cap and no join,
which Word 16 and LibreOffice draw with flat ends and round corners, where
the page ends a stroke butt and mitres it: a rectangle's 10pt frame came
out with rounded corners, a star's points 4pt short, a triangle's apex
13pt short, and a path's or a line's round or square caps flat.

DocxDrawings.Shape now carries the outline's cap and join, taken from a
path's and a line's payload and the page's butt and mitre elsewhere, and
the outline states them: cap="flat|rnd|sq", then <a:miter lim="1000000"/>
(the page's limit of ten), <a:round/> or <a:bevel/>. Word draws each as
the page does; LibreOffice reads no limit and bevels a corner sharper
than 30 degrees.

A drawn path's and a drawn line's report notes stop naming caps and
joins; a line written as a paragraph border still names its caps.

The Word export companion preview is re-rendered: its one outline gains
its cap and join.
@DemchaAV
DemchaAV merged commit 5c4e4d8 into 2.5-dev Oct 14, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-drawing-joins-caps branch October 14, 2026 20:21
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