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.1 — a patch over the PowerPoint release: a heading no longer strands above a block that asked to stay whole, and the documented snippets were corrected against the shipped API.
graph-compose-render-pptx(new in 2.1.0) turns the same resolved layout into an editable deck — one page per slide, geometry-identical to the PDF, text and panels as native shapes,@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.
PowerPoint output needs graph-compose-render-pptx on the classpath in addition to graph-compose; without it buildPptx fails with a MissingBackendException naming the artifact. See Which artifact? below.
Path deck = Path.of("twin-output.pptx");
try (DocumentSession doc = GraphCompose.document(Path.of("twin-output.pdf"))
.pageSize(DocumentPageSize.SLIDE_16_9)
.create()) {
// … describe the page (see Hello world below)
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.
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") }That coordinate renders PDF out of the box: it aggregates the lean graph-compose-core
engine plus the graph-compose-render-pdf backend, so existing 1.x callers upgrade with
no code change.
Which artifact? — the 2.0 module split, when you want to take less or more
| Goal | Depend on |
|---|---|
| PDF — the 1.x default | graph-compose |
| Batteries-included (PDF + templates + fonts + emoji) | graph-compose-bundle |
| Lean core, bring your own backend | graph-compose-core |
| Built-in CV / cover-letter / invoice / proposal templates | add graph-compose-templates |
| PowerPoint deck, geometry-identical to the PDF | add graph-compose-render-pptx |
| DOCX export (semantic) | add graph-compose-render-docx |
Every 2.0 coordinate shares the graph-compose version (the fonts and emoji companions
keep their own lines). A bare graph-compose-core renders nothing until a backend is on
the classpath — opening a session (create()) throws MissingBackendException, which
names the artifact to add (graph-compose-render-pdf, already included in
graph-compose).
Bundled fonts & colour emoji — optional companions
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.graph-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 in graph-compose-bundle.
Distribution — Maven Central, hosted Javadocs, legacy JitPack
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.
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).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.
- Your first document — the five-minute path from an empty project to a rendered PDF.
- Getting started — DSL or templates, and how to choose; the first-render walk-through.
- Examples gallery — every runnable example, with a PDF you can preview without building anything.
The module-first release: the single jar became a family of per-concern artifacts, so you install exactly what you render, and graph-compose stayed a drop-in for PDF callers. Everything the 1.9 line added ships unchanged.
Full detail in CHANGELOG.md; every removed API and its replacement in the 2.0 modules migration guide.
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 — each links to its own README (what it is, when to depend on it, smallest complete example)
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(wrapper/) — 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.
| 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.
📚 Full docs index — categorised map of every doc, ADR, and recipe. Start there to navigate the documentation.
The index routes by what you are doing — first document, using or authoring a template, extending the engine, running in production. The entry points most people want directly:
- Templates — layered architecture (CV, cover letter, invoice, proposal on
BrandTheme) · which template system? for callers arriving from a pre-2.0 surface - Recipes — the cookbook: tables, themes, shapes, transforms, page backgrounds, streaming, extending
- Operations — production rendering · layout snapshot testing · troubleshooting
- Project — Contributing · Roadmap · Support · Security policy · API stability · Migration to 2.0
- 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.




