A runnable, single-file Java example for every public surface in
GraphCompose — built-in templates, v1.5 cinematic features, public-API
showcases, and a kitchen-sink master demo. Each example writes a PDF
to examples/target/generated-pdfs/; the same PDFs are committed to
assets/readme/examples/ so you can
preview every render straight from GitHub without running anything.
Install the library artifact once from the repository root:
./mvnw -DskipTests installThen run all 26 examples in one shot:
./mvnw -f examples/pom.xml exec:java \
-Dexec.mainClass=com.demcha.examples.GenerateAllExamples…or run a single example by passing its main class:
./mvnw -f examples/pom.xml exec:java \
-Dexec.mainClass=com.demcha.examples.MasterShowcaseExampleGenerated PDFs land in examples/target/generated-pdfs/. The same
mvnw.cmd form works on Windows PowerShell with backslash paths.
| Example | What it shows | Preview · Source |
|---|---|---|
| Cover Letter | One-page BusinessTheme.modern() cover letter with section presets |
PDF · Source |
| Invoice (V1) | InvoiceTemplateV1 driven from InvoiceDocumentSpec |
PDF · Source |
| Proposal (V1) | ProposalTemplateV1 driven from ProposalDocumentSpec |
PDF · Source |
| Module-first Profile | Authoring directly against DocumentSession.module(...).paragraph(...) |
PDF · Source |
| CV — single template | One CV via ModernProfessional.create(BusinessTheme.modern()) (Templates v2) |
PDF · Source |
| CV — template gallery | All 14 v2 CV presets in one orchestrated run | Source |
| Cover letter — template gallery | All 14 paired v2 cover-letter presets in one orchestrated run | Source |
| Example | What it shows | Preview · Source |
|---|---|---|
| Invoice — cinematic V2 | InvoiceTemplateV2 + BusinessTheme.modern() |
PDF · Source |
| Proposal — cinematic V2 | ProposalTemplateV2 + BusinessTheme.modern() |
PDF · Source |
| Handcrafted Proposal | v1.4-style cinematic proposal composed by hand | PDF · Source |
| Example | What it shows | Preview · Source |
|---|---|---|
| Shape containers | Circles, ellipses, rounded cards with ClipPolicy.CLIP_PATH |
PDF · Source |
| Transforms | rotate, scale, and per-layer zIndex swap |
PDF · Source |
| Advanced tables | Row span, zebra rows, totals, repeating header on page break | PDF · Source |
| Custom Business Theme | Hand-built BusinessTheme driving InvoiceTemplateV2 |
PDF · Source |
| Example | What it shows | Preview · Source |
|---|---|---|
| Nested lists | ListBuilder.addItem(label, Consumer) — depth cascade, per-depth markers, mixed flat / nested authoring |
PDF · Source |
| Composed table cells | DocumentTableCell.node(DocumentNode) — paragraphs, lists, sub-tables inside cells with two-pass measurement |
PDF · Source |
| Canvas layer (free placement) | CanvasLayerNode — pixel-precise (x, y) placement of children inside a fixed bounding box, with ClipPolicy clipping |
PDF · Source |
| Example | What it shows | Preview · Source |
|---|---|---|
| Rich text | Every RichText method (bold / italic / underline / link / colour / accent / size / link / append) |
PDF · Source |
| Section presets | pageBackground, band, softPanel, accentLeft / Right / Top / Bottom, per-corner DocumentCornerRadius |
PDF · Source |
| Barcodes | QR, Code 128, Code 39, EAN-13, EAN-8, branded QR with theme colours | PDF · Source |
| PDF chrome | DocumentMetadata, DocumentWatermark, DocumentHeaderFooter, DocumentBookmarkOptions |
PDF · Source |
| Example | What it shows | Preview · Source |
|---|---|---|
| HTTP streaming | writePdf(OutputStream) for Servlet / S3 / GCS — caller's stream is not closed |
PDF · Source |
| Layout snapshot regression | Deterministic layoutSnapshot() workflow with baseline + drift report |
PDF · Source |
| Example | What it shows | Preview · Source |
|---|---|---|
| Weekly schedule | Bar / restaurant shift schedule via reusable WeeklyScheduleRenderer with typed DayShift API |
PDF · Source |
| Business report cover | Single-page Q1 investor brief — hero image, KPI cards, bar chart, metrics table | PDF · Source |
| Master showcase | Kitchen-sink "Q2 sample report" combining the canonical surface end-to-end | PDF · Source |
A one-page modern cover letter — BusinessTheme.modern() drives every
colour and font choice; section presets (softPanel, accentLeft,
accentTop) carry the visual hierarchy; opening rich-text strip
highlights the candidate's headline.
try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
.pageBackground(THEME.pageBackground())
.margin(56, 48, 56, 48)
.create()) {
document.pageFlow()
.name("CoverLetter")
.spacing(18)
.addRow("Header", row -> row
.weights(3, 1)
.addSection("Identity", section -> section
.addParagraph(p -> p.text("Mariia Demchyshyn")
.textStyle(THEME.text().h1())))
.addSection("Date", section -> section
.addParagraph(p -> p.text("15 May 2026"))))
.addSection("Headline", section -> section
.softPanel(THEME.palette().surfaceMuted(), 10, 18)
.accentLeft(ACCENT, 4)
.addRich(rich -> rich
.plain("I help teams ship ")
.style("designed PDFs as code", BOLD_BRAND)
.plain(" — semantic Java DSL, deterministic layout.")))
// … recipient block + body paragraphs + highlights row + closing …
.build();
document.buildPdf();
}InvoiceTemplateV1.compose(document, spec) handles the full layout —
header band, parties row, line-items table, totals row, payment-terms
footer — driven from a InvoiceDocumentSpec. Use this when you want
the legacy hard-coded theme; for V2 cinematic, see below.
InvoiceDocumentSpec spec = InvoiceDocumentSpec.builder()
.invoiceNumber("GC-2026-041")
.issueDate("02 Apr 2026")
.dueDate("16 Apr 2026")
.fromParty(p -> p.name("GraphCompose Studio"))
.billToParty(p -> p.name("Northwind Systems"))
.lineItem("Template architecture", "Reusable invoice flow", "2", "GBP 980", "GBP 1,960")
.totalRow("Total", "GBP 1,960")
.build();
try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
.margin(28, 28, 28, 28)
.create()) {
new InvoiceTemplateV1().compose(document, spec);
document.buildPdf();
}ProposalTemplateV1 rendered against a ProposalDocumentSpec —
sections, scope items, deliverables, sign-off. Pairs naturally with
InvoiceTemplateV1 for consistent "spec → PDF" pipelines.
Authoring against DocumentSession.pageFlow().module(...) — no
template, no theme, just the canonical DSL. Smallest possible footprint
for "I just need a one-page PDF from data".
document.pageFlow()
.module("Profile", module -> module
.heading("Mariia Demchyshyn")
.paragraph("Senior Backend Engineer")
.paragraph("mariia@example.com · +44 20 7946 0234"));
document.buildPdf();One CV rendered through the Templates v2 surface:
ModernProfessional.create(BusinessTheme.modern()) paired with a
CvSpec data shape. The preset is one final class with one
create(BusinessTheme) factory — copy-and-tweak rather than
fork-a-monolith.
Generates every v2 CV preset in one orchestrated run — 14 presets
covering single-column, two-column-sidebar, and three-column-magazine
layouts. Use this as the side-by-side catalogue when picking a base
preset for your own CV product. Each preset is a one-liner factory
(ModernProfessional.create(theme), NordicClean.create(theme),
…); see templates/cv/presets/ for the full list.
| Variant | |
|---|---|
| Modern professional | |
| Nordic clean | |
| Classic serif | |
| Compact mono | |
| Timeline minimal | |
| Engineering resume (was "Tech lead") | |
| Panel (was "Product leader") | |
| Executive · BoxedSections · CenteredHeadline · BlueBanner · EditorialBlue · SidebarPortrait · MonogramSidebar | run the gallery to render |
Generates all 14 paired v2 cover-letter presets in one run — one
letter style per CV preset so a candidate's CV and cover letter
share the same visual language end-to-end. Each preset is a
one-liner factory (ModernProfessionalLetter.create(theme),
NordicCleanLetter.create(theme), …) under
templates/coverletter/presets/.
InvoiceTemplateV2(BusinessTheme.modern()) — the cinematic invoice.
Hero softPanel with invoice number / dates / inline rich-text status,
two-column parties row, themed line-items table with headerStyle +
zebra + totals + repeatHeader(), footer row with accentLeft strips.
BusinessTheme theme = BusinessTheme.modern();
InvoiceTemplateV2 template = new InvoiceTemplateV2(theme);
try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
.pageBackground(theme.pageBackground())
.margin(28, 28, 28, 28)
.create()) {
template.compose(document, invoice);
document.buildPdf();
}ProposalTemplateV2 — same BusinessTheme-driven pattern as the
invoice. Hero rounded only on the right (DocumentCornerRadius.right(...)),
themed executive-summary panel, sender / recipient parties row,
theme.text().h2() headings, timeline + pricing tables with
repeatHeader(), zebra rows, and a totalRow(...).
A v1.4-style cinematic proposal composed by hand — no template — to
show how the same primitives compose without a XxxTemplateV2
wrapper. Useful starting point when your domain doesn't fit any
built-in template.
addContainer(...), addCircle(...), addEllipse(...) build a
ShapeContainerNode whose bounding box is dictated by a
ShapeOutline. Children are clipped via ClipPolicy.CLIP_PATH
(default), CLIP_BOUNDS, or OVERFLOW_VISIBLE. The
ShapeContainerBuilder exposes the same nine-point alignment
vocabulary as LayerStackBuilder plus position(node, dx, dy, anchor)
for screen-space nudges.
.addContainer(
ShapeOutline.RoundedRectangle.of(220, 140, 14),
ClipPolicy.CLIP_PATH,
DocumentColor.rgb(28, 31, 38), // background
DocumentStroke.of(DocumentColor.rgb(196, 153, 76), 1.0),
container -> container
.center(centeredImage) // 9-point alignment
.position(badge, 120, 18, Anchor.TOP_LEFT)) // pixel-preciseDocumentTransform.rotate(deg) and .scale(uniform | sx, sy) —
attached to any Transformable<T> builder
(ShapeContainerBuilder, ShapeBuilder, LineBuilder,
EllipseBuilder, ImageBuilder, BarcodeBuilder). Identity
transforms emit no markers, so layout snapshots stay byte-identical to
v1.4. Per-layer zIndex lets a layer declared earlier draw on top of
layers declared later — LayerStackNode.Layer and shape-container
layers both gain int zIndex (default 0).
.addCircle(60, ROYAL_BLUE, container -> container
.rotate(15)
.center(label))
.layer(redSquare, Anchor.CENTER, /* zIndex */ 10) // declared first, drawn on top
.layer(tealSquare, Anchor.CENTER) // declared second, drawn beneathDocumentTableCell.rowSpan(int) mirrors colSpan(int).
TableBuilder.zebra(odd, even) paints alternating rows.
totalRow(...) adds a bold-on-grey-blue totals row.
repeatHeader() re-emits the leading rows on every continuation page
when the table paginates.
table.columns(...)
.headerRow("Item", "Description", "Qty", "Unit", "Amount")
.repeatHeader() // pinned on every page
.zebra(zebraOdd, zebraEven)
.row("Tall", "Spans 3 rows", "—", "—", "—") // colSpan(2).rowSpan(3) cell
.row("…", …)
.totalRow("", "", "", "Total", "GBP 1,960");Build a BusinessTheme from raw DocumentPalette / SpacingScale /
TextScale / TablePreset records — no factory shortcut. Plug it
straight into InvoiceTemplateV2 to retheme the whole template
without touching any code that uses it.
BusinessTheme studioEmerald = new BusinessTheme(
new DocumentPalette(/* page, surface, surfaceMuted, ink, accent, … */),
SpacingScale.cinematic(),
new TextScale(/* h1, h2, body, caption fonts … */),
TablePreset.cinematic());
new InvoiceTemplateV2(studioEmerald).compose(document, invoice);Every RichText method laid out as labelled rows on a single A4 page:
plain, bold, italic, boldItalic, underline, strikethrough,
color, accent, size, style, link, append. Use this as the
visual reference when picking which call to make for inline text.
.addRich(rich -> rich
.plain("Customer ")
.bold("Northwind Systems")
.plain(" placed order ")
.accent("#GC-2026-041", BRAND_GOLD)
.plain(" — see ")
.link("invoice", "https://example.com/invoice/41")
.plain("."))pageBackground, band, softPanel, the four
accentLeft / accentRight / accentTop / accentBottom strips, and
per-corner DocumentCornerRadius (top, bottom, left, right,
only(...)) rendered side-by-side as recipe cards.
.addSection("Hero", section -> section
.softPanel(theme.palette().surfaceMuted(), 10, 18)
.accentLeft(theme.palette().accent(), 4)
.cornerRadius(DocumentCornerRadius.right(12))
.addParagraph(p -> p.text("Hero block").textStyle(theme.text().h1())))BarcodeBuilder with five symbology types — QR, Code 128,
Code 39, EAN-13, EAN-8 — plus a branded QR using the active
theme's foreground / background colours. ZXing is the encoder; the
PDF backend rasterises and embeds.
.addBarcode(b -> b
.symbology(BarcodeSymbology.QR_CODE)
.data("https://github.com/DemchaAV/GraphCompose")
.size(140, 140)
.foreground(theme.palette().ink())
.background(theme.palette().surface()))Backend-neutral DocumentMetadata, DocumentWatermark,
DocumentHeaderFooter (header + footer with {page} / {pages} / {date} tokens), and paragraph-level DocumentBookmarkOptions
materialising as PDF outline entries.
GraphCompose.document(outputFile)
.metadata(DocumentMetadata.builder()
.title("Q1 2026 Investor Brief")
.author("Mariia Demchyshyn")
.build())
.watermark(DocumentWatermark.draftStamp(theme.palette().muted()))
.headerFooter(DocumentHeaderFooter.tokens("Q1 Brief", "Page {page} of {pages}"))
.create();writePdf(OutputStream) for Servlet / S3 / GCS adopters. The caller's
stream is not closed by GraphCompose — pinned by
HttpStreamingDemoTest. A Spring Boot @RestController snippet in the
example javadoc shows the canonical wiring.
@GetMapping(value = "/invoice/{id}", produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<StreamingResponseBody> invoice(@PathVariable Long id) {
InvoiceDocumentSpec spec = invoiceService.loadInvoice(id);
StreamingResponseBody body = response -> {
try (DocumentSession document = GraphCompose.document()
.pageSize(DocumentPageSize.A4)
.pageBackground(BusinessTheme.modern().pageBackground())
.margin(28, 28, 28, 28)
.create()) {
new InvoiceTemplateV2(BusinessTheme.modern()).compose(document, spec);
document.writePdf(response); // streams directly, no in-memory PDF
}
};
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=invoice.pdf")
.body(body);
}The full compose → layoutSnapshot() → LayoutSnapshotJson.toJson(...)
workflow with a copy-and-paste baseline / drift report pattern, plus a
pointer to the production
LayoutSnapshotAssertions.assertMatches(document, "...") helper for
in-test usage.
DocumentSession document = GraphCompose.document(outputFile)…create();
new InvoiceTemplateV2(theme).compose(document, spec);
String snapshot = LayoutSnapshotJson.toJson(document.layoutSnapshot());
String baseline = Files.readString(baselinePath);
if (!baseline.equals(snapshot)) {
Files.writeString(driftPath, snapshot);
throw new AssertionError("Layout drift detected — diff " + driftPath);
}
document.buildPdf();Bar / restaurant weekly shift schedule rendered through the canonical
DSL via a reusable WeeklyScheduleRenderer. The renderer's typed API
lets you express any combination of full-day status fills, half-day
shifts (lunch / dinner), and cross-meal shifts without parsing strings:
import com.demcha.examples.support.WeeklyScheduleRenderer;
import com.demcha.examples.support.WeeklyScheduleRenderer.*;
private static final List<StaffMember> STAFF = List.of(
new StaffMember("AARON PARK", JobTitle.MANAGER),
new StaffMember("DIANA COLE", JobTitle.BARTENDER),
new StaffMember("JASPER LIN", JobTitle.BAR_BACK)
);
private static final Map<String, DayShift[]> SHIFTS = Map.ofEntries(
Map.entry("AARON PARK", new DayShift[] {
DayShift.acrossDay("09:00", "18:00", ShiftType.STOCK), // Mon — stock recon all day
DayShift.OFF, // Tue
DayShift.OFF, // Wed
DayShift.dinnerOnly("16:00", "00:00"), // Thu — dinner only
DayShift.OFF, // Fri
DayShift.shifts("11:00", "16:00", "16:00", "22:00"), // Sat — both halves
DayShift.shifts("08:00", "13:00", "13:00", "18:00") // Sun
}),
Map.entry("DIANA COLE", new DayShift[] {
DayShift.halves(Half.shift("12:00", "20:00"), Half.STANDBY),
// …
})
);
WeeklyScheduleRenderer.renderTo(outputFile, "AURORA",
LocalDate.of(2026, 5, 4), STAFF, WEEK, SHIFTS);The renderer auto-fills the seven date labels from weekStart
(Monday 4th / Tuesday 5th / …), sorts staff by JobTitle.ordinal()
so groups appear in declared order, and emits a separator row at every
job-title boundary so adding or removing a StaffMember never
requires updating positional indices. The colour palette and column
widths live behind Theme.aurora() and Layout.landscape() records,
so reskinning is one parameter swap.
📄 View PDF · 📜 Example source · 📜 Renderer source
Single-page Q1 investor brief — top band identifier, serif headline,
procedurally rendered hero image (Java Graphics2D PNG embedded via
DocumentImageData.fromBytes(...)), three gold-ringed KPI cards,
strategic-highlights bullet list paired with a five-quarter Revenue /
Profit bar chart, YoY metrics table, and a confidential / page-number
footer. Use this as the visual reference for landing-page hero shots.
Fictional "Q2 sample report" combining the canonical surface
end-to-end: BusinessTheme + page background + hero with rotated
shape container + branded QR + executive summary + zebra-striped
totals table + accent-bordered highlight cards + Code 128 footer
barcode. Reference it when composing your own multi-page documents.
Every example file follows the same shape:
public final class FooExample {
private static final BusinessTheme THEME = BusinessTheme.modern();
// … more constants if useful …
private FooExample() {
}
public static Path generate() throws Exception {
Path outputFile = ExampleOutputPaths.prepare("foo.pdf");
try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
.pageBackground(THEME.pageBackground())
.margin(28, 28, 28, 28)
.create()) {
document.pageFlow()
.name("Foo")
.spacing(12)
// … your composition …
.build();
document.buildPdf();
}
return outputFile;
}
public static void main(String[] args) throws Exception {
System.out.println("Generated: " + generate());
}
}This makes every example runnable on its own (main), callable
from GenerateAllExamples (generate() returns the output path),
and deterministic — the same data + same code always produces the
same bytes (verified by LayoutSnapshotRegressionExample).
For the canonical authoring patterns — builder hierarchy, theme
tokens, table presets, golden / anti-patterns, and a 40-line skeleton
for new templates — read
docs/template-authoring.md
once before writing your own.
| Path | What's there |
|---|---|
examples/src/main/java/com/demcha/examples/ |
One file per example, runnable via main |
examples/src/main/java/com/demcha/examples/support/ |
Reusable helpers (ExampleOutputPaths, WeeklyScheduleRenderer) |
examples/target/generated-pdfs/ |
Output of running the examples (gitignored) |
assets/readme/examples/ |
Committed PDF previews linked from this gallery |
docs/template-authoring.md |
Template authoring cheatsheet |
CHANGELOG.md |
Per-version surface changes (every example link is current to v1.6) |
