Skip to content

Commit 2099476

Browse files
authored
docs(docx): record what the semantic backend drops (#446)
The DOCX page stated its limits as "coverage is narrower than the PDF backend" and named four dropped drawing kinds. Everything a Word reader would actually be surprised by went unmentioned: hyperlinks are dropped entirely, there are no bookmarks, headers/footers or watermark, and a mixed-style RichText paragraph is written with every run in the paragraph's own style, so per-run bold or colour is lost. Two capability-matrix cells were wrong in opposite directions. Text decorations were marked unsupported for DOCX although underline maps to Word's single underline and only STRIKETHROUGH falls through; the paragraph row was marked fully supported although runs lose their own style and link target. Core's page offered "implement the FixedLayoutBackendProvider SPI" as the bring-your-own-backend route. A session resolves font metrics as it opens, so that SPI alone throws at create(); the page now says both halves are needed and which artifact ships one.
1 parent 3a67ad6 commit 2099476

4 files changed

Lines changed: 38 additions & 8 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ For a Spring Boot `@RestController` streaming the PDF straight to the response,
207207
| Format | Status | Notes |
208208
|---|---|---|
209209
| PDF | Production | Fixed-layout backend on PDFBox 3.0. Full DSL coverage. |
210-
| DOCX | Partial | Semantic export via Apache POI. Unsupported nodes (`shape`, `line`, `ellipse`, `barcode`) are dropped silently — layout fidelity is best-effort for paragraph / list / table content. |
210+
| DOCX | Partial | Semantic export via Apache POI — paragraphs, block images, tables and metadata. Word owns the flow, so drawing nodes (`shape`, `line`, `ellipse`, `barcode`) are dropped silently. Beyond geometry, **hyperlinks, bookmarks, headers/footers and multi-section documents are not implemented** — see [render-docx](./render-docx/README.md#what-it-maps-and-what-it-does-not). |
211211
| PPTX | Beta | Fixed-layout export via Apache POI from the same resolved layout — one page per editable slide with native shapes and text frames; clipped regions land as pixel-exact pictures. First shipped in 2.1, marked `@Beta` while the API shape settles. |
212212

213213
### Text & internationalization

core/README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ it, with **no PDFBox, POI, or template code** on its dependency tree.
99
## When to depend on it
1010

1111
- You want the smallest engine and will **bring your own render backend** — add
12-
`graph-compose-render-pdf`, or implement the `FixedLayoutBackendProvider` SPI.
12+
`graph-compose-render-pdf`, or implement the `FixedLayoutBackendProvider` SPI. A custom
13+
backend needs **both** halves of the SPI: opening a session measures text through a
14+
`FontMetricsProvider`, so publish one alongside your backend. `graph-compose-render-pdf`
15+
is the only shipped artifact that registers one.
1316
- You are building a library on top of GraphCompose and don't want to impose a backend
1417
on your consumers.
1518

@@ -27,8 +30,9 @@ try (var doc = GraphCompose.document(out).create()) {
2730
} // with graph-compose-render-pdf present, buildPdf() / toPdfBytes() / toImages() work
2831
```
2932

30-
A core-only classpath asked to render throws `MissingBackendException`, whose message
31-
names the artifact to add.
33+
A core-only classpath throws `MissingBackendException` at `create()` — before any render
34+
call, since the session resolves its font metrics as it opens — and the message names the
35+
artifact to add.
3236

3337
## Install
3438

docs/architecture/backend-capability-matrix.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Payload records live in `core` under
4343

4444
| Capability (payload) | PDF (fixed) | PPTX (fixed) | DOCX (semantic) |
4545
|---|---|---|---|
46-
| Paragraph — pre-wrapped lines, runs, alignment (`ParagraphFragmentPayload`) |`PdfParagraphFragmentRenderHandler` |`PptxParagraphFragmentRenderHandler` (one absolute, wrap-disabled frame per measured line) | semantic paragraphs (`DocxSemanticBackend`) |
46+
| Paragraph — pre-wrapped lines, runs, alignment (`ParagraphFragmentPayload`) |`PdfParagraphFragmentRenderHandler` |`PptxParagraphFragmentRenderHandler` (one absolute, wrap-disabled frame per measured line) | ⚠️ semantic paragraphs (`DocxSemanticBackend`) — every run takes the paragraph's style, so per-run styling and `linkTarget` are dropped |
4747
| Inline code/badge chips (`InlineBackground` on text spans) |`PdfParagraphFragmentRenderHandler` |`PptxParagraphFragmentRenderHandler` ||
4848
| Inline images (`ParagraphImageSpan`) |`PdfParagraphFragmentRenderHandler` |`PptxParagraphFragmentRenderHandler` ||
4949
| Inline vector shapes (`ParagraphShapeSpan`) |`PdfParagraphFragmentRenderHandler` | ⚠️ `PptxParagraphFragmentRenderHandler` + `PptxInlineGeometry` (distinct per-corner radii render with the top-left radius — single-adjust preset) ||
@@ -64,7 +64,7 @@ Payload records live in `core` under
6464
| Anchor markers (`AnchorMarkerPayload`) |`PdfAnchorMarkerRenderHandler` + `PdfInternalLinkWriter` |`PptxAnchorMarkerRenderHandler` + `PptxNavigationWriter` (slide-jump hyperlinks resolved after all fragments, so forward references work) ||
6565
| 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) ||
6666
| 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 ||
67-
| Text decorations — underline / strikethrough (`DocumentTextDecoration`) |`PdfTextDecorations` (em-proportional marks: underline −0.10 em, strikethrough +0.28 em, thickness 0.05 em) |`PptxTextFrames.applyStyle` (PowerPoint draws its own marks — sub-point placement differences vs the PDF's constants) | |
67+
| Text decorations — underline / strikethrough (`DocumentTextDecoration`) |`PdfTextDecorations` (em-proportional marks: underline −0.10 em, strikethrough +0.28 em, thickness 0.05 em) |`PptxTextFrames.applyStyle` (PowerPoint draws its own marks — sub-point placement differences vs the PDF's constants) | ⚠️ `DocxSemanticBackend.applyStyle` (underline maps to Word's single underline; `STRIKETHROUGH` is dropped) |
6868

6969
## Navigation and interactivity
7070

render-docx/README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,34 @@ try (var doc = GraphCompose.document().create()) {
3535
}
3636
```
3737

38-
DOCX maps the semantic node graph, not the fixed PDF layout — coverage is narrower than the
39-
PDF backend.
38+
## What it maps, and what it does not
39+
40+
DOCX walks the **semantic node graph**, not the fixed PDF layout — Word owns the flow, so
41+
the page geometry the PDF backend resolves is deliberately ignored. Shapes, gradients,
42+
clips, transforms and alpha have no DOCX counterpart by design; a document that draws with
43+
them exports its text and tables, not its drawing.
44+
45+
What maps: paragraphs (runs, alignment), block images (`STRETCH` / `CONTAIN` / `COVER`),
46+
table rows including row and column spans, and document metadata. Run styling carries font
47+
family, size, colour, bold, italic and underline.
48+
49+
Beyond geometry, these are **not implemented** even though Word itself can express them —
50+
check the list before you promise a `.docx` to a reader:
51+
52+
- **Hyperlinks are dropped**, external and internal alike; the link text survives as plain text.
53+
- **No bookmarks and no navigation outline.**
54+
- **No repeating headers or footers**, and no watermark layer.
55+
- **`STRIKETHROUGH` is dropped** — the other decorations map.
56+
- **No barcodes or QR codes**, and no inline images or code/badge chips inside a paragraph.
57+
- **Per-run styling in a mixed-style paragraph is flattened.** Every run in a `RichText`
58+
paragraph is written with the paragraph's style, so a bold or accent-coloured segment
59+
loses its own styling; the text itself is kept.
60+
- **Multi-section documents export through the fixed-layout backends only**
61+
`renderSections` is not part of the semantic path.
62+
- **Output is not byte-deterministic**: rendering twice does not produce identical files.
63+
64+
Per-capability detail, with the implementing class for every supported cell:
65+
[backend capability matrix](../docs/architecture/backend-capability-matrix.md).
4066

4167
## Install
4268

0 commit comments

Comments
 (0)