Skip to content
Closed
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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,25 @@ follow semantic versioning; release dates are ISO 8601.

### Documentation

- **The engine deck stopped calling a shipped backend planned.** Its first page listed
PPTX as *Planned* beside a version badge reading v2.1.0 — the release that shipped it,
and the release whose own copy of that deck is published as a `.pptx`. The page
described the library as generating "structured business PDF documents" and its
pipeline as ending in "PDFBox writes the bytes", both written when PDF was the only
output. PPTX is now listed as the live, `@Beta` backend it is, and the prose covers
both. The benchmark pages are redrawn from a fresh five-run median: at 1000 rows
GraphCompose renders in 40.0 ms against iText's 170.1 ms and JasperReports' 38.5 ms,
on 19.3 MB of peak heap against 68.0 MB and 52.4 MB — the same shape the previous
snapshot showed, with Jasper at parity on time and GraphCompose far lighter on memory.
- **README leads with the banner, and gathers the documents that explain the engine.**
The hero is the engine deck's opening page on a page cropped to wrap it, rendered
straight to a raster through the image backend — no intermediate PDF, no external
rasteriser. `GenerateAllExamples` now renders that banner as a document too, so
`engine-banner.pdf` is a published preview the drift gate refreshes and compares like
any other rather than a file only one entry point could produce. A new section
collects the five documents that describe the engine by being its output: the deck,
the twin PDF/PPTX pair, the feature catalogue, the master showcase and the Maven
Central banner, each with what it shows and the example that renders it.
- **The PowerShell commands run as written.** Two pages handed Windows readers a
command that fails. PowerShell splits a `-D` flag whose property name contains a dot,
passing the native command `-Dexec` and `.mainClass=…` as separate arguments, and
Expand Down
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# GraphCompose

<p align="center">
<img src="./assets/GraphComposeLogo.png" alt="GraphCompose logo" width="300"/>
<a href="./assets/readme/examples/engine-banner.pdf"><img src="./assets/readme/engine-banner.png" alt="GraphCompose — a declarative Java DSL: authored code, resolved layout, PDF and PPTX output" width="900"/></a>
</p>

<p align="center">
<sub>This banner is a GraphCompose document, and the image above is the engine rendering it &mdash; straight to a raster, no intermediate PDF.<br/>
It is the opening page of the <a href="./assets/readme/examples/engine-deck.pdf">engine deck</a> &middot; <a href="./assets/readme/examples/engine-banner.pdf">as a PDF</a> &middot; <a href="./examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java">the source that renders both</a></sub>
</p>

<p align="center">
Expand Down Expand Up @@ -383,6 +388,26 @@ GraphCompose uses PDFBox under the hood as the rendering backend &mdash; the com

> **Templates in 2.0** &mdash; there is one template surface: the layered preset families in `graph-compose-templates`, themed through `BrandTheme`. Arriving from a pre-2.0 surface (classic presets, the built-in `*Template` classes)? **[Which template system should I use?](./docs/templates/which-template-system.md)** maps every retired name to its layered replacement.

## The engine, explained by the engine

Every document below is rendered by GraphCompose from a runnable example in this
repository, and re-rendered on every release. They are the fastest way to see what the
engine does without cloning anything &mdash; and each one is the engine's own output, so
what it shows is what it can do.

| Document | What it shows | Source |
|---|---|---|
| **[Engine deck](./assets/readme/examples/engine-deck.pdf)** (4 pages) | The pipeline end to end &mdash; DSL &rarr; measure &rarr; paginate &rarr; render &mdash; then the measured comparison against iText 9 and JasperReports, and how all three scale from 40 to 1000 rows. Pages 3&ndash;4 are drawn from the benchmark result file, not typed in. | [EngineDeckExample](./examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java) |
| **[Twin output](./assets/readme/examples/twin-output.pdf)** &middot; [PPTX](./assets/readme/examples/twin-output.pptx) | One composition, two files. Open them side by side: same geometry, one print-ready, one editable in PowerPoint. | [TwinOutputExample](./examples/src/main/java/com/demcha/examples/flagships/TwinOutputExample.java) |
| **[Feature catalogue](./assets/readme/examples/feature-catalog.pdf)** (7 pages) | Self-documenting blocks: each shows the exact API call in a grey panel and renders the result directly beneath it. Headings land in the PDF outline, so your viewer's bookmark panel is the index. | [FeatureCatalogExample](./examples/src/main/java/com/demcha/examples/flagships/FeatureCatalogExample.java) |
| **[Master showcase](./assets/readme/examples/master-showcase.pdf)** &middot; [PPTX](./assets/readme/examples/master-showcase.pptx) | A realistic report &mdash; modules, sections, rows, tables, shapes, layers in one document, from a single source file. | [MasterShowcaseExample](./examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java) |
| **[Maven Central banner](./assets/readme/examples/maven-banner.pdf)** &middot; [PPTX](./assets/readme/examples/maven-banner.pptx) | The release announcement card &mdash; coordinate, capability chips, and the code &rarr; layout &rarr; document pipeline in one 16:9 frame. | [MavenBannerPptxExample](./examples/src/main/java/com/demcha/examples/flagships/MavenBannerPptxExample.java) |

The benchmark numbers in the deck come from this repository's own harness &mdash;
[how to run it yourself](./docs/operations/benchmarks.md). Read the ratios rather than
the milliseconds: all three engines are measured in the same run on the same machine, so
their proportions survive a change of hardware while the timings do not.

## Documentation

📚 **[Full docs index](./docs/README.md)** &mdash; categorised map of every doc, ADR, and recipe. Start there to navigate the documentation.
Expand Down
Binary file added assets/readme/engine-banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/readme/examples/engine-banner.pdf
Binary file not shown.
Binary file modified assets/readme/examples/engine-deck-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/engine-deck.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ public static void main(String[] args) throws Exception {
System.out.println("Generated: " + BusinessReportExample.generate());
System.out.println("Generated: " + BusinessReportPptxExample.generate());
System.out.println("Generated: " + EngineDeckExample.generate());
// The deck's opening page again, on a page cropped to wrap it: README's hero,
// and the only banner the catalogue renders. Without it here the committed
// preview would be a file nothing can refresh, which the drift gate refuses.
System.out.println("Generated: " + EngineDeckExample.generateBanner());
System.out.println("Generated: " + EngineDeckV2Example.generate());
System.out.println("Generated: " + EngineDeckPptxExample.generate());
// The PDF first: ShowcaseSync builds a card per PDF and publishes a PPTX only
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ record Backend(String icon, String title, String sub, boolean live) {
static List<Backend> backends() {
return List.of(
new Backend("pdf-file", "PDFBox 3.0", "Production backend", true),
new Backend("docx", "DOCX export", "Semantic export", false),
new Backend("ppt-file", "PPTX", "Planned", false));
new Backend("ppt-file", "PPTX", "Editable deck · Beta", true),
new Backend("docx", "DOCX export", "Semantic export", false));
}

// ── "How it works" content (page 2) ───────────────────────────────────────
Expand All @@ -128,7 +128,7 @@ static List<PipelineStep> pipeline() {
new PipelineStep("1", "dsl", "AUTHOR", "Fluent DSL describes intent."),
new PipelineStep("2", "layout", "MEASURE", "Two-pass geometry, every node."),
new PipelineStep("3", "page-break", "PAGINATE", "Split the flow across pages."),
new PipelineStep("4", "pdf-file", "RENDER", "PDFBox writes the bytes."));
new PipelineStep("4", "pdf-file", "RENDER", "A backend writes the bytes."));
}

/** A "why it's solid" proof card: bold title + a sentence. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,13 @@ public static Path generate() throws Exception {
/**
* Renders the standalone hero banner to a raster image straight from the
* engine via {@link DocumentSession#toImage(int, int)} ({@code @since 1.9.0})
* — no intermediate PDF and no external rasterizer. This is the source of the
* repository README hero ({@code assets/readme/repository_showcase_render.png},
* written by {@link com.demcha.examples.support.ReadmeBannerRenderer}). The
* — no intermediate PDF and no external rasterizer. It is written to
* {@code assets/readme/engine-banner.png} by
* {@link com.demcha.examples.support.ReadmeBannerRenderer} and is README's
* opening hero. (The module-first hero further down that page,
* {@code repository_showcase_render.png}, is a different composition and comes
* from {@code EngineDeckV2Example} through {@code ReadmeBannerV2Renderer} —
* that is the one {@code cut-release.ps1} re-renders on a tag.) The
* dark violet field is the canonical {@code pageBackground} on a page cropped
* to wrap the content, so the image is all banner and no white frame. The
* version pill reads {@link #VERSION} / {@link #CODENAME} from the filtered
Expand Down Expand Up @@ -260,7 +264,7 @@ static void compose(DocumentSession document) {
// ═════════ PAGE 2 — how it works ═════════
.addPageBreak(b -> b.name("ToHowItWorks"))
.addSection("HowKicker", s -> kicker(s, "HOW IT WORKS",
"From one Java file to a designed PDF"))
"From one Java file to a designed document"))
.addSection("HowBody", s -> s
.padding(DocumentInsets.zero())
.addParagraph(p -> p
Expand All @@ -273,7 +277,8 @@ static void compose(DocumentSession document) {
.accent("paginates", VIOLET_DEEP)
.plain(" the flow row-by-row, and ")
.accent("renders", VIOLET_DEEP)
.plain(" through an isolated PDFBox backend. No manual coordinates, no XML templates."))
.plain(" the resolved layout through an isolated backend — PDFBox for PDF, "
+ "Apache POI for the deck. No manual coordinates, no XML templates."))
.lineSpacing(1.55)))
.addRow("Pipeline", row -> {
row.spacing(8).weights(1, 0.14, 1, 0.14, 1, 0.14, 1);
Expand Down Expand Up @@ -375,7 +380,8 @@ private static void banner(SectionBuilder s) {
.add(brandLine())
.addParagraph(p -> p
.text("Open-source Java library for generating structured business "
+ "PDF documents with a declarative DSL.")
+ "documents with a declarative DSL — print-ready PDF and an "
+ "editable PowerPoint deck from one composition.")
.textStyle(tagline()).lineSpacing(1.3).margin(DocumentInsets.top(4)))
.addShape(sh -> sh.size(749, 1.2).fillColor(RULE_DARK).margin(DocumentInsets.top(8)))
.addRow("Flow", row -> {
Expand Down
75 changes: 39 additions & 36 deletions examples/src/main/resources/benchmarks/comparative.json
Original file line number Diff line number Diff line change
@@ -1,54 +1,57 @@
{
"timestamp" : "2026-07-26 23:00:56",
"timestamp" : "2026-08-04 18:32:48",
"warmupIterations" : 50,
"measurementIterations" : 100,
"libraries" : [ {
"library" : "GraphCompose Canonical",
"avgTimeMs" : 2.84,
"avgHeapMb" : 0.17
}, {
"library" : "iText 9",
"avgTimeMs" : 2.75,
"avgHeapMb" : 0.16
"library" : "GraphCompose (1000 rows)",
"avgTimeMs" : 40.04,
"avgHeapMb" : 19.33
}, {
"library" : "JasperReports",
"avgTimeMs" : 5.7,
"avgHeapMb" : 0.16
"library" : "GraphCompose (200 rows)",
"avgTimeMs" : 9.7,
"avgHeapMb" : 3.96
}, {
"library" : "GraphCompose (40 rows)",
"avgTimeMs" : 5.7,
"avgHeapMb" : 0.99
}, {
"library" : "iText 9 (40 rows)",
"avgTimeMs" : 14.54,
"avgHeapMb" : 3.05
"avgTimeMs" : 3.72,
"avgHeapMb" : 1.0
}, {
"library" : "JasperReports (40 rows)",
"avgTimeMs" : 9.89,
"avgHeapMb" : 2.53
"library" : "GraphCompose Canonical",
"avgTimeMs" : 1.79,
"avgHeapMb" : 0.17
}, {
"library" : "GraphCompose (200 rows)",
"avgTimeMs" : 15.26,
"avgHeapMb" : 3.93
"library" : "JasperReports",
"avgTimeMs" : 3.62,
"avgHeapMb" : 0.15
}, {
"library" : "iText 9 (200 rows)",
"avgTimeMs" : 57.57,
"avgHeapMb" : 13.81
"library" : "JasperReports (1000 rows)",
"avgTimeMs" : 38.49,
"avgHeapMb" : 52.44
}, {
"library" : "JasperReports (200 rows)",
"avgTimeMs" : 21.13,
"avgTimeMs" : 12.0,
"avgHeapMb" : 10.81
}, {
"library" : "GraphCompose (1000 rows)",
"avgTimeMs" : 64.08,
"avgHeapMb" : 19.16
"library" : "JasperReports (40 rows)",
"avgTimeMs" : 6.36,
"avgHeapMb" : 2.52
}, {
"library" : "iText 9",
"avgTimeMs" : 1.84,
"avgHeapMb" : 0.16
}, {
"library" : "iText 9 (1000 rows)",
"avgTimeMs" : 291.03,
"avgHeapMb" : 68.16
"avgTimeMs" : 170.09,
"avgHeapMb" : 68.04
}, {
"library" : "JasperReports (1000 rows)",
"avgTimeMs" : 64.77,
"avgHeapMb" : 52.53
} ]
"library" : "iText 9 (200 rows)",
"avgTimeMs" : 33.98,
"avgHeapMb" : 13.79
}, {
"library" : "iText 9 (40 rows)",
"avgTimeMs" : 8.86,
"avgHeapMb" : 3.04
} ],
"aggregation" : "median",
"sourceCount" : 5,
"sourceRuns" : [ "C:\\Dev\\Java\\GraphCompose\\target\\benchmarks\\comparative\\run-20260804-183003.json", "C:\\Dev\\Java\\GraphCompose\\target\\benchmarks\\comparative\\run-20260804-183050.json", "C:\\Dev\\Java\\GraphCompose\\target\\benchmarks\\comparative\\run-20260804-183127.json", "C:\\Dev\\Java\\GraphCompose\\target\\benchmarks\\comparative\\run-20260804-183204.json", "C:\\Dev\\Java\\GraphCompose\\target\\benchmarks\\comparative\\run-20260804-183247.json" ]
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ void everyCommittedPreviewMatchesWhatTheCatalogueRenders() throws Exception {
private static final Set<String> RASTER_FIGURES = Set.of(
"barcode-showcase.png",
"chart-showcase.png",
// README's opening hero: EngineDeckExample#renderBannerImage through
// ReadmeBannerRenderer, the deck's first page on a page cropped to wrap it.
// Beside the engine-banner.pdf this guard does compare — same composition,
// same session settings — so the figure moves only when that PDF does. It is
// not in the compared set because GenerateAllExamples writes documents, not
// rasters; nothing in the catalogue produces a PNG.
"engine-banner.png",
"feature-catalog.png",
"repository_showcase_render.png",
"social-card.png",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8305,11 +8305,11 @@
"computedY" : 190.262,
"placementX" : 644.667,
"placementY" : 190.262,
"placementWidth" : 34.185,
"placementWidth" : 39.18,
"placementHeight" : 13.875,
"startPage" : 4,
"endPage" : 4,
"contentWidth" : 34.185,
"contentWidth" : 39.18,
"contentHeight" : 13.875,
"margin" : {
"top" : 0.0,
Expand Down
Loading
Loading