Declarative Java DSL for structured business documents.
Describe what the document says; the engine resolves layout, pagination, themes, and backend rendering — print-ready PDF first, an editable PowerPoint deck from the same source. Cinematic by default.
Release status — 🟢 Latest stable: v2.1.0 — the PowerPoint release:
graph-compose-render-pptxturns the same resolved layout into an editable deck — one page per slide, geometry-identical to the PDF, text and panels as native shapes. Ships as@Beta. What each backend supports ↓
· ⬆️ Upgrading from 1.x?
graph-composestays a drop-in for PDF with no code change; see the 2.0 modules migration guide · See API stability policy for tier definitions.
Live Showcase · Examples Gallery · Docs · Changelog
☝ This banner is itself a GraphCompose document — view the full module-first deck (PDF), rendered by EngineDeckV2Example: the 2.0 module graph, native vector charts, and real comparative benchmarks, all drawn by the engine. It renders its own marketing.
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 for per-feature fidelity.
Path deck = Path.of("twin-output.pptx");
try (DocumentSession doc = GraphCompose.document(Path.of("twin-output.pdf"))
.pageSize(DocumentPageSize.SLIDE_16_9)
.create()) {
compose(doc); // one description
doc.buildPdf(); // print-ready PDF
doc.buildPptx(deck); // editable PowerPoint
}| twin-output.pdf — rendered by the PDF backend | twin-output.pptx — the same page, as PowerPoint itself renders it |
![]() |
![]() |
☝ The generated deck open in PowerPoint — the headline is a selected, editable text frame, and the ribbon is live because the slide is built from native shapes. Artifacts: PDF · PPTX · source (TwinOutputExample, ~370 lines, page included).
- Author intent, not coordinates. Fluent DSL for sections, paragraphs, tables, lists, layer stacks, themes — the engine handles measurement, pagination, and rendering.
- Deterministic by design. Two-pass layout. Snapshots are stable across machines, so layout regressions are catchable in tests before any byte ships.
- Cinematic by default. Soft panels, accent strips, transforms, native vector charts, and gradients are first-class primitives, not workarounds.
- Lean core, pluggable backends. The
graph-compose-coreengine carries no PDFBox or POI; render backends are separate modules discovered viaServiceLoader— PDF is one dependency away (or already included ingraph-compose), DOCX/PPTX are opt-in — see support matrix.
Sits between iText (low-level page primitives) and JasperReports (XML-template-driven layout): a Java DSL describes the document semantically, the engine renders.
The module-first release — the single jar becomes a family of per-concern artifacts, so you install exactly what you render.
- Lean engine —
graph-compose-coreis the document model, DSL, themes, and deterministic layout with no PDFBox, POI, or template code on its dependency tree. Backends plug in through aServiceLoaderseam; a core-only classpath asked to render throwsMissingBackendExceptionnaming the artifact to add. - Opt-in render backends —
graph-compose-render-pdf(PDFBox 3.0, full DSL coverage),graph-compose-render-pptx(Apache POI, geometry-identical PowerPoint decks from the same resolved layout — one page per editable slide; clipped regions land as pixel-exact pictures; ships as beta in its first release),graph-compose-render-docx(Apache POI, semantic export). graph-composestays a drop-in — the 1.x coordinate is now a thin wrapper over core + the PDF backend, so existing callers upgrade with no code and no dependency change.- Templates are their own artifact — the CV / cover-letter / invoice / proposal preset families moved to
graph-compose-templates(imports unchanged). This is the one dependency-level break of the split. graph-compose-bundle— one batteries-included coordinate: PDF stack + templates + fonts + colour emoji.- Retired surface — the APIs deprecated across 1.6–1.9 are removed, the layered template packages dropped their
.v2suffix, andBusinessThemeplus the classic pre-layered presets are gone — each removal has a named replacement in the migration guide.
Everything the 1.9 line added — in-document navigation, native TOC and page references, bookmarks, multi-section documents, inline chips / SVG icons / colour emoji, render-to-image — ships unchanged in 2.0. Full history in CHANGELOG.md.
Requires Java 17+ (enforced by the build).
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose</artifactId>
<version>2.1.0</version>
</dependency>dependencies { implementation("io.github.demchaav:graph-compose:2.1.0") }Which artifact? (2.0 module split).
graph-composeabove is the drop-in default — it renders PDF out of the box because it aggregates the leangraph-compose-coreengine plus thegraph-compose-render-pdfbackend, so existing 1.x callers upgrade with no code change. Reach for a different coordinate only to take less or more:
Goal Depend on PDF — the 1.x default graph-composeBatteries-included (PDF + templates + fonts + emoji) graph-compose-bundleLean core, bring your own backend graph-compose-coreBuilt-in CV / cover-letter / invoice / proposal templates add graph-compose-templatesPowerPoint deck, geometry-identical to the PDF add graph-compose-render-pptxDOCX export (semantic) add graph-compose-render-docxEvery 2.0 coordinate shares the
graph-composeversion (the fonts and emoji companions keep their own lines). A baregraph-compose-corerenders nothing until a backend is on the classpath — asking it to build a PDF throwsMissingBackendException, which names the artifact to add (graph-compose-render-pdf, already included ingraph-compose).
Companion artifacts: fonts & colour emoji. Two opt-in companions carry their own version lines (they change on their own cadence, so an engine upgrade never re-downloads them):
graph-compose-fonts:1.0.0— the curated Google font families (~18 MB; pure-text and standard-14 documents need nothing extra; details in the fonts migration note) — andgraph-compose-emoji:1.0.0— inline colour emoji forRichText.emoji(":star:", size)(an unknown shortcode falls back to its literal text, so documents without emoji render unchanged). Both are already included ingraph-compose-bundle.
Distribution — Maven Central is the canonical channel from v1.6.6 onwards (
io.github.demchaav:graph-compose:<version>). Hosted Javadocs auto-publish to javadoc.io/doc/io.github.demchaav/graph-compose shortly after each Central release. The legacy JitPack URL (com.github.DemchaAV:GraphCompose:v<version>) remains resolvable for callers pinned to v1.6.5 and earlier but is no longer the documented install option.
Upgrading from 1.x? Rendering PDF through
graph-composeneeds no change at all. If you reached the built-in templates through the single 1.x jar, addgraph-compose-templates(imports are unchanged) — the 2.0 migration guide walks every case, including the removed deprecated APIs and their replacements.
import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.style.DocumentColor;
import com.demcha.compose.document.style.DocumentTextDecoration;
import com.demcha.compose.document.style.DocumentTextStyle;
import com.demcha.compose.font.FontName;
import java.nio.file.Path;
class Hello {
public static void main(String[] args) throws Exception {
// A small inline palette — swap these for your own brand colours.
DocumentColor cream = DocumentColor.rgb(252, 248, 240);
DocumentColor panel = DocumentColor.rgb(244, 238, 228);
DocumentColor accent = DocumentColor.rgb(196, 153, 76);
DocumentTextStyle h1 = DocumentTextStyle.builder()
.fontName(FontName.HELVETICA_BOLD).size(28)
.decoration(DocumentTextDecoration.BOLD)
.color(DocumentColor.rgb(20, 60, 75)).build();
DocumentTextStyle body = DocumentTextStyle.builder()
.fontName(FontName.HELVETICA).size(11)
.color(DocumentColor.rgb(34, 38, 50)).build();
try (DocumentSession document = GraphCompose.document(Path.of("hello.pdf"))
.pageSize(DocumentPageSize.A4)
.pageBackground(cream)
.margin(28, 28, 28, 28)
.create()) {
document.pageFlow(page -> page
.addSection("Hero", section -> section
.softPanel(panel, 10, 14)
.accentLeft(accent, 4)
.addParagraph(p -> p.text("GraphCompose").textStyle(h1))
.addParagraph(p -> p.text("A cinematic hero, no manual coordinates.")
.textStyle(body))));
document.buildPdf();
}
}
}For a Spring Boot @RestController streaming the PDF straight to the response, see HttpStreamingExample.
| Format | Status | Notes |
|---|---|---|
| Production | Fixed-layout backend on PDFBox 3.0. Full DSL coverage. | |
| DOCX | Partial | Semantic export via Apache POI — paragraphs, lists, block images, tables and metadata. Word owns the flow, so drawing nodes (shape, line, ellipse, barcode) are dropped, one logged warning per kind. Hyperlinks, bookmarks and headers/footers are not implemented, table colSpan/rowSpan is not applied, and image fit modes are ignored — see render-docx. |
| 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 is laid out left-to-right. Bidirectional (RTL) reordering and complex-script shaping — Arabic contextual joining, Indic reordering — are not performed, so Arabic / Hebrew text renders in logical order without correct visual ordering. Full RTL / bidi support is tracked in #140.
- A glyph the active font does not cover renders as
?(with a warning logged); load a font that covers the script you need.
- Server-side PDF generation in Java — invoices, CVs, reports, proposals, statements, schedules.
- Templated documents from data — themed presets (
ModernProfessional,ModernInvoice, …) you parameterise instead of re-styling every time. - Regression-tested layouts —
DocumentSession#layoutSnapshot()makes layout changes visible in PRs before any byte ships;PdfVisualRegressionadds a pixel-level gate for font and colour fidelity. - Streaming PDFs from web backends — Spring Boot
@RestControllerwriting straight to the response (HttpStreamingExample). - Higher-level than PDFBox, lighter than JasperReports — Java DSL describes semantics; no XML templates, no manual coordinates.
- Not a hosted PDF rendering service — it is a library you embed.
- Not a WYSIWYG editor — the DSL is code, not drag-and-drop.
- Not a reporting engine like JasperReports — no datasource bindings, no XML templates, no compiled
.jasperfiles. - Not a browser / HTML-to-PDF renderer — the engine has its own layout pipeline; HTML/CSS input is not supported.
| Library | API style | Layout | License | Best for |
|---|---|---|---|---|
| GraphCompose | Java DSL, semantic nodes | Two-pass, deterministic, snapshot-testable | MIT | Code-first business documents with layout regression tests |
| PDFBox | Low-level text / path primitives | Manual coordinates | Apache 2.0 | Direct PDF manipulation, parsing, extraction |
| iText 7 | Object/layout API + low-level canvas | Automatic layout with direct-positioning options | AGPL / commercial | When AGPL is acceptable or you have a commercial licence |
| OpenPDF | iText 4 fork | Manual + helpers | LGPL / MPL | Legacy iText 4 codebases |
| JasperReports | XML templates compiled to .jasper |
Template-driven | LGPL | Tabular reports with datasource bindings |
GraphCompose uses PDFBox under the hood as the rendering backend — the comparison is about authoring surface, not the renderer.
| You want to… | Surface | Entry point |
|---|---|---|
| Generate a one-off PDF programmatically | DSL | GraphCompose.document(...).pageFlow(...) — see Hello world above |
| Generate a CV / cover letter from data | Layered templates | ModernProfessional.create().compose(session, cvDocument) — see layered templates |
| Add a custom visual primitive | Engine extension | NodeDefinition + PdfFragmentRenderHandler — see extension guide |
| Regression-test generated layouts | Layout snapshots | DocumentSession#layoutSnapshot() — quickstart at Testing your document; full reference at snapshot testing |
| Pixel-test the rendered PDF (fonts, colours, anti-aliasing) | Visual regression | PdfVisualRegression.standard()…assertMatchesBaseline(...) — see visual regression testing |
| See the live gallery | Static showcase site | Showcase — source under web/, deployed to GitHub Pages via the Pages workflow |
Templates in 2.0 — there is one template surface: the layered preset families in
graph-compose-templates, themed throughBrandTheme. Arriving from a pre-2.0 surface (classic presets, the built-in*Templateclasses)? Which template system should I use? maps every retired name to its layered replacement.
Three snippets from the vector surfaces. Full runnable versions live in the examples gallery.
Native chart — categories + series in, native vector bars out (no rasterization).
ChartData revenue = ChartData.builder()
.categories("Q1", "Q2", "Q3", "Q4")
.series("2024", 12.4, 15.1, 9.8, 14.2)
.series("2025", 14.0, 18.2, 11.3, 16.9)
.build();
section.chart(ChartSpec.bar().data(revenue)
.legend(LegendPosition.BOTTOM)
.size(ChartSize.aspectRatio(16, 7))
.build());Overshoot-free line — a smooth curve constrained to never overshoot the data range.
section.chart(ChartSpec.line().data(series)
.interpolation(LineInterpolation.MONOTONE)
.build());SVG import + alignment — parse SVG to native geometry, seat any fixed node across the width.
SvgIcon globe = SvgIcon.parse(svgMarkup);
flow.addSvgIcon(globe, 48, HorizontalAlign.CENTER);
flow.addAligned(HorizontalAlign.RIGHT, anyFixedNode);GraphCompose splits into a public canonical surface you author against (com.demcha.compose.document.*) and an internal shared engine foundation (com.demcha.compose.engine.*, marked @Internal) that resolves geometry, pagination, and rendering behind it. Since 2.0 that boundary is also a packaging boundary: the surface and engine ship in graph-compose-core, and each render backend is a separate module. The fixed-layout backends (PDF, PPTX) register through a ServiceLoader seam and consume the same resolved LayoutGraph, which is why a deck matches the PDF geometrically. The semantic DOCX exporter registers nothing — you name it directly, and it walks the node tree without a layout pass. You author intent; the engine resolves the rest.
flowchart LR
A["GraphCompose.document(...)<br/>DocumentSession · DocumentDsl"] --> B["DocumentNode tree<br/>document.node"]
B --> C["LayoutCompiler<br/>document.layout"]
C --> D["Engine foundation @Internal<br/>measure → paginate → place"]
D --> E{ServiceLoader}
E -->|render-pdf| F["PdfFixedLayoutBackend<br/>PDFBox"]
E -->|render-pptx| G["PptxFixedLayoutBackend<br/>POI · same LayoutGraph as the PDF"]
B -.->|render-docx · named directly| I["DocxSemanticBackend<br/>POI · no layout pass"]
D -.->|layoutSnapshot| H["Deterministic snapshot<br/>(regression tests)"]
Full detail: architecture overview · package map · lifecycle.
The repository is a Maven multi-module reactor: the root pom.xml is the build aggregator, so ./mvnw clean verify at the root builds and tests every module (scope to one with -pl :<artifactId>; the lean engine lives in core/).
- Published to Maven Central
graph-compose-core(core/) — the lean document enginegraph-compose-render-pdf·-render-docx·-render-pptx— render backendsgraph-compose-templates— built-in CV / cover-letter / invoice / proposal presetsgraph-compose-testing— snapshot & visual-regression test helpersgraph-compose— the drop-in wrapper (core + PDF);graph-compose-bundle— batteries-included (adds templates + fonts + emoji)
- Companion artifacts (independent version lines) —
graph-compose-fonts,graph-compose-emoji - Development only (never published) —
qa(architecture guards + visual regression),coverage(aggregate JaCoCo),examples,benchmarks
See CONTRIBUTING for the branch-routing table and the full build / verify flow.
📚 Full docs index — categorised map of every doc, ADR, and recipe. Start there to navigate the documentation.
- Templates — layered architecture — the template surface: CV, cover-letter, invoice, and proposal preset families on
BrandTheme. Personas: quickstart · using templates · authoring presets · contributing a new family. - Which template system? — the template naming history and the migration map for callers arriving from a pre-2.0 surface (classic presets, built-in
*Templateclasses, the legacy PDF API). The retired classic docs are archived at v1-classic.
- Architecture overview · Lifecycle · Production rendering · Benchmarks · Layout snapshot testing · Troubleshooting
- Recipes index — shape-as-container · shapes · transforms · page-backgrounds · layered-page-design · absolute-placement · tables · themes · streaming · extending · font-coverage
- Examples gallery — every runnable example with PDF preview
- Contributing · Code of conduct · Security policy · Release process
- API stability policy · Which template system? · Migration to 2.0 (modules) · older migration notes
- graph-compose-markdown — a Markdown → PDF path built on the GraphCompose engine. Hand it a Markdown document and it renders through the same layout, theme, and PDFBox pipeline as the Java DSL — a companion input surface for teams who would rather author in Markdown than call the DSL directly. Published on Maven Central as
io.github.demchaav:graph-compose-markdown; independent lifecycle, consumes the engine as a dependency. - graphcompose-ai-flow — experimental sister project exploring an AI-assisted authoring flow on top of GraphCompose. Independent codebase, separate lifecycle — nothing in this repo depends on it. Track it if you are interested in agentic document composition driven by the same semantic node model.
MIT — see LICENSE.




