Skip to content
Open
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
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ jobs:
cache: maven

- name: Build and run tests
# Reactor slice: the engine, the render-docx / render-pptx semantic backends,
# Reactor slice: the engine, the render-docx semantic backend, the
# render-pptx fixed-layout backend,
# the graph-compose wrapper + graph-compose-bundle (the published empty-jar
# aggregates — covered here so a packaging break is caught on every push, not
# only via the examples job), the extracted graph-compose-testing module, the
Expand Down Expand Up @@ -243,6 +244,14 @@ jobs:
# dependency) before the examples build resolves it.
run: ./mvnw -B -ntp -f testing/pom.xml -DskipTests install

- name: Install graph-compose-render-pptx (consumed by the examples module)
# The Engine Deck PPTX example renders the flagship deck through the
# fixed-layout PPTX backend, which is not on Central. Install it after
# the wrapper (its core + render-pdf compile dependencies) AND the
# testing module — -DskipTests skips execution but Maven still
# resolves render-pptx's test-scope graph-compose-testing dependency.
run: ./mvnw -B -ntp -f render-pptx/pom.xml -DskipTests install

- name: Install graph-compose-templates (consumed by the examples module)
# The flagship + template examples render the built-in CV/invoice/proposal
# presets, which now ship in graph-compose-templates.
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,18 @@ follow semantic versioning; release dates are ISO 8601.
- `docs/architecture/backend-capability-matrix.md` — a per-capability matrix
of what each render backend supports and which class implements it,
maintained as part of every capability-changing PR.
- The README's backend descriptions now present `graph-compose-render-pptx`
as the fixed-layout, geometry-identical PowerPoint backend (one page per
editable slide) instead of a semantic exporter, with its own
quick-start dependency row; the Engine Deck flagship example additionally
renders as a .pptx deck (`EngineDeckPptxExample`, part of
`GenerateAllExamples`).

### Build

- The examples CI job installs `graph-compose-render-pptx` before generating,
so the Engine Deck PPTX example resolves the backend the same way the DOCX
example resolves its own.

### Tests

Expand Down Expand Up @@ -158,6 +170,13 @@ follow semantic versioning; release dates are ISO 8601.
default-output-file and empty-session failure modes; the backend-free
missing-format diagnostics were already pinned by the core's
`MissingBackendContractTest`.
- The qa module now guards the PPTX backend's boundaries at bytecode level:
POI stays a PPTX-backend implementation detail (the engine, the PDF backend,
and the templates must not touch it), the render backends stay
template-agnostic, and the PDF backend must not depend on the PPTX artifact
(the reverse dependency is by design). The `ShapeOutline` exhaustiveness
guard now renders every permit through both fixed-layout backends, so a new
outline kind cannot silently miss a PPTX dispatch branch.

## v2.0.0 — 2026-07-13

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Sits between **iText** (low-level page primitives) and **JasperReports** (XML-te
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-core` is the document model, DSL, themes, and deterministic layout with **no PDFBox, POI, or template code** on its dependency tree. Backends plug in through a `ServiceLoader` seam; a core-only classpath asked to render throws `MissingBackendException` naming the artifact to add.
- **Opt-in render backends** — `graph-compose-render-pdf` (PDFBox 3.0, full DSL coverage), `graph-compose-render-docx` and `graph-compose-render-pptx` (Apache POI, semantic export).
- **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), `graph-compose-render-docx` (Apache POI, semantic export).
- **`graph-compose` stays 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](./docs/migration/v2.0.0-modules.md#the-one-break-templates) of the split.
- **`graph-compose-bundle`** — one batteries-included coordinate: PDF stack + templates + fonts + colour emoji.
Expand Down Expand Up @@ -91,7 +91,8 @@ dependencies { implementation("io.github.demchaav:graph-compose:2.0.0") }
> | **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` |
> | **DOCX / PPTX export** | add `graph-compose-render-docx` / `graph-compose-render-pptx` |
> | **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
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ are with the canonical DSL, then jump to its detailed section below.
| [Cover Letter](#cover-letter) | One-page `BusinessTheme.modern()` cover letter with section presets | [PDF](../assets/readme/examples/cover-letter.pdf) · [Source](src/main/java/com/demcha/examples/templates/coverletter/CoverLetterFileExample.java) |
| [Module-first Profile](#module-first-profile) | Authoring directly against `DocumentSession.module(...).paragraph(...)` — DSL-direct, no template | [PDF](../assets/readme/examples/module-first-profile.pdf) · [Source](src/main/java/com/demcha/examples/flagships/ModuleFirstFileExample.java) |
| **Engine Showcase** | Single-page cinematic brand promo — semantic-graph → polished-PDFs visual metaphor with rounded clip frame, magazine headline lockup, KPI cards, capability columns; source of the README hero image | [Source](src/main/java/com/demcha/examples/flagships/EngineShowcase.java) |
| **Engine Deck** | Multi-page **landscape** capability deck — page 1 is a banner infographic (DSL code → engine → backends → **real rendered-document thumbnails**), then an authoring-pipeline walkthrough, and two pages of **real benchmark data** (GraphCompose vs iText 9 vs JasperReports) loaded from a bundled result file and drawn as tables + native charts; the landscape companion to Engine Showcase | [PDF](../assets/readme/examples/engine-deck.pdf) · [Source](src/main/java/com/demcha/examples/flagships/EngineDeckExample.java) |
| **Engine Deck** | Multi-page **landscape** capability deck — page 1 is a banner infographic (DSL code → engine → backends → **real rendered-document thumbnails**), then an authoring-pipeline walkthrough, and two pages of **real benchmark data** (GraphCompose vs iText 9 vs JasperReports) loaded from a bundled result file and drawn as tables + native charts; the landscape companion to Engine Showcase. The same composition also renders as a **geometry-identical PowerPoint deck** (one page = one editable slide) through `buildPptx()` | [PDF](../assets/readme/examples/engine-deck.pdf) · [Source](src/main/java/com/demcha/examples/flagships/EngineDeckExample.java) · [PPTX source](src/main/java/com/demcha/examples/flagships/EngineDeckPptxExample.java) |

### 🧱 Core DSL

Expand Down
8 changes: 8 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@
<version>${graphcompose.version}</version>
</dependency>

<!-- The fixed-layout PPTX backend: the Engine Deck PPTX example renders
the flagship deck as a geometry-identical PowerPoint file. -->
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-render-pptx</artifactId>
<version>${graphcompose.version}</version>
</dependency>

<!-- The built-in CV / cover-letter / invoice / proposal templates that the
flagship + template examples render. -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import com.demcha.examples.features.transforms.TransformsExample;
import com.demcha.examples.flagships.BusinessReportExample;
import com.demcha.examples.flagships.EngineDeckExample;
import com.demcha.examples.flagships.EngineDeckPptxExample;
import com.demcha.examples.flagships.FeatureCatalogExample;
import com.demcha.examples.flagships.FinancialReportExample;
import com.demcha.examples.flagships.MasterShowcaseExample;
Expand Down Expand Up @@ -212,6 +213,7 @@ public static void main(String[] args) throws Exception {
System.out.println("Generated: " + FeatureCatalogExample.generate());
System.out.println("Generated: " + BusinessReportExample.generate());
System.out.println("Generated: " + EngineDeckExample.generate());
System.out.println("Generated: " + EngineDeckPptxExample.generate());
System.out.println("Generated: " + FinancialReportExample.generate());
System.out.println("Generated: " + BookTemplateExample.generate());
System.out.println("Generated: " + PoetryTitlePageExample.generate());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.demcha.examples.flagships;

import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.examples.support.ExampleOutputPaths;

import java.nio.file.Path;

/**
* The Engine Deck flagship rendered as a PowerPoint deck: the exact
* composition of {@link EngineDeckExample} — banner infographic, authoring
* pipeline, and two benchmark pages — built once and emitted through the
* fixed-layout PPTX backend via {@link DocumentSession#buildPptx()}. One
* resolved page becomes one identically-sized slide with the same geometry
* the PDF carries, so the .pptx opens in PowerPoint as a slide-per-page copy
* of the PDF deck — editable shapes and text frames, except clipped regions,
* which land as pixel-exact pictures.
*/
public final class EngineDeckPptxExample {

private EngineDeckPptxExample() {
}

/**
* Builds the landscape showcase deck as a .pptx.
*
* @return the generated file path
* @throws Exception when rendering or icon IO fails
*/
public static Path generate() throws Exception {
Path outputFile = ExampleOutputPaths.prepare("flagships", "engine-deck.pptx");
try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4.landscape())
.margin(16, 16, 30, 16)
.create()) {
EngineDeckExample.compose(document);
document.buildPptx();
}
return outputFile;
}

/**
* Generates the deck from the command line.
*
* @param args unused
* @throws Exception when rendering fails
*/
public static void main(String[] args) throws Exception {
System.out.println("Generated: " + generate());
}
}
9 changes: 9 additions & 0 deletions qa/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- The PPTX backend — on the qa classpath so the cross-module boundary
rules have POI-using subjects and the outline-coverage guard can
render every ShapeOutline permit through both fixed-layout backends. -->
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-render-pptx</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- The built-in templates (the relocated visual-parity + smoke suites
render them) plus their tests-classifier jar, which carries the template
data fixtures (com.demcha.mock.*DataFixtures). -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
/**
* Cross-module architecture guards, enforced at bytecode level. The engine
* ({@code graph-compose-core}), the PDF backend ({@code graph-compose-render-pdf}),
* and the built-in templates ({@code graph-compose-templates}) only meet on one
* classpath here in the qa module, so the boundaries <em>between</em> them can
* only be asserted from here.
* the PPTX backend ({@code graph-compose-render-pptx}), and the built-in
* templates ({@code graph-compose-templates}) only meet on one classpath here
* in the qa module, so the boundaries <em>between</em> them can only be
* asserted from here.
*
* <p>These complement the intra-core {@code ModuleBoundaryArchTest} in the engine
* module. In particular the templates &rarr; engine boundary went unguarded when
Expand All @@ -24,8 +25,8 @@
class CrossModuleBoundaryArchTest {

// Every module's production classes on the qa classpath (engine + render-pdf +
// templates + testing). DO_NOT_INCLUDE_TESTS drops qa's own test classes, which
// legitimately depend on all of them.
// render-pptx + templates + testing). DO_NOT_INCLUDE_TESTS drops qa's own test
// classes, which legitimately depend on all of them.
private static final JavaClasses MODULE_CLASSES = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.demcha.compose");
Expand All @@ -35,6 +36,10 @@ class CrossModuleBoundaryArchTest {
"com.demcha.compose.document.backend.fixed.pdf..",
"com.demcha.compose.engine.render.pdf.."
};
private static final String[] PPTX_BACKEND = {
"com.demcha.compose.document.backend.fixed.pptx..",
"com.demcha.compose.document.backend.semantic.pptx.."
};

@Test
void importedTheTemplatesAndEngineModuleClasses() {
Expand All @@ -45,6 +50,9 @@ void importedTheTemplatesAndEngineModuleClasses() {
.anyMatch(c -> c.getPackageName().startsWith("com.demcha.compose.document.templates")))
.as("the templates classes must be on the qa classpath so the boundary rules have subjects")
.isTrue();
assertThat(MODULE_CLASSES.contain("com.demcha.compose.document.backend.fixed.pptx.PptxFixedLayoutBackend"))
.as("the PPTX backend classes must be on the qa classpath so the POI rules have subjects")
.isTrue();
}

@Test
Expand All @@ -63,21 +71,58 @@ void templatesDoNotDependOnTheEngine() {
void templatesStayBackendNeutral() {
ArchRule rule = noClasses()
.that().resideInAPackage(TEMPLATES)
.should().dependOnClassesThat().resideInAPackage("org.apache.pdfbox..")
.because("templates are pure authoring code over the canonical DSL; PDFBox lives "
+ "behind the graph-compose-render-pdf backend seam, not in a preset");
.should().dependOnClassesThat()
.resideInAnyPackage("org.apache.pdfbox..", "org.apache.poi..")
.because("templates are pure authoring code over the canonical DSL; PDFBox and POI "
+ "live behind the render-backend seams, not in a preset");

rule.check(MODULE_CLASSES);
}

@Test
void thePdfBackendDoesNotDependOnTemplates() {
void theRenderBackendsDoNotDependOnTemplates() {
ArchRule rule = noClasses()
.that().resideInAnyPackage(PDF_BACKEND)
.that().resideInAnyPackage(concat(PDF_BACKEND, PPTX_BACKEND))
.should().dependOnClassesThat().resideInAPackage(TEMPLATES)
.because("the render backend is template-agnostic — it renders the resolved layout "
+ "graph and knows nothing about the built-in presets");
.because("the render backends are template-agnostic — they render the resolved "
+ "layout graph and know nothing about the built-in presets");

rule.check(MODULE_CLASSES);
}

@Test
void onlyRenderBackendsDependOnPoi() {
// The DOCX semantic backend also legitimately uses POI; it is not on
// the qa classpath today, but the exclusion keeps this rule correct
// the day render-docx joins for its own boundary guards.
ArchRule rule = noClasses()
.that().resideOutsideOfPackages(concat(PPTX_BACKEND,
new String[]{"com.demcha.compose.document.backend.semantic.docx.."}))
.should().dependOnClassesThat().resideInAPackage("org.apache.poi..")
.because("POI is a render-backend implementation detail — the engine, the PDF "
+ "backend, and the templates must stay free of it so the core artifact "
+ "never drags the POI dependency in");

rule.check(MODULE_CLASSES);
}

@Test
void thePdfBackendDoesNotDependOnThePptxBackend() {
// The reverse dependency is by design: the PPTX backend reuses the PDF
// measurement stack and raster sub-renders for geometry identity.
ArchRule rule = noClasses()
.that().resideInAnyPackage(PDF_BACKEND)
.should().dependOnClassesThat().resideInAnyPackage(PPTX_BACKEND)
.because("the PDF backend is the reference implementation and must remain "
+ "deployable without the PPTX artifact");

rule.check(MODULE_CLASSES);
}

private static String[] concat(String[] first, String[] second) {
String[] merged = new String[first.length + second.length];
System.arraycopy(first, 0, merged, 0, first.length);
System.arraycopy(second, 0, merged, first.length, second.length);
return merged;
}
}
Loading
Loading