diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a236c309..8968910b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -35,24 +35,23 @@ When writing new code, avoid Java 21+ APIs and language constructs that don't ex
## How to propose changes
-GraphCompose follows a fork → feature branch → pull request flow. **Target the branch that matches your change** — GraphCompose is mid-transition to the 2.0 module line, so pick the base branch from this table before you fork:
+GraphCompose follows a fork → feature branch → pull request flow. **Target the branch that matches your change** — pick the base branch from this table before you fork:
| Change type | Base branch |
|---|---|
-| **2.0 feature / fix** (almost all current work) | `2.0-dev` |
-| **Critical 1.9.x fix** (bug / security backport) | `develop` |
+| **Feature / fix** (almost all work) | `develop` |
+| **Critical 1.9.x fix** (bug / security backport) | `1.x` |
| Stable releases (tags) | `main` |
-| After 2.0 GA | `develop` |
-Almost all current work is 2.0 and targets **`2.0-dev`**. `develop` and `main` carry the shipping **1.9.x** line and take only critical fixes; `main` is the public stable surface and accepts release merges only. See [Version lines](#version-lines-and-the-1x-maintenance-branch) below for the full transition.
+Almost all work targets **`develop`**, the ongoing 2.x line. The `1.x` branch takes critical fixes and security backports only — no features. `main` is the public stable surface and accepts release merges only. See [Version lines](#version-lines-and-the-1x-maintenance-branch) below.
### Contribution flow
1. **Fork** the repository on GitHub and clone your fork locally.
-2. **Create a feature branch** from your target base (`2.0-dev` for 2.0 work — substitute `develop` for a critical 1.9.x fix):
+2. **Create a feature branch** from your target base (`develop` for feature work — substitute `1.x` for a critical 1.9.x backport):
```bash
- git checkout 2.0-dev
- git pull --ff-only origin 2.0-dev
+ git checkout develop
+ git pull --ff-only origin develop
git checkout -b feature/short-description
```
Use `feature/...` for new functionality, `fix/...` for bug fixes, and `docs/...` for documentation-only changes. Issue-prefixed names (`42/fix/short-description`) are also welcome — convenient when the branch closes a specific issue.
@@ -62,7 +61,7 @@ Almost all current work is 2.0 and targets **`2.0-dev`**. `develop` and `main` c
./mvnw -B -ntp clean verify
```
This runs the architecture-and-documentation guards plus the full test suite. The same gate runs in CI on every PR.
-5. **Push** your feature branch to your fork and open a pull request against the base branch you started from (`2.0-dev` for 2.0 work) on `DemchaAV/GraphCompose`. Reference any related issue and describe the user-visible change in the PR body.
+5. **Push** your feature branch to your fork and open a pull request against the base branch you started from (`develop` for feature work) on `DemchaAV/GraphCompose`. Reference any related issue and describe the user-visible change in the PR body.
6. **CI runs automatically.** Active jobs:
- `Architecture and Documentation Guards` — fast canonical / engine-boundary guard tests, fail-first gate (always runs)
- `Build and run tests (JDK 17)`, `(JDK 21)`, `(JDK 25)` — full `mvnw verify` in parallel matrix across the supported JVMs
@@ -71,7 +70,7 @@ Almost all current work is 2.0 and targets **`2.0-dev`**. `develop` and `main` c
- `Performance Smoke Check` — PR-only coarse benchmark to catch performance regressions
- `CI Gate` — single aggregate status check that is green when every job that ran passed
- **Selective on pull requests:** a `dorny/paths-filter` step skips the heavy jobs when a PR touches nothing that affects the build — a **docs-only PR runs the guards only**; `Binary Compatibility` runs only when the core module changed, and the `Performance Smoke Check` only when core / render-pdf / templates changed. Pushes to `2.0-dev` / `main` (and manual dispatch) always run the full gate. Point branch protection at **`CI Gate`** + **`Architecture and Documentation Guards`** rather than the individual matrix legs, so a docs-only PR is not left waiting on a skipped check.
+ **Selective on pull requests:** a `dorny/paths-filter` step skips the heavy jobs when a PR touches nothing that affects the build — a **docs-only PR runs the guards only**; `Binary Compatibility` runs only when the core module changed, and the `Performance Smoke Check` only when core / render-pdf / templates changed. Pushes to `develop` / `main` (and manual dispatch) always run the full gate. Point branch protection at **`CI Gate`** + **`Architecture and Documentation Guards`** rather than the individual matrix legs, so a docs-only PR is not left waiting on a skipped check.
The PR cannot merge into a protected branch until all required checks are green.
7. **Address review comments**, then squash any fixup commits before merge. The maintainer merges through GitHub once review is complete.
@@ -85,7 +84,7 @@ Almost all current work is 2.0 and targets **`2.0-dev`**. `develop` and `main` c
- linear history is enforced (squash or rebase, no merge commits)
- force pushes and branch deletion are disabled
-`2.0-dev` (and, for 1.9.x fixes, `develop`) accepts feature-branch PRs from contributors. The maintainer may push directly for solo-driven release-prep work; external contributions still flow through PRs.
+`develop` (and, for 1.9.x backports, `1.x`) accepts feature-branch PRs from contributors. The maintainer may push directly for solo-driven release-prep work; external contributions still flow through PRs.
### Release flow
@@ -98,16 +97,11 @@ See [docs/contributing/release-process.md](./docs/contributing/release-process.m
### Version lines and the 1.x maintenance branch
-GraphCompose is mid-transition to the 2.0 module line. Until 2.0 ships:
+The 2.0 GA shipped, so the branches now hold their long-term roles:
-- **`2.0-dev`** is the working branch for 2.0 — feature branches for 2.0 work target `2.0-dev`, not `develop`.
-- **`develop`** and **`main`** carry the shipping **1.9.x** line; `main` is its stable, tagged surface (latest `v1.9.x`).
-
-At the **2.0 GA** merge the branches take their long-term roles:
-
-- **`main`** fast-forwards to 2.0 and becomes the stable 2.0 line.
-- **`develop`** becomes the ongoing 2.x working branch (what `2.0-dev` was); `2.0-dev` retires.
-- A **`1.x`** maintenance branch is cut from the final 1.9.x commit on `main` at that moment. It receives **critical fixes and security / CVE backports only — no features** — released as `1.9.x` patches from `1.x` via the same `cut-release.ps1` + tag flow. New feature work always targets the 2.x line.
+- **`develop`** is the ongoing 2.x working branch — all feature branches target it.
+- **`main`** is the stable 2.x line, tagged at each release (latest `v2.x`).
+- **`1.x`** is the maintenance branch cut from the final 1.9.x commit. It receives **critical fixes and security / CVE backports only — no features** — released as `1.9.x` patches from `1.x` via the same `cut-release.ps1` + tag flow. New feature work always targets the 2.x line.
## Repository map
diff --git a/README.md b/README.md
index a3d582d7..735305b5 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@
## One source → a PDF and an editable PowerPoint deck
-The same `DocumentSession` emits both. The PDF backend prints the resolved layout; the PPTX backend (**beta**) rebuilds it as slides with identical geometry — text, panels, tables, and vectors arrive in PowerPoint as **native, editable shapes**, not screenshots (the page below lands as 69 native shapes; only its clip-masked logo art is a picture).
+The same `DocumentSession` emits both. The PDF backend prints the resolved layout; the PPTX backend (**beta**) rebuilds it as slides. Both consume the same resolved layout graph, so page and slide frames and every positioned element share the same geometry — text, panels, tables, and vectors arrive in PowerPoint as **native, editable shapes**, not screenshots (the page below lands as 69 native shapes; only its clip-masked logo art is a picture). Glyphs are rasterised by the viewer, so the exact text rendering depends on the fonts installed on the viewing machine; see the [backend capability matrix](docs/architecture/backend-capability-matrix.md) for per-feature fidelity.
```java
Path deck = Path.of("twin-output.pptx");
diff --git a/ROADMAP.md b/ROADMAP.md
index 8017ea16..b596bbe3 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -2,41 +2,42 @@
GraphCompose is solo-maintained. This roadmap is a direction, not a contract. Dates are intentionally omitted. Concrete work is tracked in [issues](https://github.com/DemchaAV/GraphCompose/issues) and shipped work is recorded in [CHANGELOG.md](CHANGELOG.md). For v1.6 phase-level detail, see [docs/roadmaps/v1.6-roadmap.md](docs/roadmaps/v1.6-roadmap.md).
-## Now — 2.0 line
+## Now — 2.1 line
-In flight on `2.0-dev`, preparing the 2.0.0 GA. The 2.0 line is about **packaging and internal hygiene**, not new authoring API — binary-breaking by design, with `japicmp` report-only for the cycle.
+In development on `develop`, building toward the **2.1.0** minor. The headline is the **fixed-layout PPTX render backend**: the same `DocumentSession` that prints a PDF now also emits an editable PowerPoint deck — one page per slide, identical geometry by construction, native shapes. It ships as `@Beta` (Experimental) while its API shape settles; the geometry identity with the PDF backend is a design invariant, not subject to change. See the [API stability policy](docs/api-stability.md) and the [backend capability matrix](docs/architecture/backend-capability-matrix.md).
-**Scope frozen (2026-07).** 2.0.0 ships exactly the set below; no further internal engineering is pulled in before GA — that work is the post-2.0 line (next section).
+Alongside it, 2.1 hardens pagination: `keepWithNext()` for headings, orphaned-heading fixes ahead of paragraphs, tables and lists, and the layered CV presets wired to the keep-with-next policy.
+
+Full detail lands in [CHANGELOG.md](CHANGELOG.md) under `v2.1.0`.
+
+## Current stable — 2.0
+
+The **2.0.0** GA is the current stable line. 2.0 was about **packaging and internal hygiene**, not new authoring API — binary-breaking by design.
- **Modular split** — the single jar is split into `graph-compose-core` plus `graph-compose-render-pdf` / `graph-compose-render-docx` / `graph-compose-render-pptx` / `graph-compose-templates` / `graph-compose-testing`, with render backends discovered through a `ServiceLoader` SPI. `graph-compose` stays a drop-in wrapper (core + render-pdf) so existing PDF callers upgrade unchanged. See the [2.0 modules migration guide](docs/migration/v2.0.0-modules.md) and [ADR 0016](docs/adr/0016-multi-module-packaging.md).
- **Legacy removal** — the dead Entity-Component-System execution layer and the deprecated (`forRemoval`) public API are gone; the classic template presets are replaced by the layered `templates.*` stack on `BrandTheme`.
- **Release & publishing pipeline** — multi-module Maven Central publishing, the `core/`-layout reactor (`./mvnw clean verify` at the root builds everything), and the cut / tag / GA runbook.
- **Compatibility tests** — the guard, snapshot, and visual-regression suites that prove the split left rendered output unchanged.
-- **Maintenance** — the **v1.9.x** line (current stable) receives critical fixes only.
-
-Further internal engineering — more layout-class decomposition, abstraction cleanup, streaming, optional performance work — is **deferred to post-2.0** and does not gate GA.
-Full detail in [CHANGELOG.md](CHANGELOG.md) under `v2.0.0 — Planned`.
+The **v1.9.x** line receives critical fixes only.
## Next — post-2.0 engineering
-Committed direction for after the 2.0 GA: internal refactors, scale work, and tooling that do **not** change the public authoring API. Tracked in [docs/roadmaps/post-2.0-engineering.md](docs/roadmaps/post-2.0-engineering.md).
+Committed internal direction for the post-2.0 line: refactors, scale work, and tooling that do **not** change the public authoring API. Tracked in [docs/roadmaps/post-2.0-engineering.md](docs/roadmaps/post-2.0-engineering.md).
- **Decompose the layout hot files** — split `LayoutCompiler` and `TextFlowSupport` along their natural seams into individually-tested collaborators, with layout output unchanged.
-- **ArchUnit module-boundary guards** — enforce the canonical / engine / render layering structurally, replacing the path-based greps that can pass vacuously after a move.
-- **Cross-module coverage aggregation** — a dedicated non-published module that compile-depends on the tested modules so JaCoCo sees the `qa` suites; report-only first, thresholds after a baseline read.
-- **Per-module binary-compatibility baselines** — once the 2.0 GA artifacts are published, switch `japicmp` to per-module baselines in break-on-incompatible mode.
+- **Per-module binary-compatibility baselines** — now that the 2.0 GA artifacts are published, switch `japicmp` from the single-artifact baseline to per-module baselines in break-on-incompatible mode.
## Later (directional)
Not committed. Reflects current thinking; priorities may shift based on user feedback and adoption signals.
+- **PPTX beyond beta.** Graduate the fixed-layout PPTX backend from `@Beta` to stable and close the remaining fidelity gaps — true vector clipping instead of the raster fallback ([#413](https://github.com/DemchaAV/GraphCompose/issues/413)), exact numeric dash arrays, and distinct per-corner radii.
- **DOCX visibility for unsupported nodes.** Make currently-silent skips (`shape`, `line`, `ellipse`, `barcode`) loud — minimum a warn log, ideally a strict-mode flag that fails instead of dropping content silently.
- **Block-level alignment for fixed-size flow children.** Paths, images, layer stacks, shape containers and barcodes currently left-align in a flow; centring one means wrapping it in a full-width `ShapeContainer` just to use its CENTER anchor. Add a per-node horizontal align (left / centre / right — the `margin: auto` / `align(center)` analogue) so a fixed box can place itself in the flow directly. Surfaced by the v1.8 SVG icon-gallery and feature-catalog work.
- **Backend-neutral layout measurement.** Decouple measurement from PDFBox-specific resources so non-PDF backends do not pull PDFBox into the dependency graph.
- **DOCX maturity.** Either expand DOCX coverage toward PDF parity, or move DOCX behind an explicitly experimental flag.
-- **Property-based testing.** Random table spans, pagination edge cases, deeply nested layouts.
-- **Real PPTX export.** Current state is a manifest skeleton. Will only be built out if there is concrete user demand.
+- **Property-based testing.** Expand the `@Property` layout-invariant seed to random table spans, pagination edge cases, and deeply nested layouts.
- **Public Javadoc site.** Generated and hosted, kept in sync with releases.
## Not on the roadmap
diff --git a/docs/README.md b/docs/README.md
index 06b92746..3bce26e4 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -38,6 +38,11 @@ back here.
- **[templates/v2-layered/](templates/v2-layered/)** — the template surface (CV is the reference implementation): `data` / `theme` / `components` / `widgets` / `presets`.
- **[templates/v1-classic/](templates/v1-classic/)** — 🗄️ archived: the classic spec/builder/presets surface removed in 2.0; kept for pre-2.0 callers.
+### Output backends
+- **[architecture/backend-capability-matrix.md](architecture/backend-capability-matrix.md)** — what each render backend supports, per capability. The source of truth for PDF vs PPTX fidelity.
+- **[../render-pptx/README.md](../render-pptx/README.md)** — `graph-compose-render-pptx`: editable PowerPoint decks from the same session that prints the PDF (`@Beta`, first shipped in 2.1.0).
+- **[api-stability.md](api-stability.md)** — stability tier per package, and what a tier promises.
+
### Architecture
- **[architecture/overview.md](architecture/overview.md)** — high-level system architecture (engine + DSL + templates + backends).
- **[architecture/lifecycle.md](architecture/lifecycle.md)** — the document lifecycle from `GraphCompose.document(...)` through `buildPdf()`.
diff --git a/docs/api-stability.md b/docs/api-stability.md
index cfebd412..605846e6 100644
--- a/docs/api-stability.md
+++ b/docs/api-stability.md
@@ -241,6 +241,8 @@ Javadoc per element.
| `com.demcha.compose.document.templates.invoice.*` | **Stable** | `graph-compose-templates` | Layered invoice family — `ModernInvoice` on `InvoiceDocumentSpec`. |
| `com.demcha.compose.document.templates.proposal.*` | **Stable** | `graph-compose-templates` | Layered proposal family — `ModernProposal` on `ProposalDocumentSpec`. |
| `com.demcha.compose.document.templates.data.*` | **Stable** | `graph-compose-templates` | Family-neutral document data records (invoice / proposal / schedule specs). |
+| `com.demcha.compose.document.backend.fixed.pptx` | **Experimental** | `graph-compose-render-pptx` | Marked `@Beta` at the package level — `PptxFixedLayoutBackend`, its builder, and `PptxFixedLayoutBackendProvider`. First shipped in 2.1.0. |
+| `com.demcha.compose.document.backend.fixed.pptx.handlers` | **Experimental** | `graph-compose-render-pptx` | Marked `@Beta` at the package level — the `PptxFragmentRenderHandler` seam and its built-in handlers. |
| `com.demcha.compose.document.layout.*` | **Internal** | `graph-compose-core` | Marked `@Internal` at the package level. Engine surface. |
| `com.demcha.compose.engine.*` | **Internal** | `graph-compose-core` | Engine surface; not part of the public contract regardless of `public` keyword. `engine.render.pdf.*` ships in `graph-compose-render-pdf`. |
diff --git a/docs/architecture/backend-capability-matrix.md b/docs/architecture/backend-capability-matrix.md
index fa81b745..d4818f9d 100644
--- a/docs/architecture/backend-capability-matrix.md
+++ b/docs/architecture/backend-capability-matrix.md
@@ -22,9 +22,12 @@ Backends:
`com.demcha.compose.document.backend.fixed.pdf`. Consumes the resolved
`LayoutGraph`; the reference implementation.
- **PPTX (fixed-layout)** — `graph-compose-render-pptx`,
- `com.demcha.compose.document.backend.fixed.pptx` (in development).
- Consumes the same resolved `LayoutGraph`; geometry-identical to PDF by
- construction, since layout is compiled in core before any backend runs.
+ `com.demcha.compose.document.backend.fixed.pptx`. Consumes the same
+ resolved `LayoutGraph`; geometry-identical to PDF by construction, since
+ layout is compiled in core before any backend runs. Ships as `@Beta`
+ (Experimental) in its first release — usable, with the per-capability
+ status in the tables below; the API shape may still change in a minor
+ while feedback lands (see [../api-stability.md](../api-stability.md)).
- **DOCX (semantic)** — `graph-compose-render-docx`,
`DocxSemanticBackend`. Walks the semantic node tree, deliberately
ignores fixed-layout geometry; Word owns the flow. Geometry rows are
diff --git a/docs/architecture/canonical-legacy-parity.md b/docs/architecture/canonical-legacy-parity.md
index 0db4fdfc..247930f4 100644
--- a/docs/architecture/canonical-legacy-parity.md
+++ b/docs/architecture/canonical-legacy-parity.md
@@ -92,7 +92,7 @@ compatibility concerns. New authoring features should be added through
| Guide lines | Done | `guideLines(true)` is available on document builder and session. |
| Metadata/protection/watermark/header/footer | Done | Configure on `DocumentSession` (e.g. `metadata(...)`, `watermark(...)`, `protect(...)`, `header(...)`, `footer(...)`); convenience PDF entrypoints (`buildPdf`, `writePdf`, `toPdfBytes`) honour these options without an explicit backend builder. `PdfFixedLayoutBackend.builder()` remains for advanced cases. |
| DOCX semantic export | Done | Use `session.export(new DocxSemanticBackend())` for paragraph/table/image-aware Word output. Requires `org.apache.poi:poi-ooxml` on the consumer classpath. |
-| PPTX semantic export | Planned for v1.6 (stretch) | Build out `PptxSemanticBackend` from the existing manifest skeleton to a working POI-based exporter — paragraphs as text boxes, tables as PowerPoint tables, sections as slides. See [v1.6 roadmap, Phase D](../roadmaps/v1.6-roadmap.md). |
+| PPTX export | Done (fixed-layout, `@Beta` since 2.1.0) | `session.buildPptx(path)` with `graph-compose-render-pptx` on the classpath emits an editable deck from the same resolved layout graph as the PDF — one page per slide, native shapes, matching geometry. See the [backend capability matrix](backend-capability-matrix.md). The older `PptxSemanticBackend` manifest remains in the module but is not what the convenience methods use. |
| PDFBox types in session API | Rejected | Keep PDFBox behind the fixed PDF backend. |
## Diagnostics
diff --git a/docs/capabilities.md b/docs/capabilities.md
index 5b5ddd10..32697c49 100644
--- a/docs/capabilities.md
+++ b/docs/capabilities.md
@@ -53,7 +53,7 @@ tracks what is `Partial` or `Planned`.
| Write a PDF file | `buildPdf()`, `buildPdf(Path)` | Stable | [Getting started](getting-started.md) |
| Stream to a caller-owned stream | `writePdf(OutputStream)` | Stable | [Streaming](recipes/streaming.md) |
| In-memory bytes | `toPdfBytes()` | Stable | [Getting started](getting-started.md) |
-| Geometry-identical PowerPoint deck | `buildPptx()`, `buildPptx(Path)`, `writePptx(OutputStream)`, `toPptxBytes()` — needs `graph-compose-render-pptx` on the classpath | Stable | [Backend capability matrix](architecture/backend-capability-matrix.md) |
+| Geometry-identical PowerPoint deck | `buildPptx()`, `buildPptx(Path)`, `writePptx(OutputStream)`, `toPptxBytes()` — needs `graph-compose-render-pptx` on the classpath | Experimental (`@Beta`, first shipped in 2.1.0) | [Backend capability matrix](architecture/backend-capability-matrix.md) |
| Editable Word (semantic) | `export(new DocxSemanticBackend())` | Stable (semantic, not PDF parity) | [Troubleshooting](troubleshooting.md) |
| PDF chrome (metadata / watermark / header / footer / protection) | `metadata(...)`, `watermark(...)`, `header(...)`, `footer(...)`, `protect(...)` | Stable | [Getting started](getting-started.md) |
| Layout snapshot regression | `LayoutSnapshotAssertions.assertMatches(...)` | Stable | [Layout snapshot testing](operations/layout-snapshot-testing.md) |
@@ -70,9 +70,20 @@ tracks what is `Partial` or `Planned`.
---
+## New in 2.1.0
+
+These ship from 2.1.0 onward — confirm your dependency version before relying on them:
+
+| Capability | Main API |
+|---|---|
+| Editable PowerPoint deck (`@Beta`) | `buildPptx(Path)`, `writePptx(OutputStream)`, `toPptxBytes()` — add `graph-compose-render-pptx` |
+| Slide page-size presets | `DocumentPageSize.SLIDE_16_9`, `DocumentPageSize.SLIDE_4_3` |
+| Select a render backend by format | `BackendProviders.fixedLayout("pptx")` |
+| Keep a heading with its content | `SectionBuilder.keepWithNext()`, `LineBuilder.keepWithNext()` |
+
## New in 1.9.0
-These ship from 1.9.0 onward — confirm your dependency version before relying on them:
+These ship from 1.9.0 onward:
| Capability | Main API |
|---|---|
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 7c45f3bf..8dd81cf3 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -204,6 +204,44 @@ without the outline frame and logs a one-time capability warning. See
[`docs/recipes/shape-as-container.md`](recipes/shape-as-container.md)
for the full recipe.
+## PowerPoint output
+
+Add `graph-compose-render-pptx` to the classpath and the same session
+also emits an editable `.pptx` deck. The backend registers itself
+through `ServiceLoader`, so there is nothing to wire up:
+
+
+```java
+import com.demcha.compose.GraphCompose;
+import com.demcha.compose.document.api.DocumentPageSize;
+import com.demcha.compose.document.api.DocumentSession;
+
+import java.nio.file.Path;
+
+try (DocumentSession document = GraphCompose.document()
+ .pageSize(DocumentPageSize.SLIDE_16_9)
+ .margin(48, 48, 48, 48)
+ .create()) {
+
+ document.pageFlow(page -> page
+ .module("Title", module -> module.paragraph("Quarterly review")));
+
+ document.buildPptx(Path.of("deck.pptx"));
+}
+```
+
+One resolved page becomes one identically-sized slide, and fragments
+land at the same coordinates as in the PDF — both backends consume the
+same layout graph, compiled in core before either runs. Glyphs are
+still drawn by the viewer, so the rendered text depends on the fonts
+installed on the viewing machine.
+
+The backend ships as `@Beta` (Experimental) in 2.1.0: it is usable for
+production decks, but its API shape may change in a minor release.
+Per-capability fidelity — what is a native shape, what is approximated,
+and the raster fallback used for clipped regions — is documented in the
+[backend capability matrix](architecture/backend-capability-matrix.md).
+
## Templates
Templates compose into the same `DocumentSession`. Data specs live
diff --git a/render-pptx/README.md b/render-pptx/README.md
index a03edf51..acd64358 100644
--- a/render-pptx/README.md
+++ b/render-pptx/README.md
@@ -2,20 +2,92 @@
`io.github.demchaav:graph-compose-render-pptx`
-The semantic PPTX export backend for GraphCompose. Split out of the DOCX artifact so a DOCX
-consumer never pulls the PPTX code (and vice versa).
+The fixed-layout PowerPoint render backend for GraphCompose. The same
+`DocumentSession` that prints a PDF also emits an editable `.pptx` deck — one
+resolved page becomes one identically-sized slide, and every fragment lands at
+the same coordinates, because both backends consume the same `LayoutGraph`
+compiled in core before either of them runs.
## Status
-`PptxSemanticBackend` is a **slide-safe semantic manifest skeleton** — it validates the node
-graph against what a slide surface can represent; full `.pptx` file emission is not built out
-yet. Depend on it for the semantic manifest / forward-compatibility, not for production slide
-export. Track the [ROADMAP](../ROADMAP.md) for real PPTX output.
+**Experimental (`@Beta`), first shipped in 2.1.0.** The backend is complete
+enough for production decks: text, tables, shapes, images, gradients, links and
+navigation, headers/footers/watermarks, and multi-section documents all arrive as
+native PowerPoint objects rather than pictures. Across the 38 capabilities the
+matrix tracks, 24 map to a native equivalent, 10 render natively with an
+approximated styling detail (distinct per-corner radii collapse to one value,
+numeric dash arrays map to the nearest preset, some inline SVG layers fall back
+to a transparent PNG), and 4 are not supported at all — see the limitations below.
+
+The `@Beta` marker covers the *API shape* (`PptxFixedLayoutBackend`, its builder,
+and the `PptxFragmentRenderHandler` seam), which may still change in a minor
+release while feedback lands. Geometry identity with the PDF backend is a design
+invariant and is not subject to change.
+
+The per-capability breakdown lives in one place:
+[backend capability matrix](../docs/architecture/backend-capability-matrix.md).
+The stability policy is [docs/api-stability.md](../docs/api-stability.md).
+
+## First deck
+
+Put `graph-compose-core` and this artifact on the classpath; the backend
+registers itself through `ServiceLoader`, so no wiring is needed.
+
+```java
+try (DocumentSession document = GraphCompose.document()
+ .pageSize(DocumentPageSize.SLIDE_16_9)
+ .create()) {
+ compose(document);
+ document.buildPptx(Path.of("deck.pptx"));
+}
+```
+
+`writePptx(OutputStream)` and `toPptxBytes()` are the streaming and in-memory
+counterparts. Rendering the *same* session to both formats gives a PDF and a
+deck with matching page/slide geometry.
+
+`DocumentPageSize.SLIDE_16_9` (960 × 540 pt) and `DocumentPageSize.SLIDE_4_3`
+(720 × 540 pt) match the PowerPoint defaults. Any other page size works too —
+the slide simply takes that size.
+
+## Limitations to know before you ship
+
+- **Clipping falls back to a raster island.** A clipped composite is re-rendered
+ through the PDF backend and placed as a picture, so that region loses text
+ editability and any run-level links inside it. It is pixel-accurate and can be
+ switched off with `PptxFixedLayoutBackend.builder().clipRasterFallback(false)`.
+ True vector clipping is tracked in
+ [#413](https://github.com/DemchaAV/GraphCompose/issues/413).
+- **Glyphs are drawn by the viewer, not by GraphCompose.** Shape frames, line
+ boxes and positions are fixed by the layout graph, but the actual glyph
+ rasterisation depends on the fonts installed on the viewing machine. Fonts are
+ embedded where the font's licensing bits allow it; otherwise PowerPoint
+ substitutes, and the backend logs which family was substituted.
+- **Byte-for-byte reproducibility is opt-in.** The default path streams the deck
+ with live timestamps. Pass
+ `PptxFixedLayoutBackend.builder().deterministic(instant)` to pin the core
+ properties and normalise the zip entries.
+- **PDF-only options do not apply**: document protection, viewer preferences and
+ the debug guide-line overlays are PDF concepts and are ignored here, each with
+ a one-time warning.
+- **`renderToImages` is not implemented** and throws with a pointer to the PDF
+ backend — POI's slide rasteriser cannot honour embedded fonts. The session-level
+ `toImage(...)` / `toImages(...)` are unaffected: they resolve the PDF backend
+ explicitly.
+- **Multi-section documents** render into one deck; the sections must share a
+ slide size.
## When to depend on it
-Opt-in, at compile scope, only if you target the PPTX semantic surface. Not included by any
-of `graph-compose`, `graph-compose-core`, or `graph-compose-bundle`.
+Opt-in, at compile scope, when you want PowerPoint output. It is not pulled in
+by `graph-compose`, `graph-compose-core`, or `graph-compose-bundle`.
+
+Note that this artifact depends on `graph-compose-render-pdf` at compile scope:
+the PDF backend supplies the shared font-measurement library (so glyph widths
+stay aligned with the widths that produced the layout graph) and performs the
+raster pass behind the clipping fallback. A PPTX-only consumer therefore also
+resolves the PDF stack. Extracting a backend-neutral measurement artifact is
+tracked on the roadmap.
## Install
@@ -35,3 +107,10 @@ dependencies { implementation("io.github.demchaav:graph-compose-render-pptx:2.0.
The full "which artifact?" table: [root README → Installation](../README.md#installation).
Upgrading from 1.x: [modules migration guide](../docs/migration/v2.0.0-modules.md).
+
+---
+
+The module also carries `PptxSemanticBackend`, a slide-safe semantic
+manifest that validates a node graph against what a slide surface can represent.
+It predates the fixed-layout backend, is outside the scope of the capability
+matrix, and is not what `buildPptx(...)` uses.