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
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,31 @@ follow semantic versioning; release dates are ISO 8601.
`PptxFixedLayoutBackend.Builder.clipRasterFallback(false)` restores the
previous unclipped vector rendering with its one-time warning.

- The fixed PPTX backend renders document chrome and emits navigation.
Metadata lands in the OPC core properties (title, author, subject, keywords,
plus the extended application property; OPC has no producer field).
Watermarks render per slide with the PDF placement math — text rotated about
its baseline start, images with native alpha, tiling supported — and
behind-content watermarks apply before fragment rendering, so document order
produces the PDF's layering without z-order surgery. Repeating
headers/footers resolve `{page}` / `{pages}` / `{date}` tokens with the
numbering window rules (start-at, count-from, show-on-first-page, numeral
styles). Recorded link rectangles become transparent hotspots after every
fragment is placed: external targets as URL hyperlinks, internal targets as
slide-jump hyperlinks (forward references included; a dangling anchor is
skipped with a warning), and the first bookmark on a page names its slide.
Protection, viewer preferences, and debug overlays have no PPTX counterpart
and are skipped with a one-time warning.
- `PptxFixedLayoutBackend.renderSections` / `writeSections` concatenate
multi-section documents into one deck: each section keeps its own chrome
with section-local page numbering, navigation resolves across section
boundaries, and deck metadata follows the first section that declares it.
A deck carries one slide size, so sections with differing page canvases are
rejected.
- `PptxFixedLayoutBackend.Builder.deterministic(boolean | Instant)` renders
byte-identical decks across runs by pinning the OPC created/modified
properties and normalizing every zip entry timestamp (zone-independent).

### Fixed

- `DocumentSession.toImages` / `toImage` rasterized documents that use binary
Expand Down Expand Up @@ -109,6 +134,14 @@ follow semantic versioning; release dates are ISO 8601.
from the resolved layout graph across a page break with a repeated header
and a row-spanning cell, and pin the fills-before-ink paint order plus the
page-start top-border branch at the handler level.
- PPTX chrome, navigation, section, and determinism tests pin the OPC core
properties, the watermark's layer order and pass-through rotation, header/
footer token resolution with numbering windows and roman styles, resolved
URL and slide-jump hotspots (a dangling anchor degrades to a warning, not a
failure), section-local footer restarts with a cross-section slide jump, the
differing-page-size rejection, and render-twice byte equality with pinned
zip entry times; a chrome-and-navigation parity demo renders the PDF/PPTX
pair with per-page PNG previews.

## v2.0.0 — 2026-07-13

Expand Down
30 changes: 15 additions & 15 deletions docs/architecture/backend-capability-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,19 @@ Payload records live in `core` under
| Image — STRETCH / CONTAIN / COVER fit (`ImageFragmentPayload`) | ✅ `PdfImageFragmentRenderHandler` | ✅ `PptxImageFragmentRenderHandler` (COVER via the picture source crop) | ✅ semantic images (`DocxSemanticBackend`) |
| Barcode / QR (`BarcodeFragmentPayload`) | ✅ `PdfBarcodeFragmentRenderHandler` (ZXing raster) | ✅ `PptxBarcodeFragmentRenderHandler` (identical ZXing raster) | ❌ |
| 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) | ✅ semantic tables (`DocxSemanticBackend`) |
| Clip region open/close (`ShapeClipBegin/EndPayload`) | ✅ `PdfShapeClipBegin/EndRenderHandler` (CLIP_BOUNDS + CLIP_PATH) | ✅ raster fallback in `PptxFixedLayoutBackend` — the clip region renders through the PDF backend into one transparent picture on the clip bounds (pixel-exact, not editable as shapes; `Builder.clipRasterFallback(false)` restores unclipped vectors + warning) | ⚠️ inline fallback + one-time capability warning |
| Clip region open/close (`ShapeClipBegin/EndPayload`) | ✅ `PdfShapeClipBegin/EndRenderHandler` (CLIP_BOUNDS + CLIP_PATH) | ✅ raster fallback in `PptxFixedLayoutBackend` — the clip region renders through the PDF backend into one transparent picture on the clip bounds (pixel-exact, not editable as shapes; `Builder.clipRasterFallback(false)` restores unclipped vectors + warning; a true vector clip is tracked in [#413](https://github.com/DemchaAV/GraphCompose/issues/413)) | ⚠️ 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 |
| Anchor markers (`AnchorMarkerPayload`) | ✅ `PdfAnchorMarkerRenderHandler` + `PdfInternalLinkWriter` | ⚠️ `PptxAnchorMarkerRenderHandler` (destinations recorded; slide-jump emission pending) | ❌ |
| Bookmark markers (`BookmarkMarkerPayload`) | ✅ `PdfBookmarkMarkerRenderHandler` + `PdfBookmarkOutlineWriter` | ⚠️ `PptxBookmarkMarkerRenderHandler` (records collected; slide-name mapping pendingPPTX has no document outline) | ❌ |
| 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 treethe first bookmark on a page names its slide, further bookmarks on the same page are dropped with a debug note) | ❌ |
| Alpha / opacity | ✅ `PdfAlphaSupport` (`PDExtendedGraphicsState`) | 🚧 (native fill alpha) | ❌ |

## Navigation and interactivity

| Capability | PDF (fixed) | PPTX (fixed) | DOCX (semantic) |
|---|---|---|---|
| External hyperlinks (fragment- and run-level) | ✅ `PdfLinkAnnotationWriter` + link rects in `PdfFixedLayoutBackend` | ⚠️ `PptxParagraphFragmentRenderHandler` (transparent measured-span shape hotspots preserve resolved text styling; fragment rectangles arrive with navigation support) | ❌ |
| Internal links (anchor jump, forward references) | ✅ `PdfInternalLinkWriter` (two-pass) | 🚧 (slide-jump hyperlinks) | ❌ |
| Document outline / bookmarks tree | ✅ `PdfBookmarkOutlineWriter` | 🚧 (no PPTX outline concept — slide names where 1:1, otherwise dropped with a note) | ❌ |
| External hyperlinks (fragment- and run-level) | ✅ `PdfLinkAnnotationWriter` + link rects in `PdfFixedLayoutBackend` | ✅ `PptxNavigationWriter` (transparent hotspots for measured span, line, and fragment rectangles, emitted above all content after the fragment pass) | ❌ |
| Internal links (anchor jump, forward references) | ✅ `PdfInternalLinkWriter` (two-pass) | ✅ `PptxNavigationWriter` (deferred slide-jump hyperlinks, resolved after all fragments — including across sections) | ❌ |
| Document outline / bookmarks tree | ✅ `PdfBookmarkOutlineWriter` | ⚠️ `PptxNavigationWriter` (no PPTX outline concept — slide names where 1:1, extra bookmarks dropped with a note) | ❌ |

## Document chrome and output options

Expand All @@ -77,22 +77,22 @@ honour an option ignores it (documented contract).

| Capability | PDF (fixed) | PPTX (fixed) | DOCX (semantic) |
|---|---|---|---|
| Metadata (title, author, …) | ✅ `PdfDocumentPostProcessor` | 🚧 (OPC core properties) | ✅ (`DocxSemanticBackend` via `SemanticExportContext`) |
| Watermark (front/back layers) | ✅ `PdfWatermarkRenderer` | 🚧 (per-slide shape, send-to-back/front) | ❌ |
| Repeating headers / footers | ✅ `PdfHeaderFooterRenderer` | 🚧 (positioned per-slide text boxes) | ❌ |
| Protection / encryption | ✅ `PdfDocumentPostProcessor` | ❌ (ignored — no OOXML encryption support planned) | ❌ |
| Viewer preferences | ✅ `applyViewerPreferences` in `PdfFixedLayoutBackend` | ❌ (ignored — PDF-viewer concept) | n/a |
| Debug guide lines / node labels | ✅ `PdfGuideLinesRenderer`, `PdfNodeLabelRenderer` | 🚧 | n/a |
| Metadata (title, author, …) | ✅ `PdfDocumentPostProcessor` | ⚠️ `applyMetadata` in `PptxFixedLayoutBackend` (OPC core properties + extended `Application`; OPC has no producer field, so that value is not representable) | ✅ (`DocxSemanticBackend` via `SemanticExportContext`) |
| Watermark (front/back layers) | ✅ `PdfWatermarkRenderer` | ✅ `PptxChromeRenderer` (per-slide shape at the PDF placement math; behind-content applies before fragments, so no z-order surgery) | ❌ |
| Repeating headers / footers | ✅ `PdfHeaderFooterRenderer` | ✅ `PptxChromeRenderer` (positioned per-slide text boxes; `{page}` / `{pages}` / `{date}` tokens with the numbering window rules) | ❌ |
| Protection / encryption | ✅ `PdfDocumentPostProcessor` | ❌ (ignored with a one-time warning — no OOXML encryption support planned) | ❌ |
| Viewer preferences | ✅ `applyViewerPreferences` in `PdfFixedLayoutBackend` | ❌ (ignored with a one-time warning — PDF-viewer concept) | n/a |
| Debug guide lines / node labels | ✅ `PdfGuideLinesRenderer`, `PdfNodeLabelRenderer` | ❌ (ignored with a one-time warning — render through the PDF backend to see overlays) | n/a |

## Output surface and lifecycle

| Capability | PDF (fixed) | PPTX (fixed) | DOCX (semantic) |
|---|---|---|---|
| Render to bytes / stream / file (`FixedLayoutRenderer`) | ✅ `PdfFixedLayoutBackend` | ✅ `PptxFixedLayoutBackend` | ✅ `DocxSemanticBackend` (`SemanticBackend<byte[]>`) |
| Render to images (`renderToImages`) | ✅ PDFBox `PDFRenderer` | 🚧 (decision pending: POI `XSLFSlide.draw` quality) | ❌ |
| Render to images (`renderToImages`) | ✅ PDFBox `PDFRenderer` | ❌ (throws with a pointer to the PDF backend — POI's slide rasterizer cannot honour embedded fonts, and the PDF raster of the same graph is the canonical image output) | ❌ |
| Raster-slide mode — every page as one full-slide picture, pixel-exact to the PDF/PNG output (`Builder.rasterSlides(dpi)`) | n/a (the PDF raster is the source) | ✅ `PptxFixedLayoutBackend` | ❌ |
| Multi-section documents (`renderSections`, per-section chrome, cross-section links) | ✅ `buildSectionsDocument` in `PdfFixedLayoutBackend` | 🚧 | ❌ |
| Deterministic output (render twice → identical bytes) | ✅ `PdfDeterminismWriter` | 🚧 (pinned OPC timestamps + zip normalization) | ❌ |
| Multi-section documents (`renderSections`, per-section chrome, cross-section links) | ✅ `buildSectionsDocument` in `PdfFixedLayoutBackend` | ⚠️ `renderSections` in `PptxFixedLayoutBackend` (a deck carries one slide size, so every section must share the same page canvas — differing sizes throw) | ❌ |
| Deterministic output (render twice → identical bytes) | ✅ `PdfDeterminismWriter` | ✅ `PptxDeterminismWriter` (pinned OPC created/modified + zip entry-time normalization) | ❌ |
| ServiceLoader discovery (`FixedLayoutBackendProvider`) | ✅ `PdfFixedLayoutBackendProvider` (`format() == "pdf"`) | ✅ `PptxFixedLayoutBackendProvider` (`format() == "pptx"`) | n/a (semantic SPI: `SemanticBackend`) |
| `DocumentSession` convenience methods | ✅ `buildPdf` / `writePdf` / `toPdfBytes` / `toImages` | 🚧 (`buildPptx` / `writePptx` / `toPptxBytes` planned) | via `session.export(new DocxSemanticBackend(...))` |

Expand Down
Loading
Loading