Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,41 @@ follow semantic versioning; release dates are ISO 8601.

### Public API

- **A DOCX export's report names a clip where it cuts something, and a turned container's
transform where no outline names it.** A Word file has no clip a container can set round its
layers: a sidebar's ornament set past its side, a square tile's corners in a disc, a label
run past its chip are written whole. The report named a shape container's clip on one path
only, and there of every one, `OVERFLOW_VISIBLE` included; a table's `cell drawing` note said
of every drawing its cells hold that a clip on it is not carried. It named no layer stack's
`clipToBounds`, and no clip of a container written as a badge, a title and its dates, over
the flow or as drawing alone. Now:
- on every path, a `clipped shape container` or `clipped layer stack` note names a clip that
cuts what the node's layers paint, and a `clipped cell content` note on a table names one
composed in its cells;
- what is painted is measured from the layout's fragments as the page paints it, upright as
the file writes it (`DocxClipInk`):
- a fill to its outline, a gradient alone being none the file draws;
- a stroke as `java.awt.BasicStroke` makes it, with its cap, its join and the PDF's miter
limit, and a box's side borders each a line of its own;
- a picture where it is drawn, fitted in its box where it is contained, and cropped to the
ellipse it fills where the file crops it;
- a line of text from its letters' tops to their feet, read from their glyphs' outlines, or
over its whole line where those are not known — always for a standard face the PDF does
not embed, whose outlines are read through a stand-in font in its own units;
- what a clip inside another cuts away is named on that clip, not on the one round it;
- a clip that cuts nothing is not named, and the `cell drawing` note no longer speaks of
clips; with no layout behind the export, every node that clips is named, its cut not
measured;
- the note every shape container's layers were written with is now `shape container`: "its
layers are written inline, one after another in source order", with no claim of a clip;
- a shape container turned by a transform whose outline draws nothing — unpainted, or an
outline no shape shows — names it: what it holds stands upright at its size.

Measured across the DOCX fidelity corpus, the report names one clip: `LumaStudioInvoice`'s
sidebar ornament. None of this changes what is written: the 62 documents of the corpus export
to the same bytes. In `DocxNodeFieldLedgerTest` a layer stack's `clipToBounds` and a shape
container's `clipPolicy` and `transform` move from a gap to `REPORTED`; 12 node-field gaps
remain.
- **A DOCX export's report names what a container written as its contents leaves of its own
layout.** A canvas's caption set at its middle came out at its top with everything under it
risen to meet it, a band bled to the page's edges stopped at its box, a column fixed
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/backend-capability-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ Payload records live in `core` under
| Image — STRETCH / CONTAIN / COVER fit (`ImageFragmentPayload`) | ✅ `PdfImageFragmentRenderHandler` | ✅ `PptxImageFragmentRenderHandler` (COVER via the picture source crop) | ✅ `DocxSemanticBackend.writeImage` (the box comes from `NodeDefinitionSupport.resolveImageDimensions`, the same rule layout applies to `width` / `height` / `scale` and the content-width clamp; CONTAIN is embedded at its fitted size, COVER via the picture source crop as in PPTX, and the picture type is read from the bytes; its left margin and padding are not written, and a picture drawn beside its text is fitted to its box with its padding in it — each named in the report) |
| Barcode / QR (`BarcodeFragmentPayload`) | ✅ `PdfBarcodeFragmentRenderHandler` (vector: the ZXing bit matrix filled as merged rectangles) | ✅ `PptxBarcodeFragmentRenderHandler` (native freeforms: the same ZXing bit matrix as merged rectangles) | ⚠️ `DocxSemanticBackend.writeBarcode` (a PNG picture of the same ZXing bit matrix through `BarcodeMatrices`, one pixel a cell, in the symbol's two colours with their alpha and at the node's size, its data as the picture's description; it scans, but its data is part of the picture rather than editable, reported `APPROXIMATED`, which also names a link, a transform or its left margin and padding as not carried; an `anchor` is a bookmark on its paragraph; in a page zone it is skipped) |
| Table rows — resolved cells, row/col spans, two-pass fill/border paint (`TableRowFragmentPayload`) | ✅ `PdfTableRowFragmentRenderHandler` + row grouping in `PdfFixedLayoutBackend` | ✅ `PptxTableRowFragmentRenderHandler` + row grouping in `PptxFixedLayoutBackend` (positioned rectangles, edge lines, and text frames — never native PPTX tables, which re-lay-out content) | ⚠️ `DocxSemanticBackend.writeTable` (a real Word table on the grid `TableGrid` resolves: `colSpan` maps to `w:gridSpan`, `rowSpan` to `w:vMerge`, and the cascaded `DocumentTableStyle` text style reaches the cell's runs; the cell's fill maps to `w:shd` and its stroke to `w:tcBorders` — the engine's default 1pt black rule where the table states none, not Word's thinner grid — its padding to `w:tcMar`, less above and below the room Word makes for the horizontal rules (half of a rule between two rows, the lower row's, to each; the rules above and below the table whole to their row); a row's cells at the row's smallest top and bottom margins, since both editors give every cell the row's largest, the rest of each cell's padding as space above its first paragraph and below its last, down to the largest margin a cell opening with a table or in a vertical merge keeps; the cascaded `textAnchor` maps to `w:vAlign` on every cell and to `w:jc` on a text cell's paragraph, with the engine's default — the vertical middle, on the left, or on the right for a right-to-left cell — and `DEFAULT` at the bottom left, as the renderer draws it; a composed cell is written by the same writers that write its node anywhere, so one built from an image, a list or a table carries it — a nested table is a real `w:tbl` taking the width of the column it sits in, less its own margins and padding, which is the column's rather than the one the page gives it, since the layout reports a composed cell's content under the owner's path; the paragraph Word requires after a nested table is hidden where it ends its cell holding nothing and no space; a fill's opacity is dropped since `w:shd` is opaque; Word re-paginates, so the export states where the layout breaks: every row the layout placed is `w:cantSplit`, `repeatHeader(n)` rows are `w:tblHeader` and keep with the row under them, and a row of blocks is kept whole the same way; the paragraph Word requires after a document's closing table is an ordinary one where the last page has room for two lines below it, so a reader can type below the table, and otherwise a point tall with its mark hidden, so it opens no blank page, reported `APPROXIMATED` since text typed at the end then goes into the table's last cell) |
| Clip region open/close (`ShapeClipBegin/EndPayload`) | ✅ `PdfShapeClipBegin/EndRenderHandler` (CLIP_BOUNDS + CLIP_PATH) | ✅ `PptxClipSafety` + raster fallback in `PptxFixedLayoutBackend` — a provably no-op clip (padded content that cannot be cut) skips the fallback entirely and stays native, editable shapes; a clip that can cut ink renders through the PDF backend into one transparent picture on the clip bounds (pixel-exact, not editable as shapes; run-level link hotspots are not emitted and custom fragment handlers do not apply inside the picture; `Builder.clipRasterFallback(false)` restores unclipped vectors + warning; the raster targets a 2048px long edge, clamped to between native size and 4x, so a region larger than that is rendered at native resolution rather than downscaled — which also means its transient memory grows with the clip instead of stopping at the target (a 3370pt A0-landscape region costs ~45MB while rendering, against ~17MB for anything up to 2048pt); a true vector clip is tracked in [#413](https://github.com/DemchaAV/GraphCompose/issues/413)) | ⚠️ inline fallback + one-time capability warning; a picture that fills a container clipped to an ellipse takes the ellipse as its geometry, which both editors crop it to; a badge's glyph — a smaller picture in a painted container that clips it to its outline (`CLIP_PATH`) and holds nothing else but drawing — is drawn by `DocxDrawings` as a floating picture over the outline, where the layout places it, reported `APPROXIMATED` — inside a filled panel the badge and its glyph are drawn in front of the shading; an icon picture beside its text in an unpainted container or a layer stack is drawn the same way; a filled or outlined rectangle or rounded rectangle holding text, composed in a table cell, which has no place in the layout to be drawn at, is written as a panel — a one-cell table in its fill and outline, its corners squared and reported, its row held at least the outline's height less the borders both editors draw outside it where its padding does not hold its top border, and a one-line label the shape centres top to bottom on a line taller than the room Word leaves its content cut alike on both sides to that room, no closer to its letters than three quarters of a point, and seated where the page sets it; the rest of what a composed cell draws (an icon, a tile, a disc) is the table's own drawing and is drawn by `drawCellDrawing` where the layout puts it, anchored as a rectangle is |
| Clip region open/close (`ShapeClipBegin/EndPayload`) | ✅ `PdfShapeClipBegin/EndRenderHandler` (CLIP_BOUNDS + CLIP_PATH) | ✅ `PptxClipSafety` + raster fallback in `PptxFixedLayoutBackend` — a provably no-op clip (padded content that cannot be cut) skips the fallback entirely and stays native, editable shapes; a clip that can cut ink renders through the PDF backend into one transparent picture on the clip bounds (pixel-exact, not editable as shapes; run-level link hotspots are not emitted and custom fragment handlers do not apply inside the picture; `Builder.clipRasterFallback(false)` restores unclipped vectors + warning; the raster targets a 2048px long edge, clamped to between native size and 4x, so a region larger than that is rendered at native resolution rather than downscaled — which also means its transient memory grows with the clip instead of stopping at the target (a 3370pt A0-landscape region costs ~45MB while rendering, against ~17MB for anything up to 2048pt); a true vector clip is tracked in [#413](https://github.com/DemchaAV/GraphCompose/issues/413)) | ⚠️ inline fallback + one-time capability warning; the report names a clip that cuts what its layers paint, measured from the layout's fragments by `DocxClipInk` — a clip that cuts nothing (an icon inside its box, a disc's initials, a photo filling its circle) is not named; a picture that fills a container clipped to an ellipse takes the ellipse as its geometry, which both editors crop it to; a badge's glyph — a smaller picture in a painted container that clips it to its outline (`CLIP_PATH`) and holds nothing else but drawing — is drawn by `DocxDrawings` as a floating picture over the outline, where the layout places it, reported `APPROXIMATED` — inside a filled panel the badge and its glyph are drawn in front of the shading; an icon picture beside its text in an unpainted container or a layer stack is drawn the same way; a filled or outlined rectangle or rounded rectangle holding text, composed in a table cell, which has no place in the layout to be drawn at, is written as a panel — a one-cell table in its fill and outline, its corners squared and reported, its row held at least the outline's height less the borders both editors draw outside it where its padding does not hold its top border, and a one-line label the shape centres top to bottom on a line taller than the room Word leaves its content cut alike on both sides to that room, no closer to its letters than three quarters of a point, and seated where the page sets it; the rest of what a composed cell draws (an icon, a tile, a disc) is the table's own drawing and is drawn by `drawCellDrawing` where the layout puts it, anchored as a rectangle is |
| Timeline rail — one logical connector line resolved from marker and entry anchors after layout (`ShapeFragmentPayload` per page) | ✅ `PdfShapeFragmentRenderHandler` — one fragment per page, spliced beneath the markers | ✅ `PptxShapeFragmentRenderHandler` — same payload, same per-page fragments | ⚠️ `DocxDrawings` — the rail is read from the resolved layout's pass fragments and drawn per page as a `line` shape, and the markers as the shapes they are, anchored as a rectangle is: beside an entry's text they move with it when the text above is edited |
| Transform open/close — rotate/scale about fragment centre (`TransformBegin/EndPayload`) | ✅ `PdfTransformBegin/EndRenderHandler` | ✅ `PptxTransformBegin/EndRenderHandler` (group shape; rotation and centre-pivot scaling via the exterior/interior frame ratio) | ⚠️ inline fallback + one-time capability warning |
| Transform open/close — rotate/scale about fragment centre (`TransformBegin/EndPayload`) | ✅ `PdfTransformBegin/EndRenderHandler` | ✅ `PptxTransformBegin/EndRenderHandler` (group shape; rotation and centre-pivot scaling via the exterior/interior frame ratio) | ⚠️ inline fallback + one-time capability warning; a turned shape container is written upright, and the report names its transform — in its drawn outline's note, or on its own where the outline draws nothing |
| Anchor markers (`AnchorMarkerPayload`) | ✅ `PdfAnchorMarkerRenderHandler` + `PdfInternalLinkWriter` | ✅ `PptxAnchorMarkerRenderHandler` + `PptxNavigationWriter` (slide-jump hyperlinks resolved after all fragments, so forward references work) | ✅ `DocxSemanticBackend` — an anchor becomes a `w:bookmarkStart` / `w:bookmarkEnd` pair wrapping the paragraph's text, named as Word requires (letters, digits and underscores, starting with a letter, 40 characters); two anchors that clean to one name stay two bookmarks |
| Bookmark markers (`BookmarkMarkerPayload`) | ✅ `PdfBookmarkMarkerRenderHandler` + `PdfBookmarkOutlineWriter` | ⚠️ `PptxBookmarkMarkerRenderHandler` + `PptxNavigationWriter` (PPTX has no outline tree — the first bookmark on a page names its slide, further bookmarks on the same page are dropped with a debug note) | ✅ `DocxSemanticBackend` — the stated outline level becomes Word's own `HeadingN` style, so the Navigation Pane, the outline view and a generated table of contents all see the document's structure. The style carries the outline level and no formatting, so the paragraph keeps the look its author gave it; only the levels the document uses are defined, and one past Word's nine is clamped. The role is never inferred from type size |
| Alpha / opacity | ✅ `PdfAlphaSupport` (`PDExtendedGraphicsState` on every surface — shape fills/strokes, text runs, lines, side borders, table paint) | ✅ native `<a:alpha>` via POI on every surface — fills, strokes, text runs, table paint | ❌ |
Expand Down
19 changes: 16 additions & 3 deletions docs/recipes/docx-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,17 @@ tint it was flattened to. That is recorded with the rest.
- **Shape containers → inline layers.** DOCX has no portable equivalent of
a graphics-state path clip, so the container's layers are written
inline, in source order, without clipping — again with one warning per
export. The outline is drawn as a shape where the page draws it — a
export. The report names the clip — a shape container's, and a layer
stack's that clips to its bounds — wherever it cuts what the layers paint:
a square tile's corners in a disc, a label run past its chip, an ornament
set past a sidebar's side. One composed in a table's cell, which has no
place of its own, is named on its table. What is painted is measured from
the layout's fragments as the page paints it, upright as the file writes
it, and text from the outlines of its letters. A clip that cuts nothing is
not named: an icon inside its box, a disc's initials, a photo filling its
circle, a label whose line stands past its chip while its letters stay
inside. Exported with no layout behind it, every node that clips is named.
The outline is drawn as a shape where the page draws it — a
star, a diamond or a path as custom geometry — and a picture that fills
a container clipped to an ellipse takes the ellipse's shape: a portrait
is round inside its ring. A picture the layout placed is written the
Expand Down Expand Up @@ -903,7 +913,9 @@ margin where that is wider, and draws both borders outside the row's height. Mea
border's width shorter there. The body's
shapes stand above the page backgrounds, which LibreOffice stacks together with them. Each of these limits is named in the report:

- A transform is not carried: a rotated or scaled shape is drawn upright at its size.
- A transform is not carried: a rotated or scaled shape is drawn upright at its size, and so
is what a turned shape container holds — named with its outline, or on its own where the
outline draws nothing.
- A drawing carries no link, no outline entry and no bookmark: a link to a drawn shape's
anchor points at none, and a page reference to it is a fixed number. A gradient fill,
unequal corners (drawn at the largest radius) and a line's dash pattern (drawn solid) are
Expand All @@ -920,7 +932,8 @@ shapes stand above the page backgrounds, which LibreOffice stacks together with
Polygons and paths — a star, a chevron, an SVG icon's layers, a portrait drawn as paths —
are drawn the same way, as custom geometry through the same points and curves, their fill
and stroke colours carried; the dash pattern, the caps and joins and the clip round an
SVG icon are not. A shape filled only with a gradient paint shows nothing the export
SVG icon are not — a clip that cuts the icon's art, parked outside its box, is named in the
report. A shape filled only with a gradient paint shows nothing the export
carries and is **skipped**, and the report names each one. In the flow a drawn or skipped
shape still takes its room:
its placed height and margins are owed as space above what follows, and so are
Expand Down
Loading
Loading