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
6 changes: 3 additions & 3 deletions docs/architecture/canonical-legacy-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Public application code should start with:

`GraphCompose.document(...) -> DocumentSession -> DocumentDsl -> layout graph -> PdfFixedLayoutBackend`

`EntityManager` and raw engine builders remain internal, test-support, or
compatibility concerns. New authoring features should be added through
`com.demcha.compose.document.*`, not through low-level entity assembly.
The `com.demcha.compose.engine.*` foundation remains internal. New authoring
features should be added through `com.demcha.compose.document.*`, not by
reaching into engine internals.

## Status Legend

Expand Down
22 changes: 14 additions & 8 deletions docs/architecture/lifecycle.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Canonical Document Lifecycle

GraphCompose v1.2 follows the canonical session-first lifecycle:
GraphCompose follows a session-first lifecycle:

```text
GraphCompose.document(...)
Expand All @@ -9,7 +9,7 @@ GraphCompose.document(...)
-> semantic nodes
-> layout graph
-> layout snapshot or fixed backend render
-> PDF stream/bytes/file
-> PDF or PPTX stream/bytes/file
```

```mermaid
Expand Down Expand Up @@ -41,10 +41,12 @@ for layout, pagination, diagnostics, and rendering.

`DocumentSession` is mutable and not thread-safe. Create one session per document/request.

Advanced PDF-only options such as metadata, protection, watermark, headers,
and footers are configured through `PdfFixedLayoutBackend.builder()`. The
session convenience PDF methods expose only the common document-level options,
including guide-line overlays.
Backend-level tuning is configured through the backend builder —
`PdfFixedLayoutBackend.builder()` for PDF-specific options such as protection,
and `PptxFixedLayoutBackend.builder()` for PPTX-specific ones such as
deterministic output and the clip raster fallback. The backend-neutral chrome
(metadata, watermark, headers/footers) is set on the session itself and reaches
whichever backend renders, subject to the coverage noted in §5.

## 2. Authoring

Expand Down Expand Up @@ -78,9 +80,9 @@ snapshot.

Pagination happens during layout. Semantic nodes define whether they are atomic or splittable. Long paragraphs/lists can split into fragments; atomic blocks move to the next page when needed.

The lower-level ECS engine still has pagination helpers under `com.demcha.compose.engine.pagination` for internal tests, diagnostics, and backend/tooling work.
Pagination lives entirely in `LayoutCompiler` and the `NodeDefinition` split contracts under `com.demcha.compose.document.layout`. A node opts into keep-together or keep-with-next behaviour through its semantic flags; the compiler resolves the page break and emits the resulting `PlacedFragment` records.

## 5. Render
## 5. Render (PDF / PPTX)

`DocumentSession.writePdf(OutputStream)` renders the resolved layout graph through `PdfFixedLayoutBackend` and writes the PDF to a caller-owned stream without closing it. This is the preferred server path because the session does not keep a PDF byte-array cache.

Expand All @@ -94,6 +96,10 @@ The canonical PDF backend:
- dispatches placed fragments to payload handlers
- applies bookmarks, links, guide lines, metadata, watermarks, headers/footers, and protection

`writePptx(OutputStream)`, `toPptxBytes()` and `buildPptx(...)` are the PowerPoint counterparts, resolved through the `"pptx"` provider when `graph-compose-render-pptx` is on the classpath. `PptxFixedLayoutBackend` consumes the **same** resolved layout graph — one page becomes one identically-sized slide, and fragments land at the same coordinates — then emits native POI XSLF shapes rather than a rasterised page.

The chrome options are backend-neutral, but coverage differs. Watermarks and repeating headers/footers apply to both backends; metadata applies to both, mapping onto OPC core properties for PPTX where the producer field has no equivalent. Protection, viewer preferences and the debug guide-line overlays are PDF concepts that the PPTX backend ignores with a one-time warning. See the [backend capability matrix](./backend-capability-matrix.md) for the per-capability breakdown.

## 6. Close

Always close the session, normally with try-with-resources. Closing releases measurement resources and clears request-local text measurement caches. It does not require consumers to manage PDFBox objects directly.
Expand Down
107 changes: 56 additions & 51 deletions docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ flowchart TD
A["Application code — GraphCompose.document(...)"] --> B["DocumentSession + DocumentDsl<br/>(document.api · document.dsl)"]
B --> C["Semantic DocumentNode tree<br/>(document.node) — renderer-neutral"]
C --> D["LayoutCompiler + NodeRegistry<br/>(document.layout) → LayoutFragments"]
C -->|"export(...)"| H["DocxSemanticBackend — Apache POI<br/>(document.backend.semantic) — reads DocumentGraph"]
D --> E["Shared engine foundation — @Internal<br/>(engine.*): measure → paginate → place → order"]
E --> F{"Active backend"}
E --> F{"Fixed-layout backend"}
F -->|PDF| G["PdfFixedLayoutBackend<br/>(document.backend.fixed.pdf + engine.render.pdf)"]
F -->|DOCX| H["DocxSemanticBackendApache POI<br/>(document.backend.semantic)"]
F -->|PPTX| P["PptxFixedLayoutBackend — POI XSLF<br/>(document.backend.fixed.pptx) — @Beta"]
E -.->|"layoutSnapshot()"| I["Deterministic layout snapshot<br/>(regression tests — no bytes rendered)"]
```

The two fixed-layout backends branch from the **same** resolved graph, which
is why page and slide geometry match by construction. The semantic DOCX
exporter branches earlier, straight off the `DocumentGraph`: it never sees a
`LayoutGraph`, which is why it cannot reproduce fixed-layout geometry.

The PDF path deliberately spans **two** packages: the canonical backend
`document.backend.fixed.pdf` owns PDFBox lifecycle and option translation,
then dispatches resolved fragments to the engine render handlers under
Expand All @@ -48,8 +54,10 @@ Concretely:
placement, and render ordering against those prepared fragments.
5. the active backend turns the resolved `LayoutGraph` /
`PlacedFragment` stream into output bytes — `PdfFixedLayoutBackend`
for PDF, `DocxSemanticBackend` for DOCX, future PPTX backend
skeleton in place.
for PDF and `PptxFixedLayoutBackend` for PowerPoint, both consuming
the same resolved graph, so page and slide geometry match by
construction. `DocxSemanticBackend` takes the other route: it
consumes the semantic node tree directly, without the layout graph.

That separation is the core project concept. Public code describes
document intent, layout resolves geometry, renderers only draw already
Expand Down Expand Up @@ -105,11 +113,19 @@ need to reach below it.
does not need to touch it.
- **`document.backend.fixed.pdf`** — the canonical PDF backend
(`PdfFixedLayoutBackend`, fragment render handlers, option
translators). The only place PDFBox imports are allowed outside the
engine foundation.
- **`document.backend.semantic`** — semantic exporters
(`DocxSemanticBackend` based on Apache POI; `PptxSemanticBackend`
manifest skeleton).
translators), shipped in **graph-compose-render-pdf**. The only place
PDFBox imports are allowed outside the engine foundation.
- **`document.backend.fixed.pptx`** — the fixed-layout PowerPoint
backend (`PptxFixedLayoutBackend`, `PptxFragmentRenderHandler` and its
handler set), shipped in **graph-compose-render-pptx**. Consumes the
same resolved `LayoutGraph` as the PDF backend — one page becomes one
identically-sized slide. Marked `@Beta` at the package level; see the
[backend capability matrix](./backend-capability-matrix.md) for
per-capability fidelity.
- **`document.backend.semantic`** — semantic exporters that bypass the
layout graph (`DocxSemanticBackend`, Apache POI; and `PptxSemanticBackend`,
a slide-safe node-graph manifest that predates the fixed-layout backend
and is not what `buildPptx(...)` uses).

## Template layer (`com.demcha.compose.document.templates.*`)

Expand Down Expand Up @@ -165,26 +181,11 @@ objects are resolved before their parent containers so parent
finalized. See [pagination-ordering.md](./pagination-ordering.md) for
the detailed rationale and the failure modes that motivated it.

The engine materializes one deterministic hierarchy snapshot per
layout pass: parent links from `ParentComponent`, sibling order from
`Entity.children`, roots / layers / depth metadata rebuilt every pass.
Layout, pagination, snapshot extraction, and render backends all
agree on the same tree semantics.

### Entity / ECS responsibilities (engine-internal)

`Entity` is intentionally a thin ECS-style identity object. It owns:

- stable identity
- the component map
- canonical child order through `Entity.children`
- a cached render marker reference for fast `hasRender()` checks

Layout-specific math and pagination mutation live in dedicated
helpers — `EntityBounds` for geometry reads,
`ParentContainerUpdater` for parent-container size and page-shift
propagation. Deprecated helper methods on `Entity` are migration
shims, not extension points.
The compiler materializes one deterministic result per layout pass:
`LayoutCompiler` prepares each semantic node into a `PreparedNode`,
paginates it, and emits `PlacedFragment` records into a `LayoutGraph`.
Layout, pagination, snapshot extraction, and render backends all read
that one resolved graph, so they cannot disagree about geometry.

### Semantic modules

Expand Down Expand Up @@ -219,26 +220,22 @@ rows it merges.
These rules apply to engine and backend contributors. Application
code should not need any of them.

- engine builders and layout helpers consume an engine-level
`TextMeasurementSystem` instead of reaching through the active
renderer
- render marker components identify *what* needs to be rendered;
*how* it is drawn lives in renderer-owned handler packages such as
the `PdfFragmentRenderHandler` implementations under
`document.backend.fixed.pdf.handlers`
- `RenderStream` acts as a session factory, not as a per-entity
content-stream opener
- `RenderPassSession` is the shared seam for page lifetime and
page-surface reuse — it must stay free of PDFBox and backend
package imports
- the PDF entity path dispatches through registered render handlers;
there is no backend-specific render fallback path

Fixed leaf primitives (such as `TextComponent` and `BlockText`)
follow the same engine contract: they materialize as regular
entities with render/content/layout components, rely on normal
`ContentSize` / `Padding` / `Margin` / `Placement`, and do not
introduce a separate layout subsystem.
- layout helpers consume an engine-level `TextMeasurementSystem`
instead of reaching through the active renderer, so measurement is
backend-neutral and the same widths produce the layout graph that
every backend then draws
- a `PlacedFragment`'s payload identifies *what* needs to be rendered;
*how* it is drawn lives in renderer-owned handler packages — the
`PdfFragmentRenderHandler` implementations under
`document.backend.fixed.pdf.handlers` and the
`PptxFragmentRenderHandler` set under
`document.backend.fixed.pptx.handlers`
- each fixed-layout backend owns its own render-pass session and page
surface lifetime; that seam stays free of backend-library imports so a
new backend does not have to touch engine code
- fragment dispatch goes through registered handlers only; there is no
backend-specific render fallback path, and an unhandled payload fails
with `UnsupportedNodeCapabilityException` rather than drawing nothing

## Current package roots

Expand Down Expand Up @@ -276,8 +273,16 @@ last:
`ShapeContainerNode` clip and `DocumentTransform` rotation/scale
fall back to inline content with a one-time capability warning.
Authors who need clipped or rotated output must export to PDF.
- The PPTX skeleton lives behind `PptxSemanticBackend`; richer slide
layout is roadmap for v1.6+.
- The PPTX backend (`PptxFixedLayoutBackend`, Apache POI XSLF) renders
the same resolved `LayoutGraph` as PDF into an editable deck — one
page per identically-sized slide, native shapes rather than pictures.
It ships `@Beta` in 2.1.0: usable for production decks, with the API
shape still open to change in a minor. Clipped composites fall back to
a rasterised island (switchable via `clipRasterFallback(false)`), and
`renderToImages` is unsupported — the per-capability breakdown is the
[backend capability matrix](./backend-capability-matrix.md).
The older `PptxSemanticBackend` manifest remains in the module but is
not on the `buildPptx(...)` path.
- New backends should add their own rendering system, render-pass
session, text measurement system, and handler set without changing
engine builders such as tables or template data models. The shared
Expand Down
10 changes: 6 additions & 4 deletions docs/architecture/package-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ ships them differs.
| --- | --- |
| `graph-compose-core` | The lean engine: `com.demcha.compose`, the canonical `document.*` authoring surface (`api` / `dsl` / `node` / `style` / `table` / `snapshot`), `document.showcase` (`FontShowcase`), the `document.backend.fixed` SPI seam, the public `document.backend.fixed.pdf.options` records, `document.layout`, `font.*`, and the internal `engine.*` foundation. |
| `graph-compose-render-pdf` | The PDFBox backend: `document.backend.fixed.pdf.**` (the `PdfFixedLayoutBackend` impl + handlers) and the `engine.render.pdf.**` render tree. Registers the PDF `FixedLayoutBackendProvider` / `FontMetricsProvider`. |
| `graph-compose-render-docx` / `graph-compose-render-pptx` | The POI semantic exporters — `document.backend.semantic.docx` / `.pptx`. |
| `graph-compose-render-pptx` | The POI XSLF backend: `document.backend.fixed.pptx.**` (the `PptxFixedLayoutBackend` impl + handlers), registering the `"pptx"` `FixedLayoutBackendProvider`. Also carries the older `document.backend.semantic.pptx` manifest exporter. Depends on `graph-compose-render-pdf` for shared font measurement and the clip raster pass. |
| `graph-compose-render-docx` | The POI semantic exporter — `document.backend.semantic.docx`. |
| `graph-compose-templates` | The built-in preset families — `document.templates.**`. |
| `graph-compose-testing` | Consumer test support — `com.demcha.compose.testing.**`. |
| `graph-compose` | Back-compat wrapper: an empty jar over `graph-compose-core` + `graph-compose-render-pdf`. |
Expand Down Expand Up @@ -54,6 +55,7 @@ per-package artifact.
| `com.demcha.compose.document.layout` (`@Internal` at package level) | Semantic layout compiler, node definitions, fragments, split/measure contracts, and layout graph. | `NodeDefinition` is `@Beta` — Extension SPI for custom node types. New node behavior must be deterministic and covered by layout or render tests. |
| `com.demcha.compose.document.backend.fixed` | Backend-neutral fixed-layout rendering contract. | Keep it independent from PDFBox and semantic template data. |
| `com.demcha.compose.document.backend.fixed.pdf` | Canonical fixed-layout PDF backend, fragment handlers, PDF-specific options, and PDF-backed measurement resources. | Keep PDFBox lifecycle internal; normal callers should use `DocumentSession` and default PDF convenience methods. |
| `com.demcha.compose.document.backend.fixed.pptx` (`@Beta` at package level) | Fixed-layout PowerPoint backend — `PptxFixedLayoutBackend`, its provider, deterministic writer, embedded-font and clip support, plus the `.handlers` fragment handler set. Consumes the same resolved `LayoutGraph` as the PDF backend. | Keep POI XSLF lifecycle internal. Any capability change must update [backend-capability-matrix.md](./backend-capability-matrix.md) in the same commit. |
| `com.demcha.compose.document.dsl.internal` | Internal helpers for public DSL builders such as semantic name normalization and builder callback application. | Do not expose these helpers in examples; move reusable authoring concepts to public builder classes instead. |
| `com.demcha.compose.document.backend.semantic` | Experimental semantic export contracts for non-fixed outputs. | Keep exporters separate from PDF fixed-layout rendering. |
| `com.demcha.compose.document.debug` | Snapshot/debug adapters for canonical layout graph inspection. | Debug output should be deterministic and safe for tests. |
Expand All @@ -62,9 +64,9 @@ per-package artifact.

| Package | Responsibility | Extension rule |
| --- | --- | --- |
| `com.demcha.compose.engine.components.*` | Low-level ECS components, content payloads, style values, geometry, layout, and render markers. | Use only for engine primitives; public document authoring should go through `DocumentDsl` and semantic nodes. |
| `com.demcha.compose.engine.core` | Entity manager, canvas, traversal context, and base ECS system contracts. | Keep core thin; put stage-specific logic in layout, pagination, measurement, or render packages. |
| `com.demcha.compose.engine.pagination` | Pagination markers and helpers (`Breakable`, `ParentContainerUpdater`, `Offset`). | Maintain child-first ordering and page-shift propagation rules. |
| `com.demcha.compose.engine.components.*` | Engine content payloads and their caches — decoded image data and intrinsic sizes, barcode payloads, and the shared bounded LRU cache behind them. | Use only for engine primitives; public document authoring should go through `DocumentDsl` and semantic nodes. |
| `com.demcha.compose.engine.debug` | Layout snapshot value types (`LayoutSnapshot`, `LayoutNodeSnapshot`, `LayoutCanvasSnapshot`, `LayoutInsetsSnapshot`) behind `DocumentSession.layoutSnapshot()`. | Snapshot output must stay deterministic — it is a regression-test contract. |
| `com.demcha.compose.engine.font` | Backend-neutral font abstraction: `Font`, `FontBase`, `FontLineMetrics`. | Backends implement the seam; the catalogue and lookup live in the public `com.demcha.compose.font`. |
| `com.demcha.compose.engine.measurement` | Text measurement contracts and font-backed implementations. | Builders/layout helpers depend on this seam instead of reaching into renderers. |
| `com.demcha.compose.engine.render` | Backend-neutral render contracts, handler registry, and render-pass session lifetime. | Add backend-neutral contracts here, backend-specific drawing elsewhere. |
| `com.demcha.compose.engine.render.pdf` | Shared PDFBox primitives for the canonical fixed-layout backend: `PdfFont`, `GlyphFallbackLogger`, and the header/footer + watermark renderers under `helpers`. | Add canonical-shared PDF support here; per-fragment PDF drawing lives in the `PdfFragmentRenderHandler` implementations under `document.backend.fixed.pdf.handlers`. |
Expand Down
Loading