From 4e23171114e9b6df23e6017fc60274f6b34a4af0 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Sun, 26 Jul 2026 01:38:38 +0100 Subject: [PATCH] docs(docx): record what the semantic backend drops 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. --- README.md | 2 +- core/README.md | 10 +++++-- .../architecture/backend-capability-matrix.md | 4 +-- render-docx/README.md | 30 +++++++++++++++++-- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 735305b5..d2e22aa6 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ For a Spring Boot `@RestController` streaming the PDF straight to the response, | Format | Status | Notes | |---|---|---| | PDF | Production | Fixed-layout backend on PDFBox 3.0. Full DSL coverage. | -| 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. | +| 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). | | 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. | ### Text & internationalization diff --git a/core/README.md b/core/README.md index 4704140e..3a20046f 100644 --- a/core/README.md +++ b/core/README.md @@ -9,7 +9,10 @@ it, with **no PDFBox, POI, or template code** on its dependency tree. ## When to depend on it - You want the smallest engine and will **bring your own render backend** — add - `graph-compose-render-pdf`, or implement the `FixedLayoutBackendProvider` SPI. + `graph-compose-render-pdf`, or implement the `FixedLayoutBackendProvider` SPI. A custom + backend needs **both** halves of the SPI: opening a session measures text through a + `FontMetricsProvider`, so publish one alongside your backend. `graph-compose-render-pdf` + is the only shipped artifact that registers one. - You are building a library on top of GraphCompose and don't want to impose a backend on your consumers. @@ -27,8 +30,9 @@ try (var doc = GraphCompose.document(out).create()) { } // with graph-compose-render-pdf present, buildPdf() / toPdfBytes() / toImages() work ``` -A core-only classpath asked to render throws `MissingBackendException`, whose message -names the artifact to add. +A core-only classpath throws `MissingBackendException` at `create()` — before any render +call, since the session resolves its font metrics as it opens — and the message names the +artifact to add. ## Install diff --git a/docs/architecture/backend-capability-matrix.md b/docs/architecture/backend-capability-matrix.md index dd5e48ce..673e6ad3 100644 --- a/docs/architecture/backend-capability-matrix.md +++ b/docs/architecture/backend-capability-matrix.md @@ -43,7 +43,7 @@ Payload records live in `core` under | Capability (payload) | PDF (fixed) | PPTX (fixed) | DOCX (semantic) | |---|---|---|---| -| Paragraph — pre-wrapped lines, runs, alignment (`ParagraphFragmentPayload`) | ✅ `PdfParagraphFragmentRenderHandler` | ✅ `PptxParagraphFragmentRenderHandler` (one absolute, wrap-disabled frame per measured line) | ✅ semantic paragraphs (`DocxSemanticBackend`) | +| 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 | | Inline code/badge chips (`InlineBackground` on text spans) | ✅ `PdfParagraphFragmentRenderHandler` | ✅ `PptxParagraphFragmentRenderHandler` | ❌ | | Inline images (`ParagraphImageSpan`) | ✅ `PdfParagraphFragmentRenderHandler` | ✅ `PptxParagraphFragmentRenderHandler` | ❌ | | 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 | Anchor markers (`AnchorMarkerPayload`) | ✅ `PdfAnchorMarkerRenderHandler` + `PdfInternalLinkWriter` | ✅ `PptxAnchorMarkerRenderHandler` + `PptxNavigationWriter` (slide-jump hyperlinks resolved after all fragments, so forward references work) | ❌ | | 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) | ❌ | | Alpha / opacity | ✅ `PdfAlphaSupport` (`PDExtendedGraphicsState` on every surface — shape fills/strokes, text runs, lines, side borders, table paint) | ✅ native `` via POI on every surface — fills, strokes, text runs, table paint | ❌ | -| 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) | ❌ | +| 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) | ## Navigation and interactivity diff --git a/render-docx/README.md b/render-docx/README.md index d0558f47..6a1790ed 100644 --- a/render-docx/README.md +++ b/render-docx/README.md @@ -35,8 +35,34 @@ try (var doc = GraphCompose.document().create()) { } ``` -DOCX maps the semantic node graph, not the fixed PDF layout — coverage is narrower than the -PDF backend. +## What it maps, and what it does not + +DOCX walks the **semantic node graph**, not the fixed PDF layout — Word owns the flow, so +the page geometry the PDF backend resolves is deliberately ignored. Shapes, gradients, +clips, transforms and alpha have no DOCX counterpart by design; a document that draws with +them exports its text and tables, not its drawing. + +What maps: paragraphs (runs, alignment), block images (`STRETCH` / `CONTAIN` / `COVER`), +table rows including row and column spans, and document metadata. Run styling carries font +family, size, colour, bold, italic and underline. + +Beyond geometry, these are **not implemented** even though Word itself can express them — +check the list before you promise a `.docx` to a reader: + +- **Hyperlinks are dropped**, external and internal alike; the link text survives as plain text. +- **No bookmarks and no navigation outline.** +- **No repeating headers or footers**, and no watermark layer. +- **`STRIKETHROUGH` is dropped** — the other decorations map. +- **No barcodes or QR codes**, and no inline images or code/badge chips inside a paragraph. +- **Per-run styling in a mixed-style paragraph is flattened.** Every run in a `RichText` + paragraph is written with the paragraph's style, so a bold or accent-coloured segment + loses its own styling; the text itself is kept. +- **Multi-section documents export through the fixed-layout backends only** — + `renderSections` is not part of the semantic path. +- **Output is not byte-deterministic**: rendering twice does not produce identical files. + +Per-capability detail, with the implementing class for every supported cell: +[backend capability matrix](../docs/architecture/backend-capability-matrix.md). ## Install